Когда мне следует использовать 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()может потерпеть неудачу!
Эндрю

Ответы:

134
char *some_memory = "Hello World";

создает указатель на строковую константу. Это означает, что строка «Hello World» будет где-то в доступной только для чтения части памяти, и у вас есть только указатель на нее. Вы можете использовать строку только для чтения. Вы не можете вносить в него изменения. Пример:

some_memory[0] = 'h';

Просит неприятностей.

С другой стороны

some_memory = (char *)malloc(size_to_allocate);

выделяет массив char (переменную), а some_memory указывает на эту выделенную память. Теперь этот массив доступен для чтения и записи. Теперь вы можете:

some_memory[0] = 'h';

и содержимое массива изменится на "hello World"

codaddict
источник
19
Чтобы уточнить, насколько мне нравится этот ответ (я дал вам +1), вы можете сделать то же самое без malloc (), просто используя массив символов. Что-то вроде: char some_memory [] = "Привет"; some_memory [0] = 'W'; тоже будет работать.
randombits
19
Вы правы. Вы можете сделать это. Когда вы используете malloc (), память динамически выделяется во время выполнения, поэтому вам не нужно фиксировать размер массива во время компиляции, также вы можете увеличить или уменьшить его, используя realloc (). Ничего из этого не может быть сделано, когда вы это делаете: char some_memory [] = "Привет"; Здесь, даже если вы можете изменить содержимое массива, его размер является фиксированным. Итак, в зависимости от ваших потребностей вы можете использовать любой из трех вариантов: 1) указатель на char const 2) динамически выделяемый массив 3) фиксированный размер, массив, выделенный во время компиляции.
codaddict
Чтобы подчеркнуть, что это только для чтения, вы должны написать. const char *s = "hi";Разве это не требуется стандартом?
Till Theis
1
@Till, нет, потому что вы объявили указатель, инициализированный базовым адресом строкового литерала «hi». s можно совершенно законно переназначить, чтобы указать на неконстантный char. Если вам нужен постоянный указатель на строку, const char const* s;
доступную
38

Для этого точного примера malloc мало пригоден.

Основная причина, по которой требуется malloc, - это когда у вас есть данные, время жизни которых должно отличаться от области кода. Ваш код вызывает malloc в одной подпрограмме, где-то хранит указатель и, в конечном итоге, вызывает free в другой подпрограмме.

Вторая причина заключается в том, что C не имеет возможности узнать, достаточно ли места в стеке осталось для выделения. Если ваш код должен быть на 100% устойчивым, безопаснее использовать malloc, потому что тогда ваш код может знать, что выделение не выполнено, и обрабатывать его.

R Сэмюэл Клатчко
источник
4
Жизненные циклы памяти и связанный с этим вопрос о том, когда и как ее освободить, являются важной проблемой для многих распространенных библиотек и программных компонентов. Как правило, у них есть хорошо задокументированное правило: «Если вы передадите указатель на эту одну из моих подпрограмм, вам нужно будет заблокировать ее. Я буду отслеживать ее и освобождать, когда я закончу с ней. " Распространенный источник неприятных ошибок - передача такой библиотеке указателя на статически выделенную память. Когда библиотека пытается его освободить (), программа вылетает. Недавно я потратил много времени на исправление ошибки, подобной той, которую написал кто-то другой.
Боб Мерфи,
Вы хотите сказать, что malloc () используется практически только тогда, когда есть сегмент кода, который будет вызываться несколько раз в течение жизненного цикла программы, который будет вызываться несколько раз и должен быть `` очищен '', поскольку malloc () сопровождается свободным ()? Например, в такой игре, как колесо фортуны, где после того, как вы угадаете и поместите ввод в назначенный массив символов, этот массив размером с malloc () может быть освобожден для следующего предположения?
Smith Will Suffice
Истинной причиной использования malloc действительно является время жизни данных. Предположим, абстрактный тип данных представлен модулем, он объявляет тип списка и процедуры для добавления / удаления элементов из списка. Эти значения элементов необходимо скопировать в динамически выделяемую память.
Rob11311 08
@Bob: эти неприятные ошибки, сделайте соглашение, что распределитель освобождает память намного лучше, в конце концов, вы можете ее переработать. Предположим, вы выделили память с помощью calloc, чтобы улучшить локальность ссылок, что раскрывает сломанный характер этих библиотек, потому что вам нужно вызвать free только один раз для всего блока. К счастью, мне не приходилось использовать библиотеки, которые определяют память как malloc-ed, это не традиция POSIX и, скорее всего, будет считаться ошибкой. Если они «знают», что вы должны использовать malloc, почему библиотечная процедура не делает этого за вас?
Rob11311 08
17

malloc - прекрасный инструмент для выделения, перераспределения и освобождения памяти во время выполнения, по сравнению со статическими объявлениями, такими как пример вашего hello world, которые обрабатываются во время компиляции и, следовательно, не могут быть изменены в размере.

Поэтому 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 ++ в соответствующих случаях, но это уже совсем другая тема.

Поэтому всякий раз, когда вы используете malloc, всегда убедитесь, что ваша память как можно freeхуже.

adam10603
источник
Отличный пример! Way to go ^ _ ^
Муса Аль-Хасси
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» определяет память, которая «обычно» не может быть изменена во время выполнения.

Марк Уилкинс
источник