Getting a dangling pointer by returning a pointer from a local C-style array

I am a bit confused by the following code:

#include <iostream>

const char* f()
{
    const char* arr[]={"test"};
    return arr[0];
}

int main()
{
    auto x = f();
    std::cout << x;
}

In my opinion, this code should be UB (undefined behaviour). We return a pointer to a C-style array element inside a local scope. Things should go wrong. However, none of the compilers I tested with complain (I used -Wall -Wextra -pedantic on both g++ and clang). valgrind does not complain either.

Is the code above valid or is it UB as one would think?

PS: running it seems to produce the "correct" result, i.e. displaying "test", but that's not an indication of correctness.


Solution 1:

No, it's not UB.

This:

const char* f()
{
    const char* arr[]={"test"};
    return arr[0];
}

Can be rewritten to the equivalent:

const char* f()
{
    const char* arr0 = "test";
    return arr0;
}

So we're just returning a local pointer, to a string literal. String literals have static storage duration, nothing dangles. The function really is the same as:

const char* f()
{
    return "test";
}

If you did something like this:

const char* f() {
    const char arr[] = "test"; // local array of char, not array of char const*
    return arr;
}

Now that is UB - we're returning a dangling pointer.

Solution 2:

The array arr has local storage duration and will disappear at the end of scope. The string literal "test" however is a pointer to a static storage location. Temporarily storing this pointer in the local array arr before returning it doesn't change that. It will always be a static storage location.

Note that if the function were to return a C++ style string type instead of a C style const char *, the additional conversion/bookkeeping would likely leave you with a value limited in lifetime according to C++ temporary rules.