Is memory leak problem an “undefined behavior” in C ++?

It turns out many innocent looking things are undefined behavior in C ++. For example, once a non-null pointer was delete 'd even printed this pointer value undefined behavior .

Now memory leaks are definitely bad. But what is their class situation, undefined, or what other class of behavior?

+13
c ++ memory-management undefined-behavior memory-leaks
Dec 30 '09 at 6:26
source share
14 answers

Memory leaks.

No undefined behavior. Absolutely legal memory leak.

Undefined behavior: these are actions that the standard does not specifically want to define and goes before implementation, so it is flexible to perform certain types of optimization without violating the standard.

Memory management is clearly defined.
If you dynamically allocate memory and do not release it. Then the memory remains the property of the management application as it sees fit. The fact that you have lost all references to this part of the memory does not exist either there or there.

Of course, if you continue to leak, you will eventually run out of available memory, and the application will start throwing bad_alloc exceptions. But this is another problem.

+25
Dec 30 '09 at 7:15
source share

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

If I do this:

 int *a = new int[10]; 

followed by

 a = new int[10]; 

I definitely lose memory, since there is no access to the 1st allocated array, and this memory will not be automatically freed, since 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 the same given application. Let's say an application that crashes due to a leak on one machine can work fine on another machine with a large amount of RAM. In addition, for this application on this machine, an accident due to leakage may occur at different times during the run.

+6
Dec 30 '09 at 6:43
source share

When a memory leak occurs, execution is as if nothing was happening. This is determined by behavior.

Down the track, you may find that the malloc call failed due to lack of available memory. But this is malloc specific behavior, and the consequences are also well defined: calling malloc returns NULL .

Now this can lead to the failure of a program that does not check the result of malloc with segmentation violation. But this behavior is undefined (from POV language specifications) due to the fact that the program dereferences the wrong pointer, and not an earlier memory leak or an unsuccessful call to malloc .

+6
Dec 30 '09 at 7:47
source share

My interpretation of this statement:

For an object of the class type with a nontrivial destructor, the program does not need to call the destructor explicitly until the storage that the object occupies, reuses, or is freed; however, if there is no explicit call to the destructor or if the delete expression (5.3.5) is not used to free the repository, the destructor should not be implicit and any program that depends on side effects created by the destructor has undefined behavior.

as follows:

If you somehow free the storage that the object occupies without calling the destructor on the object that took up memory, UB is the consequence, if the destructor is nontrivial and has a third-party value, effects.

If new is allocated using malloc , raw storage can be released using free() , the destructor will not work, and UB will work. Or, if the pointer is discarded to an unrelated type and deleted, the memory is freed, but the wrong destructor, UB, is executed.

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

+6
Jun 10 '14 at 9:53 on
source share

(Comment below "Heads-up: this answer was moved here from Memory corruption causes undefined behavior? " - you will probably have to read this question to get the correct background for this O_o answer).

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

  • having your own memory pool in which you place the placement objects — new , then release / reuse all this without wasting time calling your destructors , unless you are dependent on the side effects of the object destructors .

  • libraries that allocate a little memory and never release it, probably because their functions / objects can be used by destructors of static objects and registered exit handlers, and you should not buy it in the whole ordered order - destruction or transitional "phoenix" - like rebirth every time these appeals occur.

I can’t understand why the Standard chooses to leave the behavior undefined when there are dependencies on the side effects, and not just say that these side effects will not happen, and let the program determine or undefined behavior, as you usually expected, given this premise.

We can still assume that the standard says that this behavior is undefined. The most important part:

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

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

Access to the object indicated by the value volatile glvalue (3.10), modifying the object, calling the library I / O function, or calling the function that performs any of these operations are all side effects that are changes in the state of the runtime environment.

There is no dependency in your program, so the behavior is undefined.

One example of a dependency suitable for the scenario in 3.8 p4, where the need or reason for undefined behavior is not obvious, is as follows:

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

The debatable question about the issues is whether the X object discussed above will be released above for purposes of 3.8 p4, since it is probably only released on the OS after the program is completed - it is not clear from reading the Standard whether this stage of the process is “life-time” within Standard behavioral requirements (my quick search for the Standard did not specify this). I personally would risk that 3.8p4 is applied here, partly because as long as it is ambiguous enough to claim that the script writer may be allowed to allow undefined behavior in this script, but even if the above code is not release, the script easily changed ala ...

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

In any case, however, the main implemented destructor above has a side effect - for "calling the library I / O function"; In addition, the observed behavior of the program may “depend” on it in the sense that the buffers that the destructor would have affected were launched upon short-term startup. But “depends on side effects” means only hints of a situation where the program would obviously have undefined behavior if the destructor did not work? I would be mistaken on the side of the first, especially since the latter case would not need a special paragraph in the Standard to fix that the behavior is undefined. Here is an example with explicitly-undefined behavior:

 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 the X destructor is called at completion time, b_ will be false , and ~X() will therefore be delete p_ for the pointer that has already been freed, creating undefined behavior. If x.~X(); was called before reuse , p_ would be set to 0, and deleting would be safe. In this sense, the correct behavior of the program can be said that it depends on the destructor, and the behavior is clearly undefined, but we just created a program that in itself corresponds to the described behavior of 3.8p4, instead of having the behavior of corollary 3.8p4 ...?

More complex problem scenarios — too long to provide code for — might include, for example, a weird C ++ library with reference counters inside file stream objects that should hit 0 to cause some processing, such as flushing input- output or combining background threads, etc. - where the refusal to perform these actions risked not only the inability to execute the output, the explicitly requested destructor, but also could not output other buffered output from the stream or to any OS with a transactional file system, which could lead to the rollback of previous I / O operations - such problems can change the behavior of the observed program or even leave the program freezes.

Note: there is no need to prove that there is any real code that behaves strangely in any existing compiler / system; The standard explicitly reserves the right for compilers to have undefined behavior ... that it all matters. This is not something you can reason about and don’t want to ignore the Standard - it is possible that C ++ 14 or some other revision changes this condition, but as long as it is there, if there is even a certain “dependence” on side effects, then there is the potential for undefined behavior (which, of course, can itself be determined by a specific compiler / implementation, so it does not automatically mean that every compiler is required to do something strange).

+4
Jun 10 '14 at 9:26
source share

The language specification says nothing about a memory leak. From a language point of view, when you create an object in dynamic memory, you do exactly that: you create an anonymous object with unlimited lifetime / storage duration. “Unlimited” in this case means that the object can only end its lifespan / storage when you explicitly release it, but otherwise it continues to live forever (as long as the program runs).

Now we usually consider that a dynamically allocated object becomes a “memory leak” at the moment of program execution, when all links (general “links”, such as pointers) to this object are lost to such an extent that they cannot be restored. Please note that even for a person, the concept of "all links are lost" is not very precisely defined. What if we have a link to some part of the object, which could theoretically be converted to a link to the entire object? Is it a memory leak or not? What if we do not have references to the object at all, but somehow we can calculate such a link using some other information available to the program (for example, the exact sequence of distributions)?

The language specification does not address such problems. Whatever you consider the occurrence of a “memory leak” in your program, from a language point of view, this is not an event at all. In terms of language, a “leaked” dynamically allocated object continues to live happily until the program ends. This is the only remaining problem: what happens when the program ends and some kind of dynamic memory is still allocated?

If I remember correctly, the language does not indicate what happens to dynamic memory, which still highlights the moment the program terminates. No attempt will be made to automatically destroy / free objects created in dynamic memory. But in such cases, there is no formal undefined behavior.

+3
Dec 30 '09 at 7:47
source share

The burden of proof lies with those who think a memory leak could be C ++ UB.

Naturally, no evidence was provided.

In short, anyone harboring any doubts, this question can never be clearly resolved, except that it very likely threatens the committee, for example. Justin Bieber’s loud music, so they add the C ++ 14 operator, which clarifies that it’s not UB.




With the release of C ++ 11 §3.8 / 4:

" For an object of class type with a nontrivial destructor, the program does not require a direct call to the destructor before the storage that the object occupies is reused or released, however, if there is no explicit call to the destructor or if the delete expression (5.3.5) is not used to free the storage , the destructor must not be implicitly called, and any program that depends on the side effects created by the destructor is undefined.

This passage had the same wording in C ++ 98 and C ++ 03. What does this mean?

  • the program does not require a direct call to the destructor before the storage that the object occupies is reused or released

    - means that you can capture the variable’s memory and reuse this memory without first destroying the existing object.

  • if there is no explicit call to the destructor or if the delete expression (5.3.5) is not used to free the storage, the destructor should not be implicitly called

    - means that if you do not destroy an existing object before reusing memory, then if the object is such that its destructor is automatically called (for example, a local automatic variable), then the program has undefined Behavior, because this destructor will work on a non-existing one an object.

  • and any program that depends on the side effects created by the destructor has undefined behavior
    - It cannot literally mean what he says, because the program always depends on any side effects, by the definition of a side effect. Or, in other words, there is no way for the program not to depend on side effects, because then they will not be side effects.

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

From the context, it can be assumed that if a program uses automatic destruction of an object of a statically known type T , where memory was reused to create an object or objects that are not T objects then undefined Behavior.




Those who have followed the comment may notice that the above explanation of the word “should” does not mean what I assumed earlier. As I see now, “necessity” is not a requirement for the implementation of what it is allowed to do. This is a software requirement that allows code.

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

The main problem is that if you literally interpret the standard paragraph above, it will still be formally OK if the new placement created an object of a different type there, just because of the explicit destruction of the original. But in this case it would not be very practical. Perhaps this falls under some other paragraph in the standard, so that it is also formally UB.

And this is also fine using the new placement 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 destructor. } 

As I can see, this is & ndash; Now.

+3
Jun 10 '14 at 9:28
source share

Specific behavior .

Consider the case when the server is running and continues to allocate heap memory, and memory is not freed, even if it is not used. Therefore, the end result will be that the server will eventually run out of memory and a certain crash will occur.

+2
Dec 30 '09 at 7:03
source share

Adding to all other answers is a completely different approach. Considering the memory allocation in § 5.3.4-18, we can see:

If any part of the initialization of the object described above, 76 ends by throwing an exception, and a suitable release function can be found, the release function is called freeing the memory in which the object was built, after which the exception continues to be distributed in the context of the new expression. If there is no one-to-one, the corresponding deallocation function can be found; the propagating exception does not deallocate the memory of objects. [Note: this is when the called distribution function does not allocate Memory; otherwise, it may result in a memory leak. -end note]

Whether this was the cause of UB here, it would be mentioned, so this is "just a memory leak."

In places like §20.6.4-10, a possible garbage collector and leak detector are mentioned. Many thoughts were included in the concept of safely received pointers, etc. to be able to use C ++ with the garbage collector (C.2.10 "Minimal support for garbage collectors").

Thus, if UB simply lost the last pointer to any object, all efforts would not make any sense.

As for “when a destructor has side effects that don't ever run it on UB,” I would say that this is not true, otherwise objects like std::quick_exit() would be inherently UB too.

+2
Jun 10 '14 at 16:24
source share

, , , undefined, , .

, , , , , . , , : " ?"

, undefined, .

+1
30 . '09 15:53
source share

, - , .

, , , undefined .

0
30 . '09 6:35
source share

: , , "undefined". undefined, , , undefined .

0
30 . '09 7:20
source share

, , undefined. , UB - , . , , , ; undefined - .

, , . , , , , , , , undefined. , exit , , , , , ; , , . , , , ; , , , .

0
10 . '14 14:36
source share

Undefined behavior means that something has not been defined or is unknown. The behavior of memory leaks is definitely known in C / C ++ to be in available memory. However, problems that may arise cannot always be identified and varied in accordance with the description of the game.

-one
Dec 30 '09 at 7:00
source share



All Articles