Функция C: это динамическое распределение? инициализация массива с изменяющейся длиной

Предположим, у меня есть функция C:

void myFunction(..., int nObs){
    int myVec[nObs] ;
    ...
}

Распределяется ли myVec динамически? nObs не является константой всякий раз, когда вызывается myFunction. Я спрашиваю, потому что в настоящее время я программирую с этой привычкой, а у моего друга были ошибки в его программе, где виновником было то, что он динамически не выделял свои массивы. Я хочу знать, является ли моя привычка программирования (инициализация, как в приведенном выше примере) безопасной привычкой.

Спасибо.


person Vinh Nguyen    schedule 23.02.2010    source источник
comment
То, что вы делаете выше, на самом деле недействительно C; он не должен компилироваться, поскольку C не допускает массивы, определенные во время выполнения. Вы уверены, что не используете C# или что-то еще?   -  person Ben Zotto    schedule 23.02.2010
comment
C99 имеет массивы переменной длины.   -  person Alok Singhal    schedule 23.02.2010
comment
Ни за что! Удивительно. Я устарел на 11 лет. Некоторые предупреждения об этом шаблоне см. здесь: clarkcox. com/blog/2009/04/07/c99s-vlas-are-evil   -  person Ben Zotto    schedule 23.02.2010
comment
Бен: да. VLA хороши, если вам нужен небольшой объем временной памяти в некритической программе, иначе от них мало толку. Я не использую их.   -  person Alok Singhal    schedule 23.02.2010
comment
@ Бен Интересный пост. Я не уверен, какой вывод из этого и комментариев, хотя. Переполнение стека при выделении VLA не определено так же, как переполнение стека в целом не определено, и некоторые платформы реализуют проверку безопасности при одновременном выделении в стеке более одной страницы. Таким образом, все сводится к тому, чтобы не выделять слишком много в стеке, будь то рекурсия, массивы постоянного размера или массивы динамического размера.   -  person Pascal Cuoq    schedule 23.02.2010
comment
@Ben: эта критика в равной степени относится к массивам постоянного размера или любой другой переменной в стеке. Компилятор не имеет возможности узнать во время компиляции, будет ли выделение пространства для какой-либо локальной переменной переполнять стек.   -  person Stephen Canon    schedule 24.02.2010


Ответы (3)


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

person Pascal Cuoq    schedule 23.02.2010
comment
alloca() не может выполнять математику строк-столбцов за вас, как двумерный массив. - person Arthur Kalliokoski; 23.02.2010

Это зависит от компилятора. Я знаю, что это нормально с gcc, но я не думаю, что спецификация C89 позволяет это. Я не уверен в более новых спецификациях C, таких как C99. Лучше всего для переносимости не использовать его.

person Eric Warmenhoven    schedule 23.02.2010
comment
Массивы переменной длины являются частью C99 (publib.boulder.ibm.com/infocenter/comphelp/v8v101/) - person R Samuel Klatchko; 23.02.2010
comment
MSVC не знает о C99. Они сосредоточены на материалах C++. - person Arthur Kalliokoski; 23.02.2010
comment
MSVC — это не компилятор C, это компилятор языка, который очень похож на C, каким он был 21 год назад. - person Stephen Canon; 24.02.2010

Он известен как "массив переменной длины". Она динамическая в том смысле, что ее размер определяется во время выполнения и может изменяться от вызова к вызову, но у нее есть класс хранения auto, как и у любой другой локальной переменной. Я бы не стал использовать для этого термин «динамическое распределение», так как это только запутает.

Термин «динамическое выделение» обычно используется для памяти и объектов, выделенных из кучи, время жизни которых определяется программистом (new/delete, malloc/free), а не область действия объекта. Массивы переменной длины выделяются и уничтожаются автоматически, когда они входят в область действия и выходят из нее, как и любая другая локальная переменная с классом хранения auto.

Массивы переменной длины не всегда поддерживаются компиляторами; в частности, VC++ не поддерживает C99 (и, следовательно, массивы переменной длины), и делать это не планируется. В настоящее время C++ их не поддерживает.

Что касается «безопасной привычки», помимо проблемы переносимости, существует очевидная возможность переполнения стека, если nObs будет достаточно большим значением. Вы могли бы в некоторой степени защититься от этого, сделав nObs меньшим целочисленным типом, например, uint8_t или uint16_t, но это не очень гибкое решение и делает смелые предположения о размере стека и выделенных объектах. assert(nObs < MAX_OBS) может быть рекомендовано, но в этот момент стек уже может быть переполнен (хотя это может быть нормально, поскольку assert() в любом случае вызывает завершение).

[править] Использование массивов переменной длины, вероятно, допустимо, если размер не определяется извне, как в вашем примере. [/редактировать]

В целом, проблемы переносимости и безопасности стека предполагают, что лучше избегать массивов переменной длины IMO.

person Clifford    schedule 23.02.2010
comment
Вы можете переполнить стек массивами постоянного размера или даже обычными локальными переменными. Если вы не готовы решать подобные проблемы, вам не следует использовать C. - person Stephen Canon; 24.02.2010
comment
@Stephen Canon: разница в том, что они постоянны и, следовательно, находятся под «локальным управлением» - автор функции применяет ограничение. Автор функции может не совпадать с ее пользователем (например, это может быть сторонняя библиотека, унаследованный код или групповая разработка), и он может не знать, как распределяется память и ограничения, которые накладывает. Кроме того, в худшем случае размер может определяться вводом конечного пользователя, что, если не будет должным образом ограничено, может вызвать проблемы. Я хочу сказать, что у него есть дополнительные опасности, не представленные массивом постоянной длины. - person Clifford; 24.02.2010
comment
@Clifford: я согласен, что есть дополнительные риски. Однако сам язык C представляет собой дополнительную опасность; Вы заключаете, что C лучше избегать или что его следует использовать в соответствующих обстоятельствах программистами, которые знают, что они делают? - person Stephen Canon; 24.02.2010
comment
Я изменил свои коды, чтобы использовать calloc/malloc с бесплатными. Что делать, если есть переполнение HEAP? После этого обсуждения я чувствую, что мне не следует даже использовать объявление переменных в своих функциях, но я должен динамически выделять, даже для переменных длины 1! знак равно - person Vinh Nguyen; 24.02.2010
comment
@Vinh: переполнения кучи не существует, хотя она может просто не выделиться. Если malloc() не работает, он возвращает NULL. Если new терпит неудачу, он генерирует исключение, если вы не используете new(nothrow), когда он возвращает ноль. В большинстве систем распределение кучи в конечном счете является обязанностью ОС, и поведение при сбое может быть другим, если вместо него этим занимается ОС. В современной настольной ОС у вас есть несколько ГБ виртуальной памяти независимо от доступной физической памяти, поэтому в большинстве случаев это не проблема. И наоборот, стек в типичном потоке Windows, например, составляет пару МБ. - person Clifford; 24.02.2010
comment
Попался! Итак, рекомендуемый способ сделать это всегда проверять, является ли указатель, выделенный из calloc/malloc, NULL, а затем писать для него какой-то обработчик? Хотя это кажется НАМНОГО утомительным. Кроме того, вы бы порекомендовали всегда динамически выделять через calloc/malloc, чем объявлять переменные, такие как int x; и двойной у; в функциях? - person Vinh Nguyen; 24.02.2010
comment
@Vinh: я ничего не рекомендую, просто описываю поведение; твой выбор. В современной настольной системе шансы на сбой невелики из-за огромного объема доступной памяти. Я полагаю, что в Linux malloc() никогда не возвращает NULL, ОС выдает ошибку нехватки памяти и завершает процесс. Конечно, не используйте динамическое выделение памяти для небольших объектов известного размера. Я бы не рекомендовал вообще использовать malloc() вместо C++ и оператора new. - person Clifford; 25.02.2010