Я використовую наступний код у своїй програмі, і він працює чудово. Але мені цікаво, чи краще зробити це з малаком або залишити його таким, яким він є?
function (int len)
{
char result [len] = some chars;
send result over network
}
Я використовую наступний код у своїй програмі, і він працює чудово. Але мені цікаво, чи краще зробити це з малаком або залишити його таким, яким він є?
function (int len)
{
char result [len] = some chars;
send result over network
}
Відповіді:
Основна відмінність полягає в тому, що VLA (масиви змінної довжини) не забезпечують механізму виявлення відмов розподілу.
Якщо ви заявляєте
char result[len];
і lenперевищує кількість доступного простору стеку, поведінка вашої програми не визначена. Не існує мовного механізму ні заздалегідь визначити, чи вдасться виділити, ні визначити після того, чи вдалося це.
З іншого боку, якщо ви пишете:
char *result = malloc(len);
if (result == NULL) {
/* allocation failed, abort or take corrective action */
}
то ви можете виправити помилки витончено або принаймні гарантувати, що ваша програма не буде намагатися продовжувати виконувати після відмови.
(Ну, здебільшого. У системах Linux malloc()можна виділити шматок адресного простору, навіть якщо немає відповідного сховища; пізніші спроби використання цього простору можуть викликати вбивцю OOM . Але перевірка на malloc()помилку все ще є хорошою практикою.)
Інша проблема багатьох систем полягає в тому, що доступно більше місця (можливо, набагато більше місця), malloc()ніж для автоматичних об'єктів, таких як VLA.
І як вже було сказано у відповіді Філіпа, VLA були додані в C99 (Microsoft, зокрема, не підтримує їх).
А VLA в C11 стали необов’язковими. Напевно, більшість компіляторів C11 підтримуватимуть їх, але ви не можете розраховувати на це.
Автоматичні масиви змінної довжини були введені до C у C99.
Якщо ви не маєте сумнівів щодо зворотної порівнянності зі старими стандартами, це добре.
Загалом, якщо це працює, не чіпайте його. Не оптимізуйте заздалегідь. Не турбуйтеся про додавання спеціальних функцій чи розумних способів робити речі, оскільки ви часто не збираєтесь ними користуватися. Не ускладнювати.
Якщо ваш компілятор підтримує масиви змінної довжини, єдина небезпека - це переповнення стека в деяких системах, коли lenсмішно великий. Якщо ви точно знаєте, що lenне буде більше певної кількості, і ви знаєте, що ваш стек не переповнюється навіть на максимальній довжині, залиште код таким, який є; в іншому випадку перепишіть його за допомогою mallocта free.
char result [sizeof(char)]- це масив розмірів 1(тому що sizeof(char)дорівнює одиниці), тому призначення буде скорочуватися some chars.
str розпадається на покажчик , тож його sizeofбуде чотири або вісім, залежно від розміру вказівника у вашій системі.
char* result = alloca(len);, який виділяє стек. Він має той самий базовий ефект (і ті ж основні проблеми)
Мені подобається думка, що у вас може бути масив, виділений під час виконання, без фрагментації пам’яті, звисаючих покажчиків тощо. Однак інші вказали, що цей розподіл часу на виконання може мовчки вийти з ладу. Тому я спробував це, використовуючи gcc 4.5.3 в середовищі базування Cygwin:
#include <stdio.h>
#include <string.h>
void testit (unsigned long len)
{
char result [len*2];
char marker[100];
memset(marker, 0, sizeof(marker));
printf("result's size: %lu\n", sizeof(result));
strcpy(result, "this is a test that should overflow if no allocation");
printf("marker's contents: '%s'\n", marker);
}
int main(int argc, char *argv[])
{
testit(100);
testit((unsigned long)-1); // probably too big
}
Вихід був:
$ ./a.exe
result's size: 200
marker's contents: ''
result's size: 4294967294
marker's contents: 'should overflow if no allocation'
Занадто велика довжина, що пройшла у другому дзвінку, явно спричинила збій (перелив у маркер []). Це не означає, що цей вид перевірки є нерозумним (дурні можуть бути розумними!) Або що він відповідає стандартам C99, але це може допомогти, якщо ви турбуєтесь про це.
Як завжди, YMMV.
Взагалі кажучи, стек - це найпростіше і найкраще місце для розміщення ваших даних.
Я б уникнув проблем VLA, просто виділивши найбільший масив, який ви очікуєте.
Однак є випадки, коли купа найкраще і возитися з мальком варто докласти зусиль.
У вбудованому програмуванні ми завжди використовуємо статичний масив замість malloc, коли malloc та вільні операції є частими. Через відсутність управління пам'яттю у вбудованій системі, часті розподілення та вільні операції спричинять фрагмент пам'яті. Але ми повинні використовувати деякі складні методи, такі як визначення максимального розміру масиву та використання статичного локального масиву.
Якщо ваша програма працює в Linux або Windows, не важливо використовувати масив або malloc. Ключовий момент полягає в тому, де ви використовуєте структуру дати та логіку коду.
Що ще ніхто не згадував - це те, що варіант масиву змінної довжини, ймовірно, буде набагато швидшим, ніж malloc / free, оскільки виділення VLA - це лише випадок коригування покажчика стека (принаймні в GCC).
Отже, якщо ця функція викликається часто (що ви, звичайно, визначаєте, профілюючи), VLA - це хороший варіант оптимізації.
Це дуже поширене рішення C, яке я використовую для проблеми, яка може бути корисною. На відміну від VLA, він не стикається з будь-яким практичним ризиком переповнення стека в патологічних випадках.
/// Used for frequent allocations where the common case generally allocates
/// a small amount of memory, at which point a heap allocation can be
/// avoided, but rare cases also need to be handled which may allocate a
/// substantial amount. Note that this structure is not safe to copy as
/// it could potentially invalidate the 'data' pointer. Its primary use
/// is just to allow the stack to be used in common cases.
struct FastMem
{
/// Stores raw bytes for fast access.
char fast_mem[512];
/// Points to 'fast_mem' if the data fits. Otherwise, it will point to a
/// dynamically allocated memory address.
void* data;
};
/// @return A pointer to a newly allocated memory block of the specified size.
/// If the memory fits in the specified fast memory structure, it will use that
/// instead of the heap.
void* fm_malloc(struct FastMem* mem, int size)
{
// Utilize the stack if the memory fits, otherwise malloc.
mem->data = (size < sizeof mem->fast_mem) ? mem->fast_mem: malloc(size);
return mem->data;
}
/// Frees the specified memory block if it has been allocated on the heap.
void fm_free(struct FastMem* mem)
{
// Free the memory if it was allocated dynamically with 'malloc'.
if (mem->data != mem->fast_mem)
free(mem->data);
mem->data = 0;
}
Щоб використовувати його у вашому випадку:
struct FastMem fm;
// `result` will be allocated on the stack if 'len <= 512'.
char* result = fm_malloc(&fm, len);
// send result over network.
...
// this function will only do a heap deallocation if 'len > 512'.
fm_free(&fm, result);
Для цього у вищенаведеному випадку використовується стек, якщо рядок вписується в 512 байт або менше. В іншому випадку він використовує виділення купи. Це може бути корисно, якщо, скажімо, у 99% часу рядок вписується в 512 байт або менше. Однак, скажімо, є якийсь божевільний екзотичний випадок, з яким час від часу вам доведеться обробляти там, де рядок становить 32 кілобайти, коли користувач заснув на своїй клавіатурі чи щось подібне. Це дозволяє вирішити обидві ситуації без проблем.
Фактична версія Я використовую у виробництві також має свою власну версію reallocі callocтак далі, а також відповідну стандарту структури C ++ даних , побудованої на ту ж концепцію, але я витягнув необхідний мінімум , щоб проілюструвати цю концепцію.
У ньому є застереження, що копіювати небезпечно, і ви не повинні повертати покажчики, виділені через нього (вони можуть закінчитися недійсними, оскільки FastMemекземпляр знищений). Він призначений для використання у простих випадках у межах локальної функції, де вам сподобається завжди використовувати стек / VLA, інакше, коли якийсь рідкісний випадок може спричинити переповнення буфера / стека. Він не є загальним призначенням і не повинен використовуватися як такий.
Я насправді створив його століттями у відповідь на ситуацію зі спадковою базою кодів із використанням C89, що колишня команда думала, що ніколи не станеться, коли користувачеві вдалося назвати елемент з ім'ям, яке було понад 2047 символів (можливо, він заснув на своїй клавіатурі ). Мої колеги насправді намагалися збільшити розмір масивів, виділених у різних місцях, до 16,384 у відповідь, і тоді я подумав, що це стає смішним і просто обмінюється більшим ризиком переповнення стека в обмін на менший ризик переповнення буфера. Це дало рішення, яке було дуже легко підключити, щоб виправити ці випадки, просто додавши пару рядків коду. Це дозволило розповсюджувати звичайний випадок дуже ефективно та все ще використовувати стек без тих шалених рідкісних випадків, які вимагали, щоб купи вибивали програмне забезпечення. Однак я ' З того часу, навіть після C99, ми вважаємо його корисним, оскільки VLA все ще не можуть захистити нас від переповнення стека. Це може, але все ж об'єднує зі стека невеликі запити на розподіл.
Стік викликів завжди обмежений. Для основних операційних систем, таких як Linux або Windows, обмеження становить один або кілька мегабайт (і ви можете знайти способи його змінити). У деяких багатопотокових програм вона може бути нижчою (оскільки потоки можна створювати з меншим стеком). На вбудованих системах він може бути розміром у кілька кілобайт. Добре правило - уникати кадрів для викликів більше кількох кілобайт.
Тож використання VLA має сенс лише у тому випадку, якщо ви впевнені, що ваш lenдосить малий (максимум кілька десятків тисяч). Інакше у вас є переповнення стека, і це випадки невизначеної поведінки , дуже страшна ситуація.
Однак використання ручного C динамічного розподілу пам'яті (наприклад, callocабо malloc&free ) має і свої недоліки:
він може вийти з ладу, і ви завжди повинні перевіряти на несправність (наприклад, callocабо mallocповертатися NULL).
це повільніше: успішний розподіл VLA займає кілька наносекунд, для успішного mallocможе знадобитися кілька мікросекунд (у хороших випадках лише частка мікросекунди) або навіть більше (у патологічних випадках, пов’язаних із молотком , набагато більше).
набагато складніше кодувати: можна freeлише тоді, коли ти впевнений, що загострена зона більше не використовується. У вашому випадку можна було б назвати як callocі freeв одній і тій же процедурі.
Якщо ви знаєте, що більшість часу ваше result (дуже погане ім’я, ви ніколи не повинні повертати адресу автоматичної змінної VLA; тому я використовую, bufа не resultнижче), це невеликий ви, можливо, це може бути спеціально,
char tinybuf[256];
char *buf = (len<sizeof(tinybuf))?tinybuf:malloc(len);
if (!buf) { perror("malloc"); exit(EXIT_FAILURE); };
fill_buffer(buf, len);
send_buffer_on_network(buf, len);
if (buf != tinybuf)
free(buf);
Однак, наведений вище код менш читабельний і, ймовірно, передчасна оптимізація. Однак воно є більш надійним, ніж чисте рішення VLA.
PS. У деяких системах (наприклад, деякі дистрибутиви Linux увімкнено за замовчуванням) є перевиконання пам'яті (що дає mallocможливість задати деякий вказівник, навіть якщо не вистачає пам'яті). Ця функція, яка мені не подобається, і зазвичай відключаю її на моїх Linux-машинах.