static struct initialization in c99
Asked Answered
P

5

17

I have encountered a strange behaviour when using compound literals for static struct initialization in GCC in c99/gnu99 modes.

Apparently this is fine:

struct Test
{
    int a;
};

static struct Test tt = {1}; /* 1 */

However, this is not:

static struct Test tt = (struct Test) {1}; /* 2 */

This triggers following error:

initializer element is not constant

Also this does not help either:

static struct Test tt = (const struct Test) {1}; /* 3 */

I do understand that initializer value for a static struct should be a compile-time constant. But I do not understand why this simplest initializer expression is not considered constant anymore? Is this defined by the standard?

The reason I'm asking is that I have encountered some legacy code written in GCC in gnu90 mode, that used such compound literal construct for static struct initialization (2). Apparently this was a GNU extension at the time, which was later adopted by C99.

And now it results in that the code that successfully compiled with GNU90 cannot be compiled with neither C99, nor even GNU99.

Why would they do this to me?

Penrose answered 6/8, 2015 at 18:42 Comment(5)
Btw., that has been fixed in GCC 5 (because the Linux kernel is using this extension but it didn't use -std=gnu89 and GCC 5 changed the default to gnu11).Aphoristic
If you want a const struct use static const struct Test tt = {1};Dana
@Dana - OP mentions that, but that's not their question. Their question is "why", not "how".Tieback
@Mr.Llama all the same: in terms of fixing the problem the best thing to do is to not use the redundant (at best) literals and just write simple code.Gloriane
@Dana it should be static struct Test tt = { 1 }; . The original code clearly is defining non-const objects.Gloriane
A
5

C language relies on an exact definition of what is constant expression. Just because something looks "known at compile time" does not mean that it satisfies the formal definition of constant expression.

C language does not define the constant expressions of non-scalar types. It allows implementations to introduce their own kinds of constant expressions, but the one defined by the standard are restricted to scalar types only.

In other words, C language does not define the concept of constant expression for your type struct Test. Any value of struct Test is not a constant. Your compound literal (struct Test) {1} is not a constant (and is not a string literal) and, for this reason, it cannot be used as an initializer for objects with static storage duration. Adding a const qualifier to it will not change anything since in C const qualifier has no relation whatsoever to the concept of constant expression. It will never make any difference in such contexts.

Note that your first variant does not involve a compound literal at all. It uses a raw { ... } initializer syntax with constant expressions inside. This is explicitly allowed for objects with static storage duration.

So, in the most restrictive sense, the initialization with a compound literal is illegal, while the initialization with ordinary { ... } initializer is fine. Some compilers might accept compound literal initialization as an extension. (By extending the concept of constant expression or by taking some other extension path. Consult compiler documentation to figure out why it compiles.)

Asthenic answered 6/8, 2015 at 21:50 Comment(1)
Thanks, it's much clearer for me now. Too bad that GCC didn't loosen that restriction until 5.2 though.Penrose
S
10

This is/was a gcc bug (HT to cremno), the bug report says:

I believe we should just allow initializing objects with static storage duration with compound literals even in gnu99/gnu11. [...] (But warn with -pedantic.)

We can see from the gcc document on compound literals that initialization of objects with static storage duration should be supported as an extension:

As a GNU extension, GCC allows initialization of objects with static storage duration by compound literals (which is not possible in ISO C99, because the initializer is not a constant).

This is fixed in gcc 5.2. So, in gcc 5.2 you will only get this warning when using the -pedantic flag see it live, which does not complain without -pedantic.

Using -pedantic means that gcc should provide diagnostics as the standard requires:

to obtain all the diagnostics required by the standard, you should also specify -pedantic (or -pedantic-errors if you want them to be errors rather than warnings)

A compound literal is not a constant expression as covered by the C99 draft standard section 6.6 Constant expressions, we see from section 6.7.8 Initialization that:

All the expressions in an initializer for an object that has static storage duration shall be constant expressions or string literals.

gcc is allowed to accept other forms of constant expressions as an extension, from section 6.6:

An implementation may accept other forms of constant expressions.

interesting to note that clang does not complain about this using -pedantic

Shennashensi answered 6/8, 2015 at 18:56 Comment(1)
Thanks a lot, I knew something was amiss. Now I only have to wait for GCC 5.2 to be ported on my embedded platform :DPenrose
A
5

C language relies on an exact definition of what is constant expression. Just because something looks "known at compile time" does not mean that it satisfies the formal definition of constant expression.

C language does not define the constant expressions of non-scalar types. It allows implementations to introduce their own kinds of constant expressions, but the one defined by the standard are restricted to scalar types only.

In other words, C language does not define the concept of constant expression for your type struct Test. Any value of struct Test is not a constant. Your compound literal (struct Test) {1} is not a constant (and is not a string literal) and, for this reason, it cannot be used as an initializer for objects with static storage duration. Adding a const qualifier to it will not change anything since in C const qualifier has no relation whatsoever to the concept of constant expression. It will never make any difference in such contexts.

Note that your first variant does not involve a compound literal at all. It uses a raw { ... } initializer syntax with constant expressions inside. This is explicitly allowed for objects with static storage duration.

So, in the most restrictive sense, the initialization with a compound literal is illegal, while the initialization with ordinary { ... } initializer is fine. Some compilers might accept compound literal initialization as an extension. (By extending the concept of constant expression or by taking some other extension path. Consult compiler documentation to figure out why it compiles.)

Asthenic answered 6/8, 2015 at 21:50 Comment(1)
Thanks, it's much clearer for me now. Too bad that GCC didn't loosen that restriction until 5.2 though.Penrose
C
4

Interestingly, the clang does not complain with this code, even with -pedantic-errors flag.

This is most certainly about C11 §6.7.9/p4 Initialization (emphasis mine going forward)

All the expressions in an initializer for an object that has static or thread storage duration shall be constant expressions or string literals.

Another subclause to look into is §6.5.2.5/p5 Compound literals:

The value of the compound literal is that of an unnamed object initialized by the initializer list. If the compound literal occurs outside the body of a function, the object has static storage duration; otherwise, it has automatic storage duration associated with the enclosing block.

and (for completeness) §6.5.2.5/p4:

In either case, the result is an lvalue.

but this does not mean, that such unnamed object can be treated as constant expression. The §6.6 Constant expressions says inter alia:

2) A constant expression can be evaluated during translation rather than runtime, and accordingly may be used in any place that a constant may be.

3) Constant expressions shall not contain assignment, increment, decrement, function-call, or comma operators, except when they are contained within a subexpression that is not evaluated.

10) An implementation may accept other forms of constant expressions.

There is no explicit mention about compound literals though, thus I would interpret this, they are invalid as constant expressions in strictly conforming program (thus I'd say, that clang has a bug).

Section J.2 Undefined behavior (informative) also clarifies that:

A constant expression in an initializer is not, or does not evaluate to, one of the following: an arithmetic constant expression, a null pointer constant, an address constant, or an address constant for a complete object type plus or minus an integer constant expression (6.6).

Again, no mention about compound literals.

Neverthless, there is a light in the tunnel. Another way, that is fully sanitized is to convey such unnamed object as address constant. The standard states in §6.6/p9 that:

An address constant is a null pointer, a pointer to an lvalue designating an object of static storage duration, or a pointer to a function designator; it shall be created explicitly using the unary & operator or an integer constant cast to pointer type, or implicitly by the use of an expression of array or function type. The array-subscript [] and member-access . and -> operators, the address & and indirection * unary operators, and pointer casts may be used in the creation of an address constant, but the value of an object shall not be accessed by use of these operators.

hence you can safely initialize it with constant expression in this form, because such compound literal indeed designates an lvalue of object, that has static storage duration:

#include <stdio.h>

struct Test
{
    int a;
};

static struct Test *tt = &((struct Test) {1}); /* 2 */

int main(void)
{
    printf("%d\n", tt->a);

    return 0;
}

As checked it compiles fine with -std=c99 -pedantic-errors flags on both gcc 5.2.0 and clang 3.6.

Note, that as opposite to C++, in C the const qualifier has no effect on constant expressions.

Curriery answered 6/8, 2015 at 19:8 Comment(0)
T
3

ISO C99 does support compound literals (according to this). However, currently only the GNU extension provides for initialization of objects with static storage duration by compound literals, but only for C90 and C++.

A compound literal looks like a cast containing an initializer. Its value is an object of the type specified in the cast, containing the elements specified in the initializer; it is an lvalue. As an extension, GCC supports compound literals in C90 mode and in C++, though the semantics are somewhat different in C++.

Usually, the specified type is a structure. Assume that struct foo and structure are declared as shown:

 struct foo {int a; char b[2];} structure;

Here is an example of constructing a struct foo with a compound literal:

 structure = ((struct foo) {x + y, 'a', 0});

This is equivalent to writing the following:

 {
   struct foo temp = {x + y, 'a', 0};
   structure = temp;
 }

GCC Extension:
As a GNU extension, GCC allows initialization of objects with static storage duration by compound literals ( which is not possible in ISO C99, because the initializer is not a constant ). It is handled as if the object is initialized only with the bracket enclosed list if the types of the compound literal and the object match. The initializer list of the compound literal must be constant. If the object being initialized has array type of unknown size, the size is determined by compound literal size.

 static struct foo x = (struct foo) {1, 'a', 'b'};
 static int y[] = (int []) {1, 2, 3};
 static int z[] = (int [3]) {1};

Note:
The compiler tags on your post include only GCC; however, you make comparisons to C99, (and multiple GCC versions). It is important to note that GCC is quicker to add extended capabilities to its compilers than the larger C standard groups are. This has sometimes lead to buggy behavior and inconsistencies between versions. Also important to note, extensions to a well known and popular compiler, but that do not comply with an accepted C standard, lead to potentially non-portable code. It is always worth considering target customers when deciding to use an extension that has not yet been accepted by the larger C working groups/standards organizations. (See ISO (Wikipedia) and ANSI (Wikipedia).)

There are several examples where the smaller more nimble Open Source C working groups or committees have responded to user base expressed interest by adding extensions. For example, the switch case range extension.

Twentieth answered 6/8, 2015 at 19:3 Comment(3)
Lot's of good information, but far too much bold and italic and italic bold to be easy reading. I think you should scale way back on the amount of emphasis.Electrocardiograph
@JonathanLeffler - Yeah, I do get carried away. Thanks for the edit.Twentieth
The switch-case extension is occasionally useful. I'd be much more interested in having the 'designated initializer ranges' extension standardized (To initialize a range of elements to the same value, write [first ... last] = value. This is a GNU extension. For example, int widths[] = { [0 ... 9] = 1, [10 ... 99] = 2, [100] = 3 };)Electrocardiograph
E
1

Quoting the C11 standard, chapter §6.5.2.5, Compound literals, paragraph 3, (emphasis mine)

A postfix expression that consists of a parenthesized type name followed by a brace-enclosed list of initializers is a compound literal. It provides an unnamed object whose value is given by the initializer list.

So, a compound literal is tread as an unnamed object, which is not considered a compile time constant.

Just like you cannot use another variable to initialize a static variable, onward C99, you cannot use this compound literal either to initialize a static variable anymore.

Enrollee answered 6/8, 2015 at 19:3 Comment(2)
@user3386109: C99 and C11 say the same thing. The object has static storage duration, but its value is not a constant expression so it can't be used in an initializer for a static object. (I'm trying to figure out how compound literals with static storage duration can be used.)Tomsk
Following up on my previous comment, I don't think you can meaningfully use the value of a static compound literal, but you can use its address (which is a constant address). For example: struct foo *foo_ptr = &(struct foo){.x = 10, .y = 20}; int *int_ptr = (int[]){100, 200, 300};. Both pointers point to the static object associated with the compound literal (or to its first element in the case of int_ptr).Tomsk

© 2022 - 2024 — McMap. All rights reserved.