1

Assume we have the following piece of code:

void foo() {
  char buffer[100];
}

Is there a (preferably portable) way in C to deallocate buffer from the runtime stack (akin to add esp, 100 in assembly), before foo() returns?

D.Fonkaz
  • 45
  • 3
  • 7
  • 2
    It will be "deallocate from the runtime stack" when the execution exits the function. – goodvibration May 25 '19 at 08:11
  • @goodvibration added a clarification. – D.Fonkaz May 25 '19 at 08:13
  • 1
    The portable way is that code does nothing to deallocate `buffer`. As far as the program is concerned, `buffer` ceases to exist when the function returns. The implementation (i.e. the compiler) is responsible for ensuring any cleanup that might be required, and there is no standard/portable way for C code to affect (or even monitor) that - the only way is machine-dependent code (e.g. assembly) which, by definition, is not portable. If you need to provide evidence that `buffer` is deallocated, then you need to conduct validation and verification related to your *compiler*, not your code – Peter May 25 '19 at 08:24
  • 1
    This sounds like an [XY problem](https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem). What you are you really trying to do? – selbie May 25 '19 at 08:32
  • @selbie This is admittedly more of a theoretical problem than a concrete one. Suppose the program runs on an architecture where the stack size is small, and you need to use a large buffer to temporarily store some data in a function. Later on in the function, after you're done with the buffer, you need to use a rather large struct for some purpose. Also suppose the buffer and struct operations are closely related, so it makes little sense to separate them into different functions. Under those conditions, how can one reuse the stack memory allocated for the buffer, for allocating the struct? – D.Fonkaz May 25 '19 at 08:47
  • 1
    Fair enough. Then you either sub-scope your arrays and variables within the function. Or just use heap allocations when you are in this situation. In some limited cases where I need fast allocs and buffer re-use, I've built a simple buffer-pool stack. – selbie May 25 '19 at 08:56

4 Answers4

5

No. The best you can do in C is use scopes:

void foo()
{
    {
        char buffer[100];
    }
}

and rely on the compiler to consider the 100 bytes of buffer available again after the inner scope exits. Unfortunately, this is not guaranteed by the standard and you need to depend on the compiler. For example, consider the following program on a typical Linux machine with a stack space of 8192KB (ulimit -s):

#include <stdio.h>

int main(void)
{
    {
        char buffer1[8192 * 800] = { 0 };
        ((char volatile *)buffer1)[0] = buffer1[0];
        printf("%p\n", buffer1);
    }

    {
        char buffer2[8192 * 800] = { 0 };
        ((char volatile *)buffer2)[0] = buffer2[0];
        printf("%p\n", buffer2);
    }

    return 0;
}

(The weird cast is to prevent the buffer variables from being optimized out.)

The program will overflow the available stack space on some compilers when not building with optimizations. clang -O0 for example will crash, but clang -O1 will reuse the buffer1 memory for buffer2 and both addresses will be the same. In other words, buffer1 has been "freed" in a sense when the scope exits.

GCC on the other hand will do this optimization even at -O0.

Nikos C.
  • 50,738
  • 9
  • 71
  • 96
2

Given:

void foo(void) {
  char buffer[100];
}

the lifetime of the object buffer begins when execution reaches the opening { (on entry to the function) and ends when execution leaves the block, reaching the closing } (or by other means such as a goto, break, or return statement).

There's no way to end the lifetime of buffer other than by leaving the block. If you want to be able to deallocate it, you have to allocate it in some other way. For example, if you allocate an object by calling malloc(), you can (and pretty much must) deallocate it by calling free:

void foo(void) {
    char *buffer_ptr = malloc(100);
    if (buffer_ptr == NULL) /* error handling code here */
    /* ... */
    if (we_no_longer_need_the_buffer) {
        free(buffer_ptr);
    }
    /* now buffer_ptr is a dangling pointer */
}

An alternative is to restrict the lifetime of buffer by defining it in a nested block:

void foo(void) {
    /* ... */
    {
        char buffer[100];
        /* ... */
    }
    /* buffer's lifetime has ended */
}

But just because buffer's lifetime ends when control leaves the inner block, that doesn't guarantee that it's physically deallocated. In the abstract machine, buffer no longer exists after leaving the inner block, but the generated code might leave it on the stack just because it's more convenient.

If you want control over allocation and deallocation, you need to use malloc and free.

Keith Thompson
  • 254,901
  • 44
  • 429
  • 631
  • I experimented with gcc, and I was able to "force its hand" by using a dynamically sized array inside a loop. The compiler has to guarantee at least as much as the declared size each time through the loop. So the first time through, I used the full size, then the second time through I dropped the size down to 1. The compiler, not knowing if the second size was larger or smaller, resized the array down to 1. I realize there's no guarantee that the stack will shrink, but in practice, it seems pretty likely it will (and it did with gcc). – Tom Karzes May 25 '19 at 09:17
  • VLAs (which are nonexistent in C90, required in C99, and optional in C11) have somewhat different rules. The lifetime of a VLA starts at the point of its declaration, not on entry to the enclosing block. How did you verify that the memory was actually deallocated? – Keith Thompson May 25 '19 at 18:56
  • I just called a function from within the loop, and the function printed the address of a local variable. So I could see the size of the caller's stack frame change. – Tom Karzes May 25 '19 at 19:28
1

it is the automatic variable and you do not have to do anything as it will be reallocated when leaving the scope of it. in your case when you return from the function

if you want make the scope narrowed just place it inside another scope.

foo()
{
      for(... .)
      {
              // if defined here it will be only in the for loop  scope
       }

       {
           // if defined here it will be only in this internal scope
        }

}

bare in mind that it requires the C standard which allows it.

0___________
  • 60,014
  • 4
  • 34
  • 74
1

Because char buffer[100]; is declared local to the function stack for void foo(), when void foo() returns, the storage for buffer is released for reuse and is no longer valid for access after that point.

So there is nothing you need to do, or can do, to "deallocate buffer" as that is handled automatically by virtue of buffer being an array with automatic storage duration, e.g. see: C11 Standard - 6.2.4 Storage durations of objects (p5)

David C. Rankin
  • 81,885
  • 6
  • 58
  • 85