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
Бен: да. VLAs са добре, ако искате малко количество временна памет в некритична програма, в противен случай те нямат голяма полза. Не ги ползвам.   -  person Alok Singhal    schedule 23.02.2010
comment
@Ben Интересна публикация. Не съм сигурен какво да заключа от това и от коментарите. Препълването на стека чрез разпределение на 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
@Клифорд: Съгласен съм, че има допълнителни рискове. Самият език 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), когато върне нула. В повечето системи разпределението на стека в крайна сметка е отговорност на ОС и поведението при повреда може да е различно, ако ОС го обработва вместо това. В модерна настолна операционна система имате определен брой Gb виртуална памет, независимо от наличната физическа памет, така че в повечето случаи това не е проблем. Обратно, стекът в типична нишка за Windows например е няколко Mb. - 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