When will stack memory be reallocated?
Asked Answered
P

4

8

There is code like the following:

int fun(){

    char* pc = NULL;

    {
        char ac[100] = "addjfidsfsfds";
        pc = ac;
    }

    ...

    pc[0] = 'd';
    printf("%s\n", pc);
    ...
}

So, can I use pc safely after the scope of ac ends? Because I'm not sure whether the stack memory allocated for ac will be reallocated by the compiler for other usage or not.

Pharmacopoeia answered 30/3, 2018 at 2:7 Comment(9)
No, you can't use it.Countrydance
All variables local to a block become invalid when the block ends.Countrydance
Could you please explain it in detail?@CountrydancePharmacopoeia
Stack memory is an implementation detail. It's undefined behavior to refer to a local object after exiting the block in which it was created.Countrydance
Try putting another block with local variables after the first block. I'll bet they'll clobber the memory that pc points to.Countrydance
Or just call another function in the first .... The stack frame used for the function call will probably clobber it.Countrydance
There is no need to reallocate memory to make this break. The compiler may optimize the code to not write that otherwise unused array to the stack in the first place.Aside
And I also vaguely recall that the area of memory below the stack pointer can be used by the OS as a scratchpad during signal handling.Cheju
that's not valid, it'll work in gcc, but not in other compilers.... and not all versions of gcc support this, it's not valid.Mn
F
8

The defect in understanding you have has to do with the Storage Duration of Objects. Unless you are working with threads, you have three types to be concerned with, static, automatic, and allocated. By declaring char ac[100] = "addjfidsfsfds"; within the block and without the static storage-class specifier, the storage duration is automatic and its lifetime ends when execution of the bock ends. Attempting to access the value afterwards is Undefined Behavior.

The C-Standard lays this out in detail in section 6.2.4, e.g.

C11 - 6.2.4 Storage durations of objects

1 An object has a storage duration that determines its lifetime. There are four storage durations: static, thread, automatic, and allocated. Allocated storage is described in 7.22.3.

2 The lifetime of an object is the portion of program execution during which storage is guaranteed to be reserved for it. An object exists, has a constant address,33) and retains its last-stored value throughout its lifetime.34) If an object is referred to outside of its lifetime, the behavior is undefined. The value of a pointer becomes indeterminate when the object it points to (or just past) reaches the end of its lifetime.

3 An object whose identifier is declared without the storage-class specifier _Thread_local, and either with external or internal linkage or with the storage-class specifier static, has static storage duration. Its lifetime is the entire execution of the program and its stored value is initialized only once, prior to program startup.

( _Thread_local details omitted)

5 An object whose identifier is declared with no linkage and without the storage-class specifier static has automatic storage duration, as do some compound literals. The result of attempting to indirectly access an object with automatic storage duration from a thread other than the one with which the object is associated is implementation-defined.

6 For such an object that does not have a variable length array type, its lifetime extends from entry into the block with which it is associated until execution of that block ends in any way. (Entering an enclosed block or calling a function suspends, but does not end, execution of the current block.) If the block is entered recursively, a new instance of the object is created each time. The initial value of the object is indeterminate. If an initialization is specified for the object, it is performed each time the declaration or compound literal is reached in the execution of the block; otherwise, the value becomes indeterminate each time the declaration is reached.

7 For such an object that does have a variable length array type, its lifetime extends from the declaration of the object until execution of the program leaves the scope of the declaration.35) If the scope is entered recursively, a new instance of the object is created each time. The initial value of the object is indeterminate.

If there is ever a question about whether accessing a value is allowed or not, consult the standard.

Filling answered 30/3, 2018 at 2:41 Comment(0)
W
4

From C Standard#6.2.1p4 [emphasis mine]

Every other identifier has scope determined by the placement of its declaration (in a declarator or type specifier). If the declarator or type specifier that declares the identifier appears outside of any block or list of parameters, the identifier has file scope, which terminates at the end of the translation unit. If the declarator or type specifier that declares the identifier appears inside a block or within the list of parameter declarations in a function definition, the identifier has block scope, which terminates at the end of the associated block. ......

Variable ac is a local(automatic) non-static variable and its lifetime is limited to its scope i.e. the block in which it has been declared. Any attempt to access it outside of its lifetime lead to undefined behavior.

From C Standard#6.2.4p2 [emphasis mine]

The lifetime of an object is the portion of program execution during which storage is guaranteed to be reserved for it. An object exists, has a constant address,33) and retains its last-stored value throughout its lifetime.34) If an object is referred to outside of its lifetime, the behavior is undefined. The value of a pointer becomes indeterminate when the object it points to (or just past) reaches the end of its lifetime.

Wetzel answered 30/3, 2018 at 2:27 Comment(0)
E
2

Using pc outside the inner scope is unsafe in your example. Because ac is destroyed right after pc = ac, you have a classic dangling pointer. With sufficient warnings enabled, I assume most compilers will emit warnings.

This may appear to work on your system, but that doesn't mean it's safe. Some compilers may choose to perform reclaim all primitives at once as an optimization, but they're not required to do that.

Euphony answered 30/3, 2018 at 2:11 Comment(1)
"unsafe"? Even more: It's simply undefined.Collate
W
2

It may be reallocated after the end of the block (when it goes out of scope).

Whether it will be reallocated varies, depending on the program and on the architecture of the machine on which it's running (and the compiler).

It's likely to be reallocated when you declare another local variable (on the stack); and/or, when you make a function call (which pushes parameter values and a return address onto the stack, and lets the called function declare new variables).

It's theoretically possible it may be reallocated at a pseudo-random time, by a hardware interrupt or similar. In some implementations an interrupt service handler (which might run at any time) starts by saving the current machine state on the stack.

Workbench answered 30/3, 2018 at 8:26 Comment(8)
+1 for mentioning "pseudo-random time" due to "hardware interrupt or similar". There are in fact such platforms ...Calabar
Even more theoretically, you could relatively easily design a compiler where doing this is actually safe, by simply using GC'd stack frames instead of a linear call stack (nothing in the standard requires automatic objects to be allocated near each other). Function call performance would be horrible, but it could be done.Wes
@Calabar I think it's true of x86 even in protected mode.Workbench
todays it's quite strange to see an architecture with interrupt mode and user mode storing the process state in the user stack. Mainly because that can lead to a double page fault in case the system has not memory for stack allocation.Mn
@LuisColorado Not the whole process state; but if an interrupt changes the instruction pointer, where is the old (current) EIP value stored, before dispatching to the interrupt service routine? I thought that EIP value was stored (pushed) on the stack, because where else?Workbench
@ChrisW, i mean what normally is automatically saved in an interrupt, the flags, the old execution mode and the program counter. It is normally saved in the kernel stack, instead of the user stack, so the user program can be interrupted even if it has not initialized an stack.Mn
@LuisColorado - You don't necessarily need an architecture: Windows supports asynchronous procedure calls (APCs) which under certain circumstances (e.g., the thread is in an alertable state) run on the thread's user mode stack! The thread would be in an alertable state in the OP's example if in the elided "..." it called certain APIs. See MSDN: Asynchronous Procedure CallsCalabar
@ChrisW, why do you complicate things so much? If the answer doesn't require complicate things (like asynchronous procedure calls) why to bother with them, is the access to the data is erroneous even for the simplest call (as the question posts) ? Are you trying to convince me that the access is invalid?Mn

© 2022 - 2024 — McMap. All rights reserved.