Возвращает ли alloca() память при возникновении исключения?

Я поддерживаю устаревшее приложение C++, которое, похоже, имеет медленную утечку памяти. Мне удалось «исправить» утечку памяти, обеспечив, чтобы текущая конфигурация больше не вызывала никаких исключений, и я также могу вызвать утечку и масштабировать ее, настроив ее так, чтобы она приводила к множеству исключений.

Вся выделяемая память выполняется с помощью alloca(), а не malloc(). Объяснение, которое мне дали, заключается в том, что alloca() работает как сборщик мусора Java и автоматически освобождает память при выходе из контекста.

Из-за того, что утечка так явно связана с выбрасываемыми исключениями, у меня есть теория, что alloca() не может освободить память при выбрасывании исключений.

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

Я был бы признателен за понимание любых экспертов.


person kutuzof    schedule 14.12.2018    source источник
comment
Поскольку alloca() не является частью стандарта С++, я не думаю, что есть какие-либо гарантии. Если вы не используете какую-либо базовую библиотеку C, вам не следует использовать malloc() или alloca(), а использовать интеллектуальные указатели. Умные указатели обеспечат автоматическую сборку мусора объекта и будут безопасными для исключений.   -  person Martin York    schedule 14.12.2018
comment
Да, они также используются во многих местах. Но независимо от этого есть много alloca(). На данный момент я просто пытаюсь понять, чем можно объяснить утечки памяти. Как мне объяснили alloca(), так это то, что он должен автоматически освобождать всю свою память. Что, кажется, работает до тех пор, пока не возникает никаких исключений.   -  person kutuzof    schedule 14.12.2018
comment
Из документов на alloca(): The alloca() function is machine- and compiler-dependent. Так что я не думаю, что вы можете что-то гарантировать. Но в документах говорится, что пространство выделяется в кадре стека. Это означает, что когда функция возвращает значение, оно должно быть освобождено. НО деструктор не будет вызываться в этих ситуациях, поэтому, если объект использует внутреннее управление памятью, это пространство не будет правильно освобождено.   -  person Martin York    schedule 14.12.2018
comment
Вы пробовали valgrind? Кстати, alloca определенно не работает как сборщик мусора JVM. Прочтите также руководство по GC   -  person Basile Starynkevitch    schedule 14.12.2018
comment
@MartinYork, если бы объект использовал внутреннее управление памятью, это пространство не было бы правильно дераспределено Это могло происходить по крайней мере в нескольких случаях. Так что может быть проблема. Спасибо!   -  person kutuzof    schedule 14.12.2018
comment
@BasileStarynkevitch определенно не работает как сборщик мусора JVM. лол, да, я не могу себе представить, но именно так разработчики OG объясняют мне это.   -  person kutuzof    schedule 14.12.2018
comment
Что такое OG-разработчики?   -  person Basile Starynkevitch    schedule 14.12.2018
comment
@BasileStarynkevitch Оригинальные разработчики   -  person kutuzof    schedule 14.12.2018
comment
@BasileStarynkevitch dictionary.com/e/slang/og   -  person kutuzof    schedule 14.12.2018


Ответы (3)


Вполне вероятно, что проблема связана с уровнем косвенности.

Буквальный вопрос: «Возвращает ли alloca память, если выдается исключение?». И ответ на это; Он возвращает только память, которая была выделена напрямую. Никакие деструкторы не запускаются, и происходит утечка любого указателя-владельца внутри alloca-выделенной памяти.

person MSalters    schedule 14.12.2018
comment
Очень вероятно, что это могло произойти. - person kutuzof; 14.12.2018

В C++ вы не должны использовать подпрограммы управления памятью C. В современном C++ интеллектуальные указатели предоставляют вам мелкозернистую детерминированную сборку мусора.

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

НО Это также означает, что любые соответствующие деструкторы объекта никогда не будут вызываться. Это означает, что если объект самостоятельно управляет памятью, она не будет очищена (поскольку деструктор не запускается для ее очистки).

Хотя alloca() наверное очень быстрый. Я думаю, что дополнительная нагрузка, которую он добавляет для управления памятью, (в общем случае) того не стоит; хотя, если у вас есть особая потребность в дополнительной скорости, это может стоить того.

Код выглядит следующим образом:

void func()
{
    MyType* x = (MyType*)alloca(sizeof(MyType));

    passXtoCFunctionThatDoesNotTakeOwnership(x);
}

Должно быть написано так:

void func()
{
    std::unique_ptr<MyType> x = std::make_unique<MyType>();

    passXtoCFunctionThatDoesNotTakeOwnership(x.get());
}

Если вы используете его для хранения массива объектов.

void func()
{
    MyType* x = (MyType*)alloca(sizeof(MyType) * arraySize);

    // STUFF
    x[0].stuff();
}

Тогда лучше использовать std::vector

void func()
{
    std::vector<MyType> x;
    x.reserve(arraySize);   // or resize() if that is appropriate

    // STUFF
    x[0].stuff();
}

Если вы используете его для простых объектов. Тогда вам, вероятно, следует просто объявлять автоматические переменные:

void func()
{
    MyType* x = (MyType*)alloca(sizeof(MyType));

    x->myData = 5;
}

Вы должны просто объявить переменную:

void func()
{
    MyType x;

    x.myData = 5;
}
person Martin York    schedule 14.12.2018
comment
Если бы я мог снова написать все это с нуля, я бы определенно сделал это именно так. Спасибо! - person kutuzof; 14.12.2018
comment
@kutuzof: Даже если не переписывать с нуля, это все же более простой вариант по сравнению с исправлением всех этих alloca проблем. - person MSalters; 14.12.2018
comment
лол, я просто хочу выбросить все это дело на помойку и переписать на java. Я думаю, что эти первоначальные затраты в конечном итоге избавят нас от постоянного обслуживания. - person kutuzof; 14.12.2018
comment
Я совсем не уверен, что переписывание на Java улучшит удобство сопровождения и/или производительность. (Java — это устаревший язык, вы также можете рассмотреть Go, Ocaml, Rust и даже Common Lisp с SBCL...) Но это действительно зависит от того, что на самом деле делает ваше приложение - person Basile Starynkevitch; 14.12.2018

Из Linux man

....

Поскольку пространство, выделенное alloca(), выделяется внутри кадра стека, это пространство автоматически освобождается, если функция return перепрыгивает вызовом longjmp(3) или siglongjmp(3).

....

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

.....

Ошибки Если кадр стека не может быть расширен, отсутствует индикация ошибки. (Однако после неудачного выделения программа, скорее всего, получит сигнал SIGSEGV, если попытается получить доступ к нераспределенному пространству.) Во многих системах alloca() нельзя использовать внутри списка аргументов вызов функции, потому что пространство стека, зарезервированное alloca(), появится в стеке в середине пространства для аргументов функции.

Таким образом, исключение не может вызвать утечку памяти для памяти, выделенной alloca в кадре стека. Однако это, как и любое исключение, может привести к утечке памяти в куче, поскольку деструкторы и методы освобождения памяти, размещенные после alloca, будут пропущены.

person Dmytro Dadyka    schedule 14.12.2018
comment
Хорошо, да, это звучит очень правдоподобно. Это программное обеспечение также работает в Linux, так что это кажется логичным. Большое спасибо! - person kutuzof; 14.12.2018
comment
На самом деле это не очень хороший ответ, и, вероятно, его не следует принимать. Как отмечают комментарии, проблема здесь в том, что alloca является функциональной формой в некоторых библиотеках времени выполнения C. Таким образом, справочная страница написана с учетом C. Вот почему он говорит о longjmp и игнорирует throw. Хороший ответ должен касаться этого взаимодействия. - person MSalters; 14.12.2018
comment
@MSalters хорошо, хороший момент. На самом деле я бы описал это программное обеспечение как смесь C и C++. Первоначально это было приложение C, которое со временем превратилось в C++. Это имеет больше смысла? - person kutuzof; 14.12.2018
comment
@kutuzof: я видел больше, чем несколько из них. Вероятно, это непреднамеренный побочный эффект того, что C++ очень совместим с C, но не идеально. Добавьте нестандартные функции, такие как alloca, и вы получите инженерные проблемы. Управляемый, но не простой. - person MSalters; 14.12.2018
comment
@MSalters, ты прав. Я действительно скучал по разговорам о вызовах деструкторов. Думаю автор поторопился с выбором ответа. - person Dmytro Dadyka; 14.12.2018
comment
@MSalters у вас возникают инженерные проблемы. Управляемый, но не простой. лол, расскажи мне об этом. Большое спасибо за совет! - person kutuzof; 14.12.2018
comment
@DmytroDadyka Спасибо и вам! - person kutuzof; 14.12.2018