Home > Software design >  Can a return type be const-qualified?
Can a return type be const-qualified?

Time:10-20

Consider:

typedef struct { int a[1]; } S;
const S foo(void) { return (S) {{3}}; }
void bar(void) { int *p = foo().a; }

Per C 2018 6.2.4 8, the expression foo() refers to an object with temporary lifetime:

A non-lvalue expression with structure or union type, where the structure or union contains a member with array type (including, recursively, members of all contained structures and unions) refers to an object with automatic storage duration and temporary lifetime. Its lifetime begins when the expression is evaluated and its initial value is the value of the expression. Its lifetime ends when the evaluation of the containing full expression ends. Any attempt to modify an object with temporary lifetime results in undefined behavior. An object with temporary lifetime behaves as if it were declared with the type of its value for the purposes of effective type. Such an object need not have a unique address.

Note that the code does not attempt to modify the object, but it does initialize an int * with the address of the first element of foo().a, which we would expect to be const-qualified. This violates the constraints in 6.5.16.1 1 that require the pointed-to destination type to have all the qualifiers of the pointed-to source type. (That paragraph is for assignment but is incorporated by 6.7.9 11, which covers initialization.)

However, neither GCC nor Clang complain about this initialization. Indeed, with -Wextra, Clang complains about the return type of foo, saying “'const' type qualifier on return type has no effect”.

Are GCC and Clang wrong to treat foo() as not const-qualified? Or is there something in the C standard that says qualifiers on return types are disregarded?

(Lest one complain there is no use for this, consider one could have baz(foo().a), in which the array is passed to a function which makes considerable use of it while it is a temporary object. However, if baz modified any part of the array, the behavior would not be defined by the C standard. A programmer might wish to forestall this error by declaring foo’s return type to be const, thus expecting to get a compiler error message if they accidentally pass an address in the temporary object to a function without a corresponding const in its parameter declaration.)

CodePudding user response:

§6.7.6.3p5 "Function declarators (including prototypes) > Semantics":

If, in the declaration "T D1", D1 has the form

D ( parameter-type-list )

or

D ( identifier-listopt )

and the type specified for ident in the declaration "T D" is "derived-declarator-type-list T", then the type specified for ident is "derived-declarator-type-list function returning the unqualified version of T"

So the type of foo is the same as if it was declared S foo(void).

See also §6.7.3p5 "Type qualifiers > Semantics":

The properties associated with qualified types are meaningful only for expressions that are lvalues.

So even if the first quoted paragraph didn't change the type, the const-qualification of the non-lvalue expression foo() would not be meaningful

CodePudding user response:

Undefined behavior?

6.7.3 Type qualifiers
...
9 If the specification of an array type includes any type qualifiers, the element type is so qualified, not the array type. If the specification of a function type includes any type qualifiers, the behavior is undefined.136)

Or am I massively missing the point?

  • Related