Если мне нужен глобальный VLA, могу ли я использовать alloca() в основной функции?

У меня есть функция main для моего приложения, и я выделяю, например, пути к файлам конфигурации и т. д. В настоящее время я использую для них malloc, но они никогда не освобождаются и всегда доступны для использования в течение всего времени существования приложения. Я даже никогда не освобождаю их, потому что ОС уже автоматически освобождает выделенную память, когда приложение завершает работу. На данный момент есть ли какая-либо причина не использовать alloca вместо malloc, потому что программа завершается, когда main возвращается, а alloca память удаляется только после освобождения функции, в которой она была выделена. Таким образом, исходя из этой логики, память, выделенная в основной функции с помощью alloca, освобождается только после завершения программы, что желательно. Верны ли эти утверждения и есть ли причина не использовать alloca (alloca - плохая практика, поэтому, когда я сказал, что alloca означал alloca или создать VLA в main) в main для объекта, подобного "глобальному VLA", который существует до завершения программы?


person user16217248    schedule 28.06.2021    source источник
comment
alloca нестандартен. и alloca освобождается, когда текущий кадр стека умирает, т. е. когда возвращается функция, вызвавшая alloca. Вы всегда должны free свою malloc память, кстати.   -  person Raildex    schedule 28.06.2021
comment
@Raildex Но что, если эта функция main?   -  person user16217248    schedule 28.06.2021
comment
Стеки относительно небольшие. Сам факт того, что это main, делает еще хуже использование alloca, поскольку он будет постоянно использовать это драгоценное пространство стека.   -  person kaylum    schedule 28.06.2021


Ответы (4)


Вы можете использовать alloca/VLA в main, но зачем?

Типичная причина их использования - если у вас есть некоторая чувствительная к производительности часть, которая вызывается много, и вы не хотите накладных расходов malloc/free. Для main ваши данные выделяются один раз в начале программы, поэтому накладные расходы на несколько вызовов malloc незначительны.

Еще одна причина не использовать alloca/VLA в основном состоит в том, что они занимают пространство стека, которое является очень ограниченным ресурсом по сравнению с пространством кучи.

person janneb    schedule 28.06.2021
comment
Итак, есть стек, куча, а затем глобальные и статические переменные? - person user16217248; 28.06.2021
comment
@ user16217248 Да, для данных. Для кода память выделяется отдельно и не относится к этим разделам (как правило, путем сопоставления памяти с исполняемым файлом). - person janneb; 28.06.2021
comment
global и static обычно находятся в одних и тех же разделах. - person 0___________; 28.06.2021

Зависит от того, сколько памяти вам нужно. Если он достаточно мал (скажем, несколько сотен байт или около того), вы можете безопасно выполнять alloca в main() или использовать VLA.

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

Если размеры могут быть сколь угодно большими, то лучше всего продолжать использовать malloc(), как вы уже это делали. Кстати, даже если вы вызываете malloc() в main() и используете его на протяжении всего времени существования программы, по-прежнему считается хорошей практикой освобождать его перед выходом.

person th33lf    schedule 28.06.2021
comment
Итак, я должен сделать обработчик atexit и поместить в него free(ThePtr)? - person user16217248; 28.06.2021
comment
@user16217248 user16217248 Обычно рекомендуется, чтобы тот же модуль, который выделил память, очищался после себя. Итак, если вы выделили память в какой-то функции foo_init(), то должна быть и соответствующая функция foo_cleanup(). Вы можете использовать atexit, но обычно в этом нет необходимости. Просто вызовите функцию очистки вручную. - person Lundin; 28.06.2021
comment
@Lundin Однако в моем случае это невозможно из-за характера моей программы. - person user16217248; 28.06.2021
comment
@user16217248 user16217248 Звучит как явное указание на проблемный дизайн программы. - person Lundin; 28.06.2021
comment
@Lundin NSApplicationMain не возвращается, и это то, что использует мое приложение, см. это - person user16217248; 01.07.2021

Технически нет, потому что любая переменная, объявленная в функции, не будет глобальной. Но вы можете сделать что-то вроде этого:

char *buffer;

int main(void) {
    char buf[size];
    buffer = buf;

Это даст вам интерфейс для глобального доступа к буферу.

На данный момент есть ли причина не использовать alloca вместо malloc?

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

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

int main(void) {
    char *buf = malloc(size);
    // Do work
    free(buf);
}

Я написал длинный ответ о alloca и VLA:s, который может быть вам полезен. Действительно ли мне нужен malloc?

person klutt    schedule 28.06.2021

VLA (как определено стандартом) и нестандартный alloca предназначены для использования для выделения временных небольших массивов в локальной области. Ничего больше.

Размещение больших объектов в стеке — известный источник незаметных и серьезных ошибок переполнения стека. По этой причине вам следует избегать больших объектов VLA и alloca. Всякий раз, когда вам нужны большие объекты в области файла, они должны быть либо массивами static, либо динамически распределяться с помощью malloc.

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

Что касается общей путаницы между стеком и кучей, см. раздел Что выделяется в стеке и в куче?

Вы даже не можете поместить стандартный VLA в область файла, потому что размер массива должен быть целочисленным константным выражением. Плюс стандарт (C17 6.7.6) прямо говорит, что вам не разрешено:

Если идентификатор объявлен как объект со статической или потоковой продолжительностью хранения, он не должен иметь тип массива переменной длины.

Что касается alloca, то это не стандартный C и по этой причине плохой. Но это также плохо, потому что у него нет безопасности типов, поэтому VLA предпочтительнее alloca - он безопаснее и переносимее.

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


Я даже никогда не освобождаю их, потому что ОС уже автоматически освобождает выделенную память, когда приложение завершает работу.

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

(Если вас беспокоит производительность free(), вы можете исключить вызовы free() из сборки релиза и использовать их только в сборке отладки. Хотя производительность редко является проблемой при закрытии программы — обычно вы можете просто закрыть отключите графический интерфейс, если таковой имеется, а затем дайте программе проглотить очищающий код в фоновом режиме.)

person Lundin    schedule 28.06.2021