lifetime of a temporary function parameter

840 views Asked by At

Creating a temporary char buffer as a default function argument and binding an r-value reference to it allows us to compose statements on a single line whilst preventing the need to create storage on the heap.

const char* foo(int id, tmp_buf&& buf = tmp_buf()) // buf exists at call-site

Binding a reference/pointer to the temporary buffer and accessing it later yields undefined behaviour, because the temporary no longer exists.

As can be seen from the example app below the destructor for tmp_buf is called after the first output, and before the second output.

My compiler (gcc-4.8.2) doesn't warn that I'm binding a variable to a temporary. This means that using this kind of micro-optimisation to use an auto char buffer rather than std::string with associated heap allocation is very dangerous.

Someone else coming in and capturing the returned const char* could inadvertently introduce a bug.

1. Is there any way to get the compiler to warn for the second case below (capturing the temporary)?

Interestingly you can see that I tried to invalidate the buffer - which I failed to do, so it likely shows I don't fully understand where on the stack tmp_buf is being created.

2. Why did I not trash the memory in tmp_buf when I called try_stomp()? How can I trash tmp_buf?

3. Alternatively - is it safe to use in the manner I have shown? (I'm not expecting this to be true!)

code:

#include <iostream>

struct tmp_buf
{
    char arr[24];
    ~tmp_buf() { std::cout << " [~] "; }
};

const char* foo(int id, tmp_buf&& buf = tmp_buf())
{
    sprintf(buf.arr, "foo(%X)", id);
    return buf.arr;
}

void try_stomp()
{
    double d = 22./7.;
    char buf[32];
    snprintf(buf, sizeof(buf), "pi=%lf", d);
    std::cout << "\n" << buf << "\n";
}

int main()
{
    std::cout << "at call site: " << foo(123456789);
    std::cout << "\n";

    std::cout << "after call site: ";
    const char* p = foo(123456789);

    try_stomp();

    std::cout << p << "\n";
    return 0;
}

output:

at call site: foo(75BCD15) [~] 
after call site:  [~] 
pi=3.142857
foo(75BCD15)
1

There are 1 answers

3
The Dark On

For question 2.

The reason you didn't trash the variable is that the compile probably allocated all the stack space it needed at the start of the function call. This includes all the stack space for the temporary objects, and objects that are declared inside a nested scope. You can't guarantee that the compiler does this (I think), rather than push objects on the stack as needed, but it is more efficient and easier to keep track of where your stack variables are this way.

When you call the try_stomp function, that function then allocates its stack after (or before, depending on your system) the stack for the main function.

Note that the default variables for a function call are actually by the compile to the calling code, rather than being part of the called function (which is why the need to be part of the function declaration, rather than the definition, if it was declared separately).

So your stack when in try_stomp looks something like this (there is a lot more going on in the stack, but these are the relevant parts):

main       - p
main       - temp1
main       - temp2
try_stomp  - d
try_stomp  - buf

So you can't trash the temporary from try_stomp, at least not without doing something really outrageous.

Again, you can't rely on this layout, as it is compile dependent, and is just an exmaple of how the compiler might do it.

The way to trash the temporary buffer would be to do it in the destructor of tmp_buf.

Also interestingly, MSVC seems to allocate stack space for all of the temporary objects separately, rather than re-use the stack space for both objects. This means that even repeated calls to foo won't trash each other. Again, you can't depend on this behavior (I think - I couldn't find an reference to it).

For question 3. No, don't do this!