Referring to literals of built-in type

Viewed 115

So we have run into a failing test on Linux which I suppose stems from wrong assumptions on my side regarding the validity of pointers referring to built-in literals. The code looks similar to this pseudo code:

auto obj = func( 'c', "str" ); // (1)
big_type big_object;           // (2) 

At (1), func() returns an object that stores a const pointer to the character literal and one to the string literal. Inspection in the debugger shows that both are correct.

At (2), debugging shows that what used to be a 'c' at the memory referenced to by the const char* in obj is overwritten.

Tets show that this also happens with int and double literals. This happens on GCC 5.4.1, it does not happen on GCC 4.1.2.

Having done C++ for >25 years, I have learned to suppose that, usually, the compiler is right and I am wrong; so I am doing this here, too.

However, while I know that, if this only regards literals of small built-in types, I can fix this (by copying them instead of referring to them), if this could happen with arbitrarily-sized objects ("str") as well, we have a pretty big problem.

So can someone please explain the exact rules regarding this?

4 Answers

From [expr.prim.literal§1]:

A literal is a primary expression. Its type depends on its form. A string literal is an lvalue; all other literals are prvalues.

More precisions about these lvalues can be found at [lex.string§16]:

Evaluating a string-literal results in a string literal object with static storage duration, initialized from the given characters as specified above. [...]

Which directly addresses the issue: string literals are the only literals that have static storage duration, and thus can be referred to by pointers that outlive the expression in which they appear.

Assuming that func is defined something like this:

some_class_type func(const char& ch, const char* str)
{
    some_class_type some_object;
    some_object.pch = &ch;
    some_object.pstr = str;
    return some_object;
}

Then you store a pointer to a temporary variable with &ch.

The life-time of ch will not be the full program, only till the end of the full expression (i.e. the call func('c', "str")), then the temporary variable will cease to exist and you are left with a stray pointer.

For single characters, like for single integers or floating point values, there's almost never a need to use pointers to them. Store values.

String literals persist throughout the life of the program. Character literals do not, because they are really just integers and receive none of the special treatment given to strings.

To look at it another way: your function receives two arguments: one is a character value, and the other is a pointer to a string literal. Making a copy of the pointer-to-string is fine, but creating a pointer to a value which was passed as an argument is not OK. The same as you'd be in trouble if you had created a pointer to the pointer-to-string. The function arguments are destroyed when the function call is complete. In the character case that means the character is gone, whereas in the pointer-to-string case, you have made a copy and kept it.

For the exact rules, it's probably sufficient (although Quentin's standard quote is obviously more authoritative) to point out the following sentence on string literals

String literals have static storage duration, and thus exist in memory for the life of the program

which isn't present for any of the other types of literal.

The other way of looking at it is to re-examine your code

object func(char c, const char *s)
{
    return object{&c, s};
}

and notice that the string literal isn't passed by value. Since (as soon as it decays to a pointer) you're just passing the address of the first character - that array has to remain valid for at least some time, and since there's no way to know what the lifetime should be, static duration is a sane default.

Related