Является ли проблема утечки памяти "undefined поведение" в С++?

Оказывается, многие невинно выглядящие вещи - это поведение undefined в С++. Например, как только ненулевой указатель был delete 'd даже распечатывал это значение указателя undefined поведение.

Теперь утечки памяти, безусловно, плохие. Но какова их классовая ситуация, undefined или какой другой класс поведения?

Ответ 1

Утечки памяти.

Нет поведения undefined. Совершенно законно утечка памяти.

Undefined поведение: это действия, которые стандарт конкретно не хочет определять и уходит до реализации, так что он гибкий для выполнения определенных типов оптимизации без нарушения стандарта.

Управление памятью четко определено.
Если вы динамически выделяете память и не выпускаете ее. Тогда память остается собственностью приложения для управления по своему усмотрению. Тот факт, что вы потеряли все ссылки на эту часть памяти, не существует ни там, ни там.

Конечно, если вы продолжите утечку, у вас в конечном итоге закончится доступная память, и приложение начнет выдавать исключения bad_alloc. Но это еще одна проблема.

Ответ 2

Утечки памяти определенно определены в C/С++.

Если я это сделаю:

int *a = new int[10];

за которым следует

a = new int[10]; 

Я определенно теряю память, так как нет доступа к 1-му выделенному массиву, и эта память не будет автоматически освобождена, поскольку GC не поддерживается.

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

Ответ 3

При утечке памяти выполнение выполняется так, как будто ничего не происходит. Это определяется поведением.

Вниз по дорожке вы можете обнаружить, что вызов malloc завершился неудачей из-за нехватки доступной памяти. Но это определенное поведение malloc, и последствия также хорошо определены: вызов malloc возвращает NULL.

Теперь это может привести к сбою программы, которая не проверяет результат malloc с нарушением сегментации. Но это поведение undefined (из POV спецификаций языка) из-за того, что программа разыменовывает неверный указатель, а не более раннюю утечку памяти или неудачный вызов malloc.

Ответ 4

Моя интерпретация этого утверждения:

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

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

Если вы каким-то образом освободите хранилище, которое объект занимает без, вызывающего деструктор на объекте, который занял память, UB является последствием, если деструктор нетривиальна и имеет стороннее значение, эффекты.

Если new выделяется с помощью malloc, необработанное хранилище может быть выпущено с помощью free(), деструктор не будет работать, и UB будет работать. Или, если указатель отбрасывается на несвязанный тип и удаляется, память освобождается, но выполняется неправильный деструктор, UB.

Это не то же самое, что пропущенный delete, где базовая память не освобождается. Опускание delete - это не UB.

Ответ 5

(Комментарий ниже "Heads-up: этот ответ был перенесен сюда из Повреждение памяти вызывает поведение undefined?" - вам, вероятно, придется прочитайте этот вопрос, чтобы получить правильный фон для этого ответа O_o).

Мне кажется, что эта часть Стандартного явно позволяет:

  • имеющий собственный пул памяти, в который вы помещаете объекты размещения- new, затем выпустите/повторно используйте все это, не тратя время на вызов своих деструкторов, , если вы не зависите от побочных эффектов деструкторов объектов.

  • библиотеки, которые выделяют немного памяти и никогда не выпускают ее, вероятно, потому, что их функции/объекты могут использоваться деструкторами статических объектов и зарегистрированных обработчиков на выходе, и это не стоит покупать во всем упорядоченном порядке - разрушение или переходный "феникс" - как возрождение каждый раз, когда эти обращения происходят.

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

Мы можем по-прежнему считать , что стандарт говорит, что это поведение undefined. Важнейшая часть:

"зависит от побочных эффектов, создаваемых деструктором, имеет поведение undefined.

Стандарт §1.9/12 явно определяет побочные эффекты следующим образом (курсивом ниже являются Стандарты, указывающие на введение формального определения):

Доступ к объекту, обозначенному значением volatile glvalue (3.10), модификацией объекта, вызовом функции ввода-вывода библиотеки или вызовом функции, выполняющей любые из этих операций, являются все побочные эффекты, которые являются изменениями состояния среды выполнения.

В вашей программе нет зависимости, поэтому поведение undefined.

Один пример зависимости, подходящий для сценария в п. 3.8 p4, где необходимость или причина поведения undefined не очевидна, такова:

struct X
{
    ~X() { std::cout << "bye!\n"; }
};

int main()
{
     new X();
}

Дискуссионный вопрос о проблемах заключается в том, будет ли рассмотрен выше X объект выше released для целей 3.8 p4, поскольку он, вероятно, только выпущен на O.S. после завершения программы - из чтения Стандарта не ясно, является ли эта стадия процесса "пожизненным" в рамках Стандартных поведенческих требований (мой быстрый поиск Стандарта не уточнил это). Я лично рискнул бы, что 3.8p4 применяется здесь, отчасти потому, что до тех пор, пока он достаточно неоднозначен, чтобы утверждать, что автор сценария может иметь право разрешать поведение undefined в этом сценарии, но даже если вышеуказанный код не является выпуском, сценарий легко изменен ala...

int main()
{
     X* p = new X();
     *(char*)p = 'x';   // token memory reuse...
}

В любом случае, однако основной реализованный деструктор выше имеет побочный эффект - за "вызов функции ввода-вывода библиотеки"; Кроме того, наблюдаемое поведение программы, возможно, "зависит" от нее в том смысле, что буферы, на которые повлиял бы деструктор, были запущены при кратковременном запуске. Но "зависит от побочных эффектов" означает только намеки на ситуации, когда программа явно имела бы поведение undefined, если деструктор не работал? Я ошибался бы на стороне первого, особенно, поскольку последний случай не нуждался бы в специальном параграфе в Стандарте, чтобы зафиксировать, что поведение undefined. Вот пример с явно-undefined поведением:

int* p_;

struct X
{
    ~X() { if (b_) p_ = 0; else delete p_; }
    bool b_;
};

X x{true};

int main()
{
     p_ = new int();
     delete p_; // p_ now holds freed pointer
     new (&x){false};  // reuse x without calling destructor
}

Когда X деструктор вызывается во время завершения, b_ будет false, а ~X() будет поэтому delete p_ для уже освобожденного указателя, создавая поведение undefined. Если перед повторным использованием был вызван x.~X();, p_ был бы установлен в 0, и удаление было бы безопасным. В этом смысле правильное поведение программы можно сказать, что она зависит от деструктора, а поведение явно undefined, но мы только что создали программу, которая сама по себе соответствует описанному поведению 3.8p4, вместо того, чтобы иметь поведение следствие 3.8p4...?

Более сложные сценарии с проблемами - слишком долго для предоставления кода для - могут включать, например, странную С++-библиотеку со ссылочными счетчиками внутри объектов файлового потока, которые должны были ударить 0, чтобы вызвать некоторую обработку, такую ​​как промывка ввода-вывода или объединение фоновых потоков и т.д. - где отказ от выполнения этих действий рисковал не только неспособностью выполнить вывод, явно запрошенный деструктор, но также не может выводить другой буферизованный вывод из потока или на какую-либо ОС с транзакционной файловой системой может привести к откату предыдущих операций ввода-вывода - такие проблемы могут изменить поведение наблюдаемой программы или даже оставить зависание программы.

Примечание: нет необходимости доказывать, что существует какой-либо реальный код, который ведет себя странно в любом существующем компиляторе/системе; Стандарт явно оставляет за собой право для компиляторов иметь поведение undefined... что все это имеет значение. Это не то, о чем вы можете рассуждать и не хотите игнорировать Стандарт - возможно, что С++ 14 или какая-то другая ревизия изменяет это условие, но до тех пор, пока оно там, если есть даже определенная "зависимость" от побочных эффектов, тогда существует потенциал для поведения undefined (который, конечно, сам по себе может быть определен конкретным компилятором/реализацией, поэтому он автоматически не означает, что каждый компилятор обязан делать что-то странное).

Ответ 6

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

Теперь мы обычно рассматриваем, что динамически выделенный объект становится "утечкой памяти" в момент выполнения программы, когда все ссылки (общие "ссылки", такие как указатели) на этот объект, теряются до такой степени, что они невосстанавливаются. Обратите внимание, что даже для человека понятие "все ссылки теряются" не очень точно определено. Что делать, если у нас есть ссылка на какую-то часть объекта, которая теоретически может быть пересчитана на ссылку на весь объект? Это утечка памяти или нет? Что делать, если у нас нет ссылок на объект вообще, но каким-то образом мы можем вычислить такую ​​ссылку, используя некоторую другую информацию, доступную программе (например, точную последовательность распределений)?

Спецификация языка не касается таких проблем. Что бы вы ни считали появление "утечки памяти" в своей программе, с точки зрения языка это вообще не событие. С точки зрения языка "просочившийся" динамически выделенный объект продолжает жить счастливо, пока программа не закончится. Это единственная оставшаяся проблема: что происходит, когда программа заканчивается и какая-то динамическая память все еще выделена?

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

Ответ 7

Бремя доказывания лежит на тех, кто думает, что утечка памяти может быть С++ UB.

Естественно, никаких доказательств не было представлено.

Короче говоря, кто-либо укрывает какие-либо сомнения, этот вопрос никогда не может быть четко разрешен, за исключением того, что он очень правдоподобно угрожает комитету, например. громкую музыку Джастина Бибера, так что они добавляют оператор С++ 14, который разъясняет, что это не UB.


При выпуске С++ 11 §3.8/4:

" Для объекта типа класса с нетривиальным деструктором программа не требует прямого вызова деструктора до того, как хранилище, которое объект занимает, повторно используется или освобождается, однако, если нет явного вызова деструктора или если выражение удаления (5.3.5) не используется для освобождения хранилища, деструктор не должен быть неявно вызван, и любая программа, зависящая от побочных эффектов, создаваемых деструктором, имеет undefined.

Этот отрывок имел ту же формулировку в С++ 98 и С++ 03. Что это значит?

  • программа не требует прямого вызова деструктора до того, как хранилище, которое объект занимает, будет повторно использовано или выпущено
     
    – означает, что можно захватить память переменной и повторно использовать эту память без первоначального уничтожения существующего объекта.

  • если нет явного вызова деструктора или если выражение удаления (5.3.5) не используется для освобождения хранилища, деструктор не должен быть неявным образом вызван
     
    – означает, что если вы не уничтожаете существующий объект перед повторным использованием памяти, тогда, если объект таков, что его деструктор автоматически вызывается (например, локальная автоматическая переменная), то программа имеет undefined Behavior, потому что этот деструктор будет работать на уже не существующий объект.

  • и любая программа, которая зависит от побочных эффектов, создаваемых деструктором, имеет поведение undefined  
    – не может означать буквально то, что он говорит, потому что программа всегда зависит от каких-либо побочных эффектов, по определению побочного эффекта. Или, другими словами, нет возможности для программы не зависеть от побочных эффектов, потому что тогда они не будут побочными эффектами.

Скорее всего, то, что было предназначено, не было тем, что окончательно пробилось на С++ 98, так что то, что у нас есть, является дефектом.

Из контекста можно предположить, что если программа использует автоматическое уничтожение объекта статически известного типа T, где память была повторно использована для создания объекта или объектов, которые не являются объектами T затем undefined Поведение.


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

Таким образом, это формально UB:

auto main() -> int
{
    string s( 666, '#' );
    new( &s ) string( 42, '-' );    //  <- Storage reuse.
    cout << s << endl;
    //  <- Formal UB, because original destructor implicitly invoked.
}

Но это в порядке с буквальной интерпретацией:

auto main() -> int
{
    string s( 666, '#' );
    s.~string();
    new( &s ) string( 42, '-' );    //  <- Storage reuse.
    cout << s << endl;
    //  OK, because of the explicit destruction of the original object.
}

Основная проблема заключается в том, что при буквальной интерпретации стандартного параграфа выше все равно будет формально ОК, если новое место размещения создало там объект другого типа, только из-за явного уничтожения оригинала. Но в этом случае это было бы не очень практично. Возможно, это подпадает под какой-то другой параграф в стандарте, так что он также формально является UB.

И это тоже нормально, используя размещение new от <new>:

auto main() -> int
{
    char* storage   = new char[sizeof( string )];
    new( storage ) string( 666, '#' );
    string const& s = *(
        new( storage ) string( 42, '-' )    //  <- Storage reuse.
        );
    cout << s << endl;
    //  OK, because no implicit call of original object destructor.
}

Как я вижу, это & ​​ndash; Теперь.

Ответ 8

Определенное поведение.

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

Ответ 9

Добавление ко всем остальным ответам, совсем другой подход. Рассматривая распределение памяти в § 5.3.4-18, мы можем видеть:

Если какая-либо часть инициализации объекта, описанная выше, 76 завершается путем исключения исключения, и подходящая функция освобождения может быть найдено, функция освобождения называется освобождением памяти, в которой объект строился, после чего исключение продолжается для распространения в контексте нового выражения. Если нет однозначных может быть найдена соответствующая функция освобождения, распространяющая исключение не освобождает память объектов. [Примечание: это когда вызываемая функция распределения не выделяет Память; в противном случае это может привести к утечке памяти. -end note ]

Было ли это причиной UB здесь, это было бы упомянуто, так что это "просто утечка памяти".

В таких местах, как §20.6.4-10, упоминается возможный сборщик мусора и детектор утечки. Многие мысли были включены в концепцию безопасно полученных указателей и др. чтобы иметь возможность использовать С++ с сборщиком мусора (C.2.10 "Минимальная поддержка для сборщиков мусора" ).

Таким образом, если бы UB просто потерял последний указатель на какой-либо объект, все усилия не имели бы никакого смысла.

Что касается "когда деструктор имеет побочные эффекты, которые не запускают его когда-либо UB", я бы сказал, что это неверно, в противном случае объекты, такие как std::quick_exit(), были бы по своей сути UB тоже.

Ответ 10

Если космический челнок должен взлететь через две минуты, и у меня есть выбор между тем, чтобы поставить его с кодом, который теряет память и код с поведением undefined, я вставляю код, который утечки памяти.

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

Возможно, поведение undefined, но в действительности и то и другое.

Ответ 11

определяется, поскольку утечка памяти - это то, что вы забываете очистить после себя.

конечно, утечка памяти, вероятно, может привести к поведению undefined позже.

Ответ 12

Прямой ответ: стандарт не определяет, что происходит при утечке памяти, поэтому это "undefined". Это неявно undefined, хотя, что менее интересно, чем явно undefined вещи в стандарте.

Ответ 13

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

При этом, на мой взгляд, стандарт противоречит самому себе в этом отрывке. С одной стороны, это гарантирует, что деструктор не будет вызываться в этом сценарии, а с другой стороны, он говорит, что если программа зависит от побочных эффектов, создаваемых деструктором, то она имеет поведение undefined. Предположим, что деструктор вызывает exit, тогда никакая программа, которая ничего не делает, может притворяться независимой от этого, потому что побочный эффект вызова деструктора помешает ему делать то, что он в противном случае сделал бы; но текст также гарантирует, что деструктор не будет вызван так, чтобы программа продолжала делать свои вещи безмятежно. Я думаю, что единственный разумный способ прочитать конец этого отрывка состоит в том, что если правильное поведение программы потребует вызова деструктора, то поведение на самом деле не определено; это лишний раз, учитывая, что только что было указано, что деструктор не будет называться.

Ответ 14

Undefined поведение означает, что что-то не было определено или неизвестно. Поведение утечек памяти определенно известно в C/С++, чтобы есть в доступной памяти. Однако возникающие проблемы не всегда могут быть определены и варьироваться в соответствии с описанием игры.