On the scale and time of life

The quote is "C ++ Programming Language" (special edition, section 4.9.6, "Objects and Lvalues") and, as you know:

[...] an object declared in a function is created when its definition is encountered and destroyed when its name goes beyond the scope

OK! And in section 4.9.4:

The name can be used only in a certain part of the program text. For a name declared in a function, this area extends from its declaration point to the end of the block in which it is declared.

All this sounds normal!

But my question is: how can a (auto) variable be destroyed when the control reaches the end of its block? And the subquery: is this really so?

For instance:

int main() { int* c = 0; { int b = 999; c = &b; } // End of the scope of b... std::cout << b; // ... so this is illegal // But ... std::cout << *c; // ... is OK, so 'b' has not really been destroyed } 

I understand that a local variable is destroyed when it leaves the scope of its function due to stack-related things related to calling the function. But when you exit a simple block { // ... } nothing happens.

Is this a specific language that leads to undefined behavior (in my case, the last cout is actually undefined), but it is practically without effect on execution (nothing is actually done to destroy the object)

Thanks!

Edit 1: I do not consider static variables.

Edit 2: In the case where the variable is an object with a destructor, it was clear to me, I asked about non-target variables.

+4
source share
5 answers

The code in your example is really undefined, and it seems to work in simple examples like this. But the compiler can choose to reuse the slot used to store variable b. Or it can be destroyed by passing data to the stack due to a function call.

+9
source

In your example

 std::cout << *c; 

- undefined behavior - you are trying to access a variable whose lifetime has expired. It just happens that the memory address is still mapped into the address space of the program, and no one overwrites this memory, so it seems to work.

You should not rely on this, and you should never write such code. There may be an interruption to pause your program so that other programs start. If this happens, many operating systems save the current state of the CPU (registers the values) in one stack, which leads to overwriting the temporary resource with the end of its service life.

+5
source

an object declared in a function is created when its definition is encountered and destroyed when its name goes out of scope

This can be easily disproved for local statics:

 void f() { static string s = ""; } // out of scope, but still alive! 

Note that the namespace is a static concept of compilation time. But the lifetime of an object is a concept of runtime. Thus, you can fully access the already destroyed object. The compiler cannot protect you from this during compilation. If the storage time of this object was stopped at this stage, you can do nothing else with your variable, because memory is no longer guaranteed. The duration of storage for automatic variables is preserved only until the output of its block. Sometimes an object ends for life, but the vault on which the object was allocated still exists. This is true if you manually call the class destructor or for the active member of the union if you write to another member.

Destruction time is important for RAII operation. Take an example where we block a mutex

 void f() { { lock x(mutex); /* do something */ } // lock destroyed => mutex unlocked /* do non-exclusive stuff */ } 
+2
source

Derefercing 'c' in your example is undefined behavior. variable 'b' is out of scope and has been destroyed. If it still prints “999” (which is why I think you think that “b” was not destroyed), you are just lucky (or unlucky :))

+1
source

Referring to an object after its end of life, this is the "undefined behavior" of IIRC. Remember that “undefined behavior” has the unpleasant habit of showing itself as “working great.” Since @Pointy is mentioned in a comment, use something like std::string b("b"); as an example instead of <an integer std::string b("b"); , and you are likely to see completely different behavior.

When the scope is closed, destructors for objects are executed, so the state of the program changes. The "undefined" part enters the game because the Standard allows and expects the destructor to change the state of the memory that frees the object allocated for members and what is not. However, the value in memory may be completely unchanged, as is the case with your whole.

+1
source

All Articles