С++: определение локального массива по сравнению с вызовом malloc

В чем разница между этим:

somefunction() {  
    ...  
    char *output;   
    output = (char *) malloc((len * 2) + 1);  
    ...  
}  

и это:

somefunction() {  
    ...  
    char output[(len * 2) + 1];  
    ...  
}  

Когда одно лучше другого?

спасибо всем за ваши ответы. вот резюме:

  1. бывший. 1 - выделение кучи
  2. бывший. 2 - это распределение стека
  3. в стеке есть ограничение по размеру, используйте его для меньших аллокаций
  4. вам нужно освободить выделение кучи, иначе она будет протекать
  5. выделение стека недоступно после выхода из функции
  6. выделение кучи доступно до тех пор, пока вы его не освободите (или приложение не завершится)
  7. VLA не являются частью стандартного C++

поправки приветствуются.

вот некоторое объяснение разницы между кучей и стеком:
Что и где такое стек и куча?


person Gush    schedule 17.07.2010    source источник
comment
Вам следует обратиться к своей книге по C++. Если у вас нет книги по С++, я бы рекомендовал получить один из текстов для начинающих, перечисленных в Полное руководство и список книг по C++.   -  person James McNellis    schedule 17.07.2010
comment
Кроме того, вопрос полностью зависит от того, как определяется len; если это не константа, то второй пример кода является некорректным C++.   -  person James McNellis    schedule 17.07.2010
comment
len является целым числом и присваивается результату strlen внутри функции.   -  person Gush    schedule 17.07.2010
comment
Я не понимаю людей с их ответом на покупку книги. Разве не на каждый вопрос есть ответ в какой-нибудь книге? Если вы не имеете в виду, что на этот вопрос отвечает множество книг, в таком случае основные вопросы неприемлемы при великом переполнении стека? Я не имею в виду указатели на конкретные книги, такие как Джеймс Макнеллис, просто указатели на книги в целом, такие как Брайан Р. Бонди. Как насчет фильтрации ваших поисков вопросов, помеченных как основные? Спасибо, но за предложение. Как пишется буква С?   -  person Gush    schedule 17.07.2010
comment
@Gush: Моя рекомендация обратиться к хорошей книге — серьезная рекомендация. Нельзя выучить язык, особенно такой сложный, как C++, исключительно задавая вопросы в Интернете. Любой вводный текст по C или C++ будет охватывать эту тему очень рано, поскольку продолжительность хранения объектов является фундаментальной частью языка. Вопросы, которые являются тривиально базовыми, хотя и разрешены на SO, сбивают с толку, потому что они часто указывают на то, что спрашивающий может изучать язык из плохого источника (я не говорю, что это обязательно имеет место с вами, просто это часто имеет место ).   -  person James McNellis    schedule 17.07.2010
comment
@Brian: Gush делает хорошее замечание; Вы бы не хотели, чтобы он купил книгу Шильда, не так ли? ;-) (@Gush: Существует Полное руководство и список книг C в котором перечислены некоторые хорошие тексты на C).   -  person James McNellis    schedule 17.07.2010
comment
@James - Если я не совсем ясно выразился, я не имел в виду рекомендации по конкретным книгам - как у вас, - которые, кстати, я нашел полезными, и я ценю ваше время.   -  person Gush    schedule 17.07.2010
comment
@Gush: Точная книга C, которую нужно купить, на самом деле не имеет значения, каждая возможная будет охватывать распределение стека и кучи. Я рекомендую купить книгу, потому что это гораздо лучший способ выучить C, чем задавать серию основных вопросов. Вы можете быть оскорблены моей рекомендацией купить книгу, но, честно говоря, это лучшее предложение, которое вы можете получить.   -  person Brian R. Bondy    schedule 17.07.2010
comment
@Gush: вот некоторые отличия: stackoverflow.com/questions/79923/   -  person Brian R. Bondy    schedule 17.07.2010
comment
Если вы используете C++, просто используйте std::vector.   -  person jamesdlin    schedule 17.07.2010


Ответы (5)


Используйте локальные переменные, когда у вас есть только небольшой объем данных, и вы не собираетесь использовать данные вне области действия функции, в которой вы их объявили. Если вы собираетесь передавать данные, используйте malloc.

Локальные переменные хранятся в стеке, который гораздо больше ограничен по размеру, чем куча, куда помещаются массивы, выделенные с помощью malloc. Обычно я использую все, что > 16 байт, помещаемое в кучу, но у вас есть немного больше гибкости, чем это. Просто не выделяйте локальные файлы в диапазоне размеров kb/mb — они принадлежат куче.

person Mark H    schedule 17.07.2010
comment
+1 за упоминание ограничений размера стека, но я думаю, что 16 байт - это немного мало. Разве размер стека по умолчанию на большинстве платформ не составляет мегабайты (поэтому в большинстве случаев переменные с несколькими КБ должны подойти)? - person Brendan Long; 17.07.2010
comment
Да, но когда вы размещаете в стеке, вы также должны учитывать время жизни данных, а также их размер. Я имел в виду 16 байт для значений, время жизни которых может быть неизвестно. Вы можете выделить несколько КБ в стеке, а затем перейти к другой функции, которая также выделяет много КБ, и так далее — он может быстро заполниться. (Подробнее это обсуждалось в ответе StackedCrooked.) - person Mark H; 17.07.2010
comment
Поскольку вопрос касается C++, я категорически против рекомендации использовать malloc. В C используйте malloc. В C++ используйте new. - person William Pursell; 17.07.2010

Первый выделяет память в куче. Вы должны помнить, чтобы освободить память, или она будет утечка. Это уместно, если память нужно использовать вне функции или если вам нужно выделить огромный объем памяти.

Второй выделяет память в стеке. Он будет восстановлен автоматически, когда функция вернется. Это наиболее удобно, если вам не нужно возвращать память звонящему.

person Ned Batchelder    schedule 17.07.2010
comment
С другой стороны, вы не хотите помещать слишком много в стек, потому что он не такой большой, как куча. Итак, int data[10] в порядке, но я бы не стал рассчитывать на то, что int data[10000] вообще сработает. - person zvone; 17.07.2010

Первый пример выделяет блок памяти из кучи. Второй выделяет память из стека. Разница становится заметной, когда вы возвращаете вывод из функции somefunction(). Динамически выделенное хранилище по-прежнему доступно для вашего использования, но хранилище на основе стека во втором примере, гм, нигде. Вы по-прежнему можете писать в это хранилище и читать его некоторое время, пока в следующий раз вы не вызовете функцию, и в это время хранилище будет случайным образом перезаписано адресами возврата, аргументами и т. д.

В этом вопросе также происходит много других странных вещей с кодом. Во-первых, если это программа на С++, вы бы хотели использовать new вместо malloc(), поэтому вы бы сказали

output = new char[len+1];

И что вообще с len*2 + 1? Возможно, это что-то особенное в вашем коде, но я предполагаю, что вы хотите выделить символы Юникода или многобайтовые символы. Если это unicode, нулевое завершение занимает два байта, а также каждый символ, а char является неправильным типом, поскольку в большинстве компиляторов это 8-битные байты. Если это мультибайт, то эй, все ставки сняты.

person seattlecpp    schedule 17.07.2010
comment
Я не знаю, правильно ли то, что я делаю, но это вытекает из документов для PQescapeStringConn - 'to' должен указывать на буфер, который может содержать как минимум на один байт больше, чем удвоенное значение длины отсюда: postgresql.org/docs/7.3/static/libpq-exec.html - person Gush; 17.07.2010

Сначала немного терминологии:

  • Первый образец называется выделением кучи.
  • Второй пример называется распределение стека.

Общее правило: размещать в стеке, если только:

  1. Требуемый размер массива неизвестен во время компиляции.
  2. Требуемый размер превышает 10% от общего размера стека. Размер стека по умолчанию в Windows и Linux обычно составляет 1 или 2 МБ. Таким образом, ваш локальный массив не должен превышать 100 000 байт.
person StackedCrooked    schedule 17.07.2010
comment
Я думаю, что эти общие правила немного слабы. Например, я мог бы выделить 10% своего стека в main(), а затем вызвать какую-то другую функцию (которая, в свою очередь, будет вызывать все больше и больше, заполняя стек). Я не смогу освободить это пространство, пока не будут завершены все мои более глубокие функции (в данном случае, по сути, конец программы). Моя программа автоматически получила на 10% меньше места в стеке. Я думаю, что правило 10% должно применяться только к чистым функциям. - person Mark H; 17.07.2010
comment
@Mark H: Вы правы в том, что правило 10% не должно выделяться в основном и, конечно же, не в реентерабельном коде. Однако я не вижу, насколько чистые функции могут быть безопаснее от переполнения стека, чем нечистые функции. - person StackedCrooked; 17.07.2010
comment
Чистая функция не будет вызывать другие функции (если только они сами не являются чистыми), но обычно они выполняют какой-то алгоритм и немедленно возвращаются. Любые локальные переменные, которые вы размещаете в них, будут недолговечными, как и должен быть стек. Переменные с более длительным временем жизни (как в предыдущем примере) должны размещаться в куче. - person Mark H; 17.07.2010
comment
Пока чистая функция не является рекурсивной, то есть :) - person StackedCrooked; 17.07.2010

Вы пометили свой вопрос как C++, так и C, но второе решение не разрешено в C++. Массивы переменной длины разрешены только в C(99).

Если бы вы предположили, что «len» является константой, оба варианта будут работать.

malloc() (и "новый" C++) выделяют память в куче, что означает, что вы должны освободить () (или, если вы выделили "новый", "удалить") буфер, или память никогда не будет восстановлена ​​( утечка).

Последний выделяет массив в стеке и исчезнет, ​​когда выйдет за пределы области видимости. Это означает, что вы не можете возвращать указатели на буфер за пределами области, в которой он выделен.

Первый полезен, когда вы хотите передать блок памяти (но в C++ лучше всего управлять этим с помощью класса RAII, а не вручную), в то время как последний лучше всего подходит для небольших массивов фиксированного размера, которые должны существовать только в одном объем.

Наконец, вы можете пометить массив, выделенный в стеке, как «статический», чтобы вывести его из стека в глобальный раздел данных:

static char output[(len * 2) + 1];

Это позволяет вам возвращать указатели на буфер вне его области действия, однако все вызовы такой функции будут ссылаться на один и тот же фрагмент глобальных данных, поэтому не используйте его, если вам каждый раз нужен уникальный блок памяти.

Наконец, не используйте malloc в C++, если у вас нет действительно веской причины (например, realloc). Вместо этого используйте «новый» и соответствующий «удалить».

person jA_cOp    schedule 17.07.2010
comment
из gcc.gnu.org/onlinedocs/gcc/Variable-Length.html Автоматические массивы переменной длины разрешены в ISO C99, и в качестве расширения GCC принимает их в режиме C90 и в C++. Я думаю, именно поэтому он компилируется и работает у меня с помощью gcc. Я мог бы поверить, что это не C++ по стандарту или правильное определение языка, на который вы могли ссылаться. Должен ли я удалить тег C++? - person Gush; 17.07.2010
comment
Мне просто было интересно, что вы используете, но для меня, чтобы предположить С++, я подумал, что должен предупредить вас, что VLA не являются частью стандартного С++. - person jA_cOp; 17.07.2010