About scope and lifetime
Quoting "The C++ programming language" (special edition, section 4.9.6, "Objects and Lvalues"), and as is well known:
[...] an object declared in a function is created when its definition is encountered and destroyed when its name goes out of scope
OK ! And in section 4.9.4:
A name can be used only in specific part of the program text. For a name declared in a function, that scope extends from its point of declaration to the end of the block in which its declaration occurs.
This all sounds fine !
But my question is: how can an (auto) variable be destroyed when the control reach the end of its bloc开发者_StackOverflow中文版k? And a subquestion: Is it actually the case ?
For example:
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 quitting the scope of its function because of the stack related things involved in a function call. But when quitting a simple { // ... }
block, nothing happens.
Is it then a language specific that leads to undefined behavior (in my case the last cout
is actually undefined) but that is in practice without effect at execution (nothing is actually executed to destroy the object) ?
Thanks !
Edit 1: I am not considering static variables.
Edit 2: In case where the variable is an object with a destructor was clear to me, I was asking about non object variables.
The code in your sample is indeed undefined behavior, and it will appear to work in simple examples like this. But the compiler may choose to reuse the slot used to store variable b. Or it may be destroyed as the result of data being pushed on the stack because of a function call.
In your example
std::cout << *c;
is undefined behavior - you try to access a variable which lifetime has ended. It just happens that the memory address is still mapped into the program address space and noone has overwitten that memory, so it seems working.
You should not rely on that and you should never write code like that. What can likely happen is that an interrupt will occur to suspend your program to let other programs run. If that happens many operating systems save the current CPU state (registers values) onto the same stack and this leads to overwriting the temporaries with lifetime ended.
an object declared in a function is created when its definition is encountered and destroyed when its name goes out of scope
This can easily be disproven for local statics:
void f() {
static string s = "";
} // out of scope, but still alive!
Note that the scope of a name is a static, compile time concept. But lifetime of an object is a runtime concept. So you can entirely get a refer to to an already destroyed object. It's not possible for the compiler to protect you from this at compile time. In case that the storage duration that object was assigned stopped at this point too, you cannot do anything anymore with your variable, because memory is not guaranteed to exist anymore. Storage duration for automatic variables lasts only until the exit of its block. Sometimes an object ends lifetime, but the storage that object was allocated on still exists. This is true if you manually call a class's destructor or for the active member of an union if you write to the other member.
The destruction time is important for RAII to work. Let's take an example where we lock a mutex
void f() {
{
lock x(mutex);
/* do something */
} // lock destroyed => mutex unlocked
/* do non-exclusive stuff */
}
Derefercing 'c' in your example is an undefined behavior. variable 'b' is out of scope and has been destroyed. If it still prints '999' (which is why I think you believe that 'b' has not been destroyed), it is just that you are getting lucky (or unlucky :))
Referring to an object after its lifetime has ended is "undefined behavior" IIRC. Remember that "undefined behavior" has a nasty habit of exhibiting itself as "works fine". As @Pointy mentioned in a comment, use something like std::string b("b");
as the example instead of an integer and you will likely see quite different behavior.
When a scope is closed, destructors for objects are executed so the state of the program is modified. The "undefined" part comes into play because the Standard allows and expects a destructor to modify the state of the object - freeing memory allocated into members and what not. However, the value in memory may be completely unchanged as it is in the case of your integer.
精彩评论