内存泄漏是“未定义的行为”吗? C++ 中的类问题?

发布于 2024-08-16 05:06:46 字数 253 浏览 4 评论 0原文

事实证明,许多看似无辜的事情在 C++ 中都是未定义的行为。例如,一旦非空指针被删除即使打印出该指针值也是未定义的行为

现在内存泄漏肯定是很糟糕的。但它们是什么类别的情况——定义的、未定义的还是其他什么类别的行为?

Turns out many innocently looking things are undefined behavior in C++. For example, once a non-null pointer has been delete'd even printing out that pointer value is undefined behavior.

Now memory leaks are definitely bad. But what class situation are they - defined, undefined or what other class of behavior?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(14

荒路情人 2024-08-23 05:06:46

内存泄漏。

不存在未定义的行为。泄漏内存是完全合法的。

未定义的行为:是标准特别不想定义的操作,并留给实现,以便在不违反标准的情况下灵活地执行某些类型的优化。

内存管理已明确定义。
如果你动态分配内存并且不释放它。然后,内存仍然是应用程序的属性,可以根据需要进行管理。事实上,你已经丢失了对那部分内存的所有引用,这一事实既不存在也不存在。

当然,如果继续泄漏,最终将耗尽可用内存,并且应用程序将开始抛出 bad_alloc 异常。但这是另一个问题。

Memory leaks.

There is no undefined behavior. It is perfectly legal to leak memory.

Undefined behavior: is actions the standard specifically does not want to define and leaves upto the implementation so that it is flexible to perform certain types of optimizations without breaking the standard.

Memory management is well defined.
If you dynamically allocate memory and don't release it. Then the memory remains the property of the application to manage as it sees fit. The fact that you have lost all references to that portion of memory is neither here nor there.

Of course if you continue to leak then you will eventually run out of available memory and the application will start to throw bad_alloc exceptions. But that is another issue.

雨巷深深 2024-08-23 05:06:46

内存泄漏在 C/C++ 中是明确定义的。

如果我这样做:

int *a = new int[10];

接下来

a = new int[10]; 

我肯定会泄漏内存,因为无法访问第一个分配的数组,并且由于不支持 GC,因此该内存不会自动释放。

但这种泄漏的后果是不可预测的,并且对于同一给定应用程序,不同的应用程序以及不同的机器会有所不同。假设某个应用程序在一台计算机上由于泄漏而崩溃,但在另一台具有更多 RAM 的计算机上可能运行良好。此外,对于给定机器上的给定应用程序,由于泄漏而导致的崩溃可能会在运行期间的不同时间出现。

Memory leaks are definitely defined in C/C++.

If I do:

int *a = new int[10];

followed by

a = new int[10]; 

I'm definitely leaking memory as there is no way to access the 1st allocated array and this memory is not automatically freed as GC is not supported.

But the consequences of this leak are unpredictable and will vary from application to application and from machine to machine for a same given application. Say an application that crashes out due to leaking on one machine might work just fine on another machine with more RAM. Also for a given application on a given machine the crash due to leak can appear at different times during the run.

天生の放荡 2024-08-23 05:06:46

如果发生内存泄漏,执行将继续进行,就好像什么也没有发生一样。这是定义的行为。

沿着轨道,您可能发现对malloc的调用由于没有足够的可用内存而失败。但这是 malloc 的已定义行为,其后果也是明确定义的:malloc 调用返回 NULL

现在,这可能会导致不检查 malloc 结果的程序因分段冲突而失败。但这种未定义的行为(从语言规范的 POV 来看)是由于程序取消引用无效指针,而不是早期的内存泄漏或失败的 malloc 调用。

If you leak memory, execution proceeds as if nothing happens. This is defined behavior.

Down the track, you may find that a call to malloc fails due to there not being enough available memory. But this is a defined behavior of malloc, and the consequences are also well-defined: the malloc call returns NULL.

Now this may cause a program that doesn't check the result of malloc to fail with a segmentation violation. But that undefined behavior is (from the POV of the language specs) due to the program dereferencing an invalid pointer, not the earlier memory leak or the failed malloc call.

逆夏时光 2024-08-23 05:06:46

我对这句话的解读:

[...] 对于具有非平凡析构函数的类类型的对象,程序不需要在重用或释放该对象占用的存储空间之前显式调用析构函数;但是,如果没有显式调用析构函数,或者如果 删除表达式
(expr.delete) 不用于释放存储,析构函数不得被隐式调用,并且任何依赖于析构函数产生的副作用的程序都具有未定义的行为。

- C++20 标准,[basic.life] p5

如下:

如果您设法释放对象占用的存储而不调用占用内存的对象的析构函数,则结果是 UB,如果析构函数并不简单并且有副作用。

如果new使用malloc分配,则可以使用free()释放原始存储,析构函数将不会运行,并且将导致UB。或者,如果将指针强制转换为不相关的类型并删除,则内存将被释放,但会运行错误的析构函数,UB。

这与省略的删除不同,后者不会释放底层内存。省略 delete 并不是 UB。


注意:所引用段落中的 UB 已被 CWG Issue 2523 因为它在常量表达式中是不可诊断的。工作草案中的[basic.life] p5不再包含此内容。

My interpretation of this statement:

[...] For an object of a class type with a non-trivial destructor, the program is not required to call the destructor explicitly before the storage which the object occupies is reused or released; however, if there is no explicit call to the destructor or if a delete-expression
(expr.delete) is not used to release the storage, the destructor shall not be implicitly called and any program that depends on the side effects produced by the destructor has undefined behavior.

- C++20 Standard, [basic.life] p5

is as follows:

If you somehow manage to free the storage which the object occupies without calling the destructor on the object that occupied the memory, UB is the consequence, if the destructor is non-trivial and has side-effects.

If new allocates with malloc, the raw storage could be released with free(), the destructor would not run, and UB would result. Or if a pointer is cast to an unrelated type and deleted, the memory is freed, but the wrong destructor runs, UB.

This is not the same as an omitted delete, where the underlying memory is not freed. Omitting delete is not UB.


Note: the UB in the cited paragraph was removed by CWG Issue 2523 because it is undiagnosable in constant expressions. [basic.life] p5 in the working draft no longer contains this.

泛泛之交 2024-08-23 05:06:46

(下面评论“注意:此答案已从 https 移至此处://stackoverflow.com/questions/24137006/does-a-memory-leak-cause-undefined-behaviour" - 您可能必须阅读该问题才能获得此答案的正确背景O_o)。

在我看来,标准的这一部分明确允许:

  • 拥有一个自定义内存池,您可以将新对象放入其中,然后释放/重用整个对象,而无需花时间调用它们的析构函数, 只要您不依赖对象析构函数的副作用

  • 分配一点内存但从不释放它的库,可能是因为它们的函数/对象可以由静态对象的析构函数和注册的退出处理程序使用,并且不值得购买整个编排顺序 -每次发生这些访问时都会发生破坏或短暂的“凤凰”式重生。

我不明白为什么标准选择不定义行为(直到 C++23)当存在副作用依赖性时 - 而不是简单地说这些副作用不会发生并让程序定义或< /em> 在这个前提下,未定义的行为正如您通常所期望的那样。

我们可以仍然考虑标准所说的什么是未定义的行为。关键部分是:

“取决于析构函数产生的副作用具有未定义的行为。”

注意:此措辞已在 C++23 中删除,请参阅 [basic. life] p5

标准§1.9/12明确定义了副作用如下(下面的斜体是标准,表明引入了正式定义):

访问由易失性泛左值(3.10)指定的对象、修改对象、调用库 I/O 函数或调用执行任何这些操作的函数都是端效果,即执行环境状态的变化。

在您的程序中,没有依赖性,因此没有未定义的行为。

可以说与第 3.8 p4 节中的场景相匹配的依赖关系示例(其中未定义行为的需求或原因并不明显)是:

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

int main()
{
     new X();
}

人们争论的一个问题是是否会考虑上面的 X 对象出于 3.8 p4 的目的而发布,因为它可能仅在程序终止后发布到操作系统 - 通过阅读标准并不清楚进程“生命周期”的该阶段是否在标准行为的范围内要求(我对标准的快速搜索并没有澄清这一点)。我个人认为 3.8p4 在这里适用,部分原因是只要它足够模糊,编译器编写者可能会觉得有权在这种情况下允许未定义的行为,但即使上面的代码不构成发布,这种情况也很容易修改了 ala...

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

无论如何,但是 main 实现的上面的析构函数有一个副作用 - 根据“调用库 I/O 函数”;此外,程序的可观察行为可以说“依赖于”它,因为如果程序运行的话,将受到析构函数影响的缓冲区在终止期间被刷新。但是“取决于副作用”意味着暗示如果析构函数不运行,程序显然会出现未定义行为的情况吗?我会犯前者的错误,特别是因为后一种情况不需要标准中的专门段落来记录该行为未定义。这是一个明显未定义行为的示例:

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 () 因此将删除 p_ 已释放的指针,从而创建未定义的行为。如果x.~X();在重用之前被调用,p_将被设置为0并且删除将是安全的。从这个意义上说,程序的正确行为可以说取决于析构函数,并且该行为显然是未定义的,但是我们是否刚刚编写了一个与 3.8p4 所描述的行为本身相匹配的程序,而不是让该行为成为结果3.8p4...?

更复杂的问题场景 - 太长而无法提供代码 - 可能包括例如一个奇怪的 C++ 库,其文件流对象内带有引用计数器,必须命中 0 才能触发某些处理,例如刷新 I/O 或加入后台线程等。 -如果不执行这些操作,不仅会导致无法执行析构函数显式请求的输出,而且还会无法从流中输出其他缓冲输出,或者在某些具有事务性文件系统的操作系统上可能会导致早期 I/O 的回滚 -此类问题可能会改变可观察到的程序行为,甚至导致程序挂起。

注意:没有必要证明任何实际代码在任何现有的编译器/系统上行为异常;标准明确保留编译器具有未定义行为的权利......这才是最重要的。这不是您可以推理并选择忽略标准的事情 - 可能是 C++14 或其他一些修订版更改了此规定,但只要它存在,那么如果甚至可以说对 存在某种“依赖性”副作用然后就有可能出现未定义的行为(当然,它本身可以由特定的编译器/实现来定义,因此它并不自动意味着每个编译器都有义务做一些奇怪的事情)。

(Comment below "Heads-up: this answer has been moved here from https://stackoverflow.com/questions/24137006/does-a-memory-leak-cause-undefined-behaviour" - you'll probably have to read that question to get proper background for this answer O_o).

It seems to me that this part of the Standard explicitly permits:

  • having a custom memory pool that you placement-new objects into, then release/reuse the whole thing without spending time calling their destructors, as long as you don't depend on side-effects of the object destructors.

  • libraries that allocate a bit of memory and never release it, probably because their functions/objects could be used by destructors of static objects and registered on-exit handlers, and it's not worth buying into the whole orchestrated-order-of-destruction or transient "phoenix"-like rebirth each time those accesses happen.

I can't understand why the Standard chooses to leave the behaviour undefined (until C++23) when there are dependencies on side effects - rather than simply say those side effects won't have happened and let the program have defined or undefined behaviour as you'd normally expect given that premise.

We can still consider what the Standard says is undefined behaviour. The crucial part is:

"depends on the side effects produced by the destructor has undefined behavior."

Note: this wording was removed in C++23, see [basic.life] p5

The Standard §1.9/12 explicitly defines side effects as follows (the italics below are the Standards, indicating the introduction of a formal definition):

Accessing an object designated by a volatile glvalue (3.10), modifying an object, calling a library I/O function, or calling a function that does any of those operations are all side effects, which are changes in the state of the execution environment.

In your program, there's no dependency so no undefined behaviour.

One example of dependency arguably matching the scenario in §3.8 p4, where the need for or cause of undefined behaviour isn't apparent, is:

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

int main()
{
     new X();
}

An issue people are debating is whether the X object above would be considered released for the purposes of 3.8 p4, given it's probably only released to the O.S. after program termination - it's not clear from reading the Standard whether that stage of a process's "lifetime" is in scope for the Standard's behavioural requirements (my quick search of the Standard didn't clarify this). I'd personally hazard that 3.8p4 applies here, partly because as long as it's ambiguous enough to be argued a compiler writer may feel entitled to allow undefined behaviour in this scenario, but even if the above code doesn't constitute release the scenario's easily amended ala...

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

Anyway, however main's implemented the destructor above has a side effect - per "calling a library I/O function"; further, the program's observable behaviour arguably "depends on" it in the sense that buffers that would be affected by the destructor were it to have run are flushed during termination. But is "depends on the side effects" only meant to allude to situations where the program would clearly have undefined behaviour if the destructor didn't run? I'd err on the side of the former, particularly as the latter case wouldn't need a dedicated paragraph in the Standard to document that the behaviour is undefined. Here's an example with obviously-undefined behaviour:

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
}

When x's destructor is called during termination, b_ will be false and ~X() will therefore delete p_ for an already-freed pointer, creating undefined behaviour. If x.~X(); had been called before reuse, p_ would have been set to 0 and deletion would have been safe. In that sense, the program's correct behaviour could be said to depend on the destructor, and the behaviour is clearly undefined, but have we just crafted a program that matches 3.8p4's described behaviour in its own right, rather than having the behaviour be a consequence of 3.8p4...?

More sophisticated scenarios with issues - too long to provide code for - might include e.g. a weird C++ library with reference counters inside file stream objects that had to hit 0 to trigger some processing such as flushing I/O or joining of background threads etc. - where failure to do those things risked not only failing to perform output explicitly requested by the destructor, but also failing to output other buffered output from the stream, or on some OS with a transactional filesystem might result in a rollback of earlier I/O - such issues could change observable program behaviour or even leave the program hung.

Note: it's not necessary to prove that there's any actual code that behaves strangely on any existing compiler/system; the Standard clearly reserves the right for compilers to have undefined behaviour... that's all that matters. This is not something you can reason about and choose to ignore the Standard - it may be that C++14 or some other revision changes this stipulation, but as long as it's there then if there's even arguably some "dependency" on side effects then there's the potential for undefined behaviour (which of course is itself allowed to be defined by a particular compiler/implementation, so it doesn't automatically mean that every compiler is obliged do something bizarre).

自此以后,行同陌路 2024-08-23 05:06:46

证据的责任在于那些认为内存泄漏可能是 C++ UB 的人。

自然没有拿出任何证据。

简而言之,对于任何怀有任何怀疑的人来说,这个问题永远无法得到明确解决,除非通过非常可信地用大声的贾斯汀·比伯音乐来威胁委员会,以便他们添加一个 C++14 声明来澄清它是不是布。


有争议的是 C++11 §3.8/4:

对于具有非平凡析构函数的类类型的对象,在重用或释放该对象占用的存储空间之前,程序不需要显式调用析构函数;但是,如果没有显式调用析构函数,或者没有使用删除表达式(5.3.5)来释放存储,则析构函数不应被隐式调用,并且任何依赖于析构函数产生的副作用具有未定义的行为。

这段文字在 C++98 和 C++03 中具有完全相同的措辞(但是,UB 在 C++23 中被删除,请参阅 [basic.life] p5 作者:CWG 2523)。这是什么意思?

  • 在重用或释放对象占用的存储空间之前,程序不需要显式调用析构函数
     
    – 意味着可以获取变量的内存并重用该内存,而无需首先销毁现有对象。

  • 如果没有显式调用析构函数,或者没有使用删除表达式(5.3.5)来释放存储,则不应隐式调用析构函数
     
    – 意味着如果在内存重用之前没有销毁现有对象,那么如果该对象的析构函数被自动调用(例如本地自动变量),则程序具有未定义的行为,因为该析构函数将在一个没有定义的对象上进行操作更长的现有对象。

  • 任何依赖于析构函数产生的副作用的程序都具有未定义的行为
     
    – 不能按字面意思理解,因为根据副作用的定义,程序总是依赖于任何副作用。或者换句话说,程序没有办法不依赖副作用,因为那样它们就不是副作用了。

最有可能的是,我们的预期并不是最终进入 C++98 的内容,因此我们手头的只是一个缺陷

从上下文中我们可以猜测,如果程序依赖于静态已知类型 T 的对象的自动销毁,其中内存已被重用来创建一个或多个不是 的对象T 对象,那么这就是未定义的行为。


看过注释的人可能会注意到,上面对“应”一词的解释并不是我之前假设的含义。正如我现在所看到的,“应该”并不是实施的要求,也不是允许做什么的要求。这是对程序的要求,即允许代码执行的操作。

因此,这在形式上是 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.
}

一个主要问题是,通过上面标准段落的字面解释,如果放置 new 在那里创建了一个不同类型的对象,那么形式上仍然是可以的,只是因为对原件的明确破坏。但在这种情况下,它在实践中就不太可行了。也许标准中的其他段落涵盖了这一点,因此它也是正式的 UB。

这也可以,使用 中的 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's destructor.
}

正如我现在所看到的。

The burden of evidence is on those who would think a memory leak could be C++ UB.

Naturally no evidence has been presented.

In short for anyone harboring any doubt this question can never be clearly resolved, except by very credibly threatening the committee with e.g. loud Justin Bieber music, so that they add a C++14 statement that clarifies that it's not UB.


At issue is C++11 §3.8/4:

For an object of a class type with a non-trivial destructor, the program is not required to call the destructor explicitly before the storage which the object occupies is reused or released; however, if there is no explicit call to the destructor or if a delete-expression (5.3.5) is not used to release the storage, the destructor shall not be implicitly called and any program that depends on the side effects produced by the destructor has undefined behavior.

This passage had the exact same wording in C++98 and C++03 (however, the UB was removed in C++23, see [basic.life] p5 by CWG 2523). What does it mean?

  • the program is not required to call the destructor explicitly before the storage which the object occupies is reused or released

    – means that one can grab the memory of a variable and reuse that memory, without first destroying the existing object.

  • if there is no explicit call to the destructor or if a delete-expression (5.3.5) is not used to release the storage, the destructor shall not be implicitly called

    – means if one does not destroy the existing object before the memory reuse, then if the object is such that its destructor is automatically called (e.g. a local automatic variable) then the program has Undefined Behavior, because that destructor would then operate on a no longer existing object.

  • and any program that depends on the side effects produced by the destructor has undefined behavior

    – can't mean literally what it says, because a program always depends on any side effects, by the definition of side effect. Or in other words, there is no way for the program not to depend on the side effects, because then they would not be side effects.

Most likely what was intended was not what finally made its way into C++98, so that what we have at hand is a defect.

From the context one can guess that if a program relies on the automatic destruction of an object of statically known type T, where the memory has been reused to create an object or objects that is not a T object, then that's Undefined Behavior.


Those who have followed the commentary may notice that the above explanation of the word “shall” is not the meaning that I assumed earlier. As I see it now, the “shall” is not a requirement on the implementation, what it's allowed to do. It's a requirement on the program, what the code is allowed to do.

Thus, this is formally UB:

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

But this is OK with a literal interpretation:

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.
}

A main problem is that with a literal interpretation of the standard's paragraph above it would still be formally OK if the placement new created an object of a different type there, just because of the explicit destruction of the original. But it would not be very in-practice OK in that case. Maybe this is covered by some other paragraph in the standard, so that it is also formally UB.

And this is also OK, using the placement new from <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's destructor.
}

As I see it – now.

落日海湾 2024-08-23 05:06:46

语言规范没有提到“内存泄漏”。从语言的角度来看,当您在动态内存中创建对象时,您所做的就是:您正在创建一个具有无限生命周期/存储持续时间的匿名对象。在这种情况下,“无限”意味着该对象只能在您显式释放它时结束其生命周期/存储持续时间,否则它会继续永远存在(只要程序运行)。

现在,当对该对象的所有引用(通用“引用”,如指针)丢失到无法恢复的程度时,我们通常认为动态分配的对象在程序执行时会成为“内存泄漏”。请注意,即使对于人类来说,“所有引用都丢失”的概念也没有非常精确的定义。如果我们有对对象某些部分的引用,理论上可以“重新计算”为对整个对象的引用怎么办?是否存在内存泄漏?如果我们没有对该对象的任何引用,但我们可以使用程序可用的其他一些信息(例如精确的分配序列)来计算这样的引用,该怎么办?

语言规范本身并不关心此类问题。无论您如何看待程序中出现的“内存泄漏”,从语言的角度来看,它根本不是事件。从语言的角度来看,“泄漏的”动态分配的对象只是继续快乐地生活,直到程序结束。这是唯一剩下的关注点:当程序结束并且仍然分配一些动态内存时会发生什么?

如果我没记错的话,该语言没有指定动态内存会发生什么,动态内存在程序终止时仍然被分配。不会尝试自动破坏/释放您在动态内存中创建的对象。但在这种情况下,不存在正式的未定义行为

The language specification says nothing about "memory leaks". From the language point of view, when you create an object in dynamic memory, you are doing just that: you are creating an anonymous object with unlimited lifetime/storage duration. "Unlimited" in this case means that the object can only end its lifetime/storage duration when you explicitly deallocate it, but otherwise it continues to live forever (as long as the program runs).

Now, we usually consider a dynamically allocated object become a "memory leak" at the point in program execution when all references (generic "references", like pointers) to that object are lost to the point of being unrecoverable. Note, that even to a human the notion of "all references being lost" is not very precisely defined. What if we have a reference to some part of the object, which can be theoretically "recalculated" to a reference to the entire object? Is it a memory leak or not? What if we have no references to the object whatsoever, but somehow we can calculate such a reference using some other information available to the program (like precise sequence of allocations)?

The language specification doesn't concern itself with issues like that. Whatever you consider an appearance of "memory leak" in your program, from the language point of view it is a non-event at all. From the language point of view a "leaked" dynamically allocated object just continues to live happily until the program ends. This is the only remaining point of concern: what happens when program ends and some dynamic memory is still allocated?

If I remember correctly, the language does not specify what happens to dynamic memory which is still allocated the moment of program termination. No attempts will be made to automatically destruct/deallocate the objects you created in dynamic memory. But there's no formal undefined behavior in cases like that.

天气好吗我好吗 2024-08-23 05:06:46

除了所有其他答案之外,还有一些完全不同的方法。查看第 5.3.4-18 节中的内存分配,我们可以看到:

如果上述对象初始化的任何部分76终止
通过抛出异常和合适的释放函数可以
发现,调用释放函数来释放其中的内存
正在构造对象,之后异常继续
在 new 表达式的上下文中传播。如果没有明确的
可以找到匹配的释放函数,传播异常
不会导致对象的内存被释放。 [注:这是
当被调用的分配函数不分配时适用
记忆;否则,很可能会导致内存泄漏。 ——尾注
]

这里会不会导致UB,会提到,所以它是“只是内存泄漏”。

在像§20.6.4-10这样的地方,提到了可能的垃圾收集器和泄漏检测器。人们对安全派生指针等概念进行了很多思考。能够将 C++ 与垃圾收集器一起使用(C.2.10“对垃圾收集区域的最低支持”)。

因此,如果 UB 只是丢失了指向某个对象的最后一个指针,那么所有的努力都将毫无意义。

关于“当析构函数有副作用时不运行它时的 UB”,我想说这是错误的,否则像 std::quick_exit() 这样的设施本质上也是 UB 的。

Adding to all the other answers, some entirely different approach. Looking at memory allocation in § 5.3.4-18 we can see:

If any part of the object initialization described above76 terminates
by throwing an exception and a suitable deallocation function can be
found, the deallocation function is called to free the memory in which
the object was being constructed, after which the exception continues
to propagate in the context of the new-expression. If no unambiguous
matching deallocation function can be found, propagating the exception
does not cause the object’s memory to be freed. [ Note: This is
appropriate when the called allocation function does not allocate
memory; otherwise, it is likely to result in a memory leak. —end note
]

Would it cause UB here, it would be mentioned, so it is "just a memory leak".

In places like §20.6.4-10, a possible garbage collector and leak detector is mentioned. A lot of thought has been put into the concept of safely derived pointers et.al. to be able to use C++ with a garbage collector (C.2.10 "Minimal support for garbage-collected regions").

Thus if it would be UB to just lose the last pointer to some object, all the effort would make no sense.

Regarding the "when the destructor has side effects not running it ever UB" I would say this is wrong, otherwise facilities such as std::quick_exit() would be inherently UB too.

北恋 2024-08-23 05:06:46

它绝对是定义的行为。

考虑服务器正在运行并不断分配堆内存的情况,即使不使用内存也不会释放内存。
因此,最终结果将是服务器最终将耗尽内存并且肯定会发生崩溃。

Its definately defined behaviour.

Consider a case the server is running and keep allocating heap memory and no memory is released even if there's no use of it.
Hence the end result would be that eventually server will run out of memory and definately crash will occur.

愛放△進行李 2024-08-23 05:06:46

如果航天飞机必须在两分钟内起飞,并且我可以在将其放入泄漏内存的代码和具有未定义行为的代码之间进行选择,那么我将放入泄漏内存的代码。

但我们大多数人通常不会遇到这种情况,即使遇到这种情况,也可能是因为进一步的失败。也许我错了,但我把这个问题理解为“哪种罪会让我更快地进入地狱?”

可能是未定义的行为,但实际上两者都是。

If the space shuttle must take off in two minutes, and I have a choice between putting it up with code that leaks memory and code that has undefined behavior, I'm putting in the code that leaks memory.

But most of us aren't usually in such a situation, and if we are, it's probably by a failure further up the line. Perhaps I'm wrong, but I'm reading this question as, "Which sin will get me into hell faster?"

Probably the undefined behavior, but in reality both.

椒妓 2024-08-23 05:06:46

定义,因为内存泄漏是指您忘记自行清理。

当然,内存泄漏可能会导致稍后出现未定义的行为。

defined, since a memory leak is you forgetting to clean up after yourself.

of course, a memory leak can probably cause undefined behaviour later.

谷夏 2024-08-23 05:06:46

直接回答:标准没有定义泄漏内存时会发生什么,因此它是“未定义的”。但它是隐式未定义的,这比标准中显式未定义的东西没那么有趣。

Straight forward answer: The standard doesn't define what happens when you leak memory, thus it is "undefined". It's implicitly undefined though, which is less interesting than the explicitly undefined things in the standard.

仙气飘飘 2024-08-23 05:06:46

这显然不可能是未定义的行为。仅仅是因为 UB 必须在某个时间点发生,而忘记释放内存或调用析构函数不会在任何时间点发生。所发生的情况只是程序终止而没有释放内存或调用析构函数;这不会以任何方式使程序或其终止的行为变得不确定。

话虽如此,我认为这段话中的标准是自相矛盾的。一方面,它确保在这种情况下不会调用析构函数,另一方面,它表示如果程序依赖于析构函数产生的副作用,则它具有未定义的行为。假设析构函数调用exit,那么任何执行任何操作的程序都不能假装独立于该析构函数,因为调用析构函数的副作用会阻止它执行本来会执行的操作;但文本还保证析构函数不会被调用,以便程序可以不受干扰地继续执行其操作。我认为阅读这段文字结尾的唯一合理的方法是,如果程序的正确行为需要调用析构函数,那么行为实际上是未定义的;这是多余的评论,因为刚刚规定析构函数不会被调用。

This obviously cannot be undefined behaviour. Simply because UB has to happen at some point in time, and forgetting to release memory or call a destructor does not happen at any point in time. What happens is just that the program terminates without ever having released memory or called the destructor; this does not make the behaviour of the program, or of its termination, undefined in any way.

This being said, in my opinion the standard is contradicting itself in this passage. On one hand it ensures that the destructor will not be called in this scenario, and on the other hand it says that if the program depends on the side effects produced by the destructor then it has undefined behaviour. Suppose the destructor calls exit, then no program that does anything can pretend to be independent of that, because the side effect of calling the destructor would prevent it from doing what it would otherwise do; but the text also assures that the destructor will not be called so that the program can go on with doing its stuff undisturbed. I think the only reasonable way to read the end of this passage is that if the proper behaviour of the program would require the destructor to be called, then behaviour is in fact not defined; this then is a superfluous remark, given that it has just been stipulated that the destructor will not be called.

余生一个溪 2024-08-23 05:06:46

未定义的行为意味着将发生什么尚未定义或未知。在 C/C++ 中,众所周知,内存泄漏的行为会消耗可用内存。然而,由此产生的问题并不总是能够按照游戏结束的描述进行定义和变化。

Undefined behavior means, what will happen has not been defined or is unknown. The behavior of memory leaks is definitly known in C/C++ to eat away at available memory. The resulting problems, however, can not always be defined and vary as described by gameover.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文