Коли я повинен використовувати malloc в C, а коли ні?


94

Я розумію, як працює malloc (). Моє питання полягає в тому, що я побачу такі речі:

#define A_MEGABYTE (1024 * 1024)

char *some_memory;
size_t size_to_allocate = A_MEGABYTE;
some_memory = (char *)malloc(size_to_allocate);
sprintf(some_memory, "Hello World");
printf("%s\n", some_memory);
free(some_memory);

Я пропустив перевірку помилок заради стислості. Моє запитання: чи не можете ви просто зробити вищезазначене, ініціалізувавши вказівник на деяке статичне сховище в пам'яті? можливо:

char *some_memory = "Hello World";

У який момент вам насправді потрібно розподілити пам'ять самостійно, а не декларувати / ініціалізувати значення, які потрібно зберегти?


5
Re: Я пропустив перевірку помилок заради стислості - на жаль, занадто багато програмістів опускають перевірку помилок, оскільки вони не усвідомлюють, що malloc()можуть не вдатися!
Андрій

Відповіді:


132
char *some_memory = "Hello World";

створює вказівник на постійну константу. Це означає, що рядок "Hello World" опиниться десь у пам'яті лише для читання, і ви просто маєте на неї вказівник. Ви можете використовувати рядок як лише для читання. Ви не можете внести зміни до нього. Приклад:

some_memory[0] = 'h';

Просить клопоту.

З іншої сторони

some_memory = (char *)malloc(size_to_allocate);

виділяє масив char (змінну) та some_memory points на виділену пам'ять. Тепер цей масив є і читанням, і записом. Тепер ви можете:

some_memory[0] = 'h';

а вміст масиву змінюється на "привіт Світ"


19
Щоб лише пояснити, наскільки мені подобається ця відповідь (я дав вам +1), ви можете зробити те саме без malloc (), просто використовуючи масив символів. Щось на зразок: char some_memory [] = "Привіт"; some_memory [0] = 'W'; також буде працювати.
Випадкові орбіти

19
Ви маєте рацію. Ви можете це зробити. Коли ви використовуєте malloc (), пам'ять динамічно розподіляється під час виконання, тому вам не потрібно фіксувати розмір масиву під час компіляції, також u може змусити його зростати або зменшуватися за допомогою realloc () Жоден із цих дій не можна зробити, коли ви це зробите: char some_memory [] = "Привіт"; Тут, навіть якщо ви можете змінити вміст масиву, його розмір фіксований. Отже, залежно від ваших потреб ви використовуєте будь-який із трьох варіантів: 1) вказівник на char const 2) динамічно виділений масив 3) фіксований розмір, час компіляції виділеного масиву.
кодирування

Щоб підкреслити, що ви повинні писати лише для читання. const char *s = "hi";Чи цього стандарт не вимагає насправді?
До Theis

@ Досі, ні тому, що ви оголосили вказівник, ініціалізований на базову адресу рядкового буквального "привіт". s можуть бути перепризначені абсолютно законно, щоб вказувати на неконстантний символ. Якщо ви хочете постійний вказівник на рядок лише для читання, вам потрібноconst char const* s;
Rob11311

38

Для цього точного прикладу, malloc мало корисний.

Основна причина, що потрібен malloc - це наявність у вас даних, які повинні тривати протягом життя, що відрізняється від коду. Ваш код викликає malloc в одній процедурі, десь зберігає вказівник і зрештою дзвонить безкоштовно в іншу процедуру.

Вторинна причина полягає в тому, що C не може знати, чи достатньо місця в стеку для виділення. Якщо ваш код повинен бути 100% надійним, безпечніше використовувати malloc, тому що тоді ваш код може знати, що розподіл не вдався і обробити його.


4
Життєві цикли пам’яті та пов’язане з цим питання про те, коли і як з нею виводитись, є важливою проблемою багатьох загальних бібліотек та програмних компонентів. Вони, як правило, мають добре задокументоване правило: "Якщо ви передасте вказівник на цю одну з моїх процедур, вам потрібно мати це malloc'd. Я буду це відстежувати і звільняти, коли закінчу з ним". " Поширеним джерелом неприємних помилок є передача такої бібліотеки вказівника на статично виділену пам’ять. Коли бібліотека намагається звільнити (), програма виходить з ладу. Нещодавно я витратив багато часу на виправлення помилок, подібних до тих, які написав хтось інший.
Боб Мерфі

Ви хочете сказати, що єдиний час, коли malloc () використовується практично, це коли є сегмент коду, який буде викликаний кілька разів протягом життя програми, який буде викликаний кілька разів і його потрібно "очистити", оскільки malloc () супроводжується вільним ()? Наприклад, у такій грі, як колесо фортуни, де після того, як ти здогадаєшся, і введеш вхід у визначений масив char, що масив розміром malloc () може бути звільнений для наступної здогадки?
Сміт вистачить

Термін служби даних справді є справжньою причиною використання malloc. Припустимо, абстрактний тип даних представлений модулем, він оголошує тип Списку та процедури для додавання / видалення елементів зі списку. Ці значення елементів потребують копіювання в динамічно виділену пам'ять.
Rob11311

@Bob: ці неприємні помилки, домовтесь, що розподільник звільняє пам'ять, набагато вищий, адже ви, можливо, переробляєте її. Припустимо, ви виділили пам’ять з calloc для покращення локальності посилань, що викриває порушений характер цих бібліотек, оскільки вам потрібно зателефонувати безкоштовно лише один раз для всього блоку. На щастя, мені не доводилося використовувати бібліотеки, які визначають пам'ять як "malloc-ed", це не традиція POSIX, і, швидше за все, це вважатиметься помилкою. Якщо вони "знають", що ви повинні використовувати malloc, чому бібліотечна процедура не робить це за вас?
Rob11311

17

malloc - чудовий інструмент для розподілу, перерозподілу та звільнення пам’яті під час виконання, порівняно зі статичними деклараціями, такими як ваш привіт світовий приклад, які обробляються під час компіляції і тому не можуть бути змінені за розміром.

Таким чином, Malloc завжди корисний, коли ви маєте справу з даними довільного розміру, наприклад, читаючи вміст файлів або маючи справу з сокетами, і ви не знаєте про тривалість даних для обробки.

Звичайно, у такому тривіальному прикладі, як той, який ви подали, malloc - це не магічний "правильний інструмент для правильної роботи", але для більш складних випадків (наприклад, створення масиву довільного розміру під час виконання), це єдиний спосіб йти.


7

Якщо ви не знаєте точного обсягу пам'яті, яку потрібно використовувати, вам потрібно динамічне розподіл ( malloc). Прикладом може бути, коли користувач відкриває файл у вашій програмі. Вам потрібно буде прочитати вміст файлу в пам'яті, але, звичайно, ви не знаєте розмір файлу заздалегідь, оскільки користувач вибирає файл на місці, під час виконання. Отже, в основному це потрібно, mallocколи ви заздалегідь не знаєте розмір даних, з якими працюєте. Принаймні, це одна з основних причин використання malloc. У вашому прикладі з простою рядком, про який ви вже знаєте розмір часу компіляції (плюс ви не хочете його змінювати), не має сенсу динамічно розподіляти це.


Трохи поза темою, але ... потрібно бути дуже обережним, щоб не створювати витоків пам’яті під час використання malloc. Розглянемо цей код:

int do_something() {
    uint8_t* someMemory = (uint8_t*)malloc(1024);

    // Do some stuff

    if ( /* some error occured */ ) return -1;

    // Do some other stuff

    free(someMemory);
    return result;
}

Ви бачите, що з цим кодом не так? Існує умовна заява про повернення між mallocта free. Спочатку це може здатися нормальним, але подумайте. Якщо виникла помилка, ви збираєтеся повернутися, не звільняючи виділену пам'ять. Це поширене джерело витоку пам'яті.

Звичайно, це дуже простий приклад, і тут дуже легко помітити помилку, але уявіть сотні рядків коду, завалених покажчиками, mallocs, frees та різними видами обробки помилок. Речі можуть дуже швидко заплутатися. Це одна з причин, що я в більшості випадків віддаю перевагу сучасній C ++ над C, але це ціла тема.

Тому, коли ви користуєтесь malloc, завжди переконайтеся, що ваша пам'ять є максимально ймовірною free.


Відмінний приклад! Шлях ^ _ ^
Муса Аль-хассі,

6
char *some_memory = "Hello World";
sprintf(some_memory, "Goodbye...");

є незаконним, рядкові літерали є const.

Це виділить 12-байтовий масив символів на стеку або в усьому світі (залежно від того, де це оголошено).

char some_memory[] = "Hello World";

Якщо ви хочете залишити місце для подальших маніпуляцій, ви можете вказати, що масив повинен бути розміром більше. (Будь ласка, не ставте 1 Мб у стек.)

#define LINE_LEN 80

char some_memory[LINE_LEN] = "Hello World";
strcpy(some_memory, "Goodbye, sad world...");
printf("%s\n", some_memory);

5

Однією з причин, коли необхідно виділити пам'ять, є те, що ви хочете її змінити під час виконання. У цьому випадку можна використовувати malloc або буфер на стеці. Простий приклад присвоєння покажчику "Hello World" визначає пам'ять, яку "типово" не можна змінювати під час виконання.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.