Why is my initializer constant in one version, but not valid another version?

121 Views Asked by At

I'm trying to declare a static array of structures and can do so when it is declared globally, but not when declared static within a function.

Here's some sample code that works properly:

#include <stdio.h>

enum
{
    Mammals,
    Amphibians,
    Avians,
    Fish,
    Human,  Elephant,   Horse,
    Frog,   Salamander,
    Eagle, Sparrow, Duck,
    Salmon, Carp, Tuna
};

typedef struct
{
    int category;
    int* examples;
    int num_examples;
} classification_t;

static classification_t classifications[] = {
    { Mammals,    (int[]){Human, Elephant, Horse}, 3 },
    { Amphibians, (int[]){Frog, Salamander},       2 },
    { Avians,     (int[]){Eagle, Sparrow, Duck},   3 },
    { Fish,       (int[]){Salmon, Carp, Tuna},     3 }
};

int main(void)
{
    for(int i=0; i<4; ++i)
    {
        printf("Category %d has %d examples\n", classifications[i].category, classifications[i].num_examples);
    }
    
    return 0;
}

Output:

Category 0 has 3 examples
Category 1 has 2 examples
Category 2 has 3 examples
Category 3 has 3 examples

This works because the big table of classifications is a static-global variable.

However, when I try to move the table within main (or any other function), I get an error:

prog.c:25:17: error: initializer element is not constant { Mammals,    (int[]){Human, Elephant, Horse}, 3 },

In this code:

#include <stdio.h>

enum
{
    Mammals,
    Amphibians,
    Avians,
    Fish,
    Human,  Elephant,   Horse,
    Frog,   Salamander,
    Eagle, Sparrow, Duck,
    Salmon, Carp, Tuna
};

typedef struct
{
    int category;
    int* examples;
    int num_examples;
} classification_t;

int main(void)
{
    //
    // The ONLY change is to move the table from outside main to inside main.
    // error: initializer element is not constant { Mammals,    (int[]){Human, Elephant, Horse}, 3 },
    //
    static classification_t classifications[] = {
        { Mammals,    (int[]){Human, Elephant, Horse}, 3 },
        { Amphibians, (int[]){Frog, Salamander},       2 },
        { Avians,     (int[]){Eagle, Sparrow, Duck},   3 },
        { Fish,       (int[]){Salmon, Carp, Tuna},     3 }
    };

    for(int i=0; i<4; ++i)
    {
        printf("Category %d has %d examples\n", classifications[i].category, classifications[i].num_examples);
    }
    
    return 0;
}

It is not at all clear to me why the array initialization works in the first example, but not the second one.

Is there a simple technique to make this big table static, but declared within a function scope?



As one commenter pointed out, the entire problem can be described more directly like this:
static int* foo = (int[]) { 3, 4, 5 };
// This is Valid; apparently, the anonymous array is a constant-pointer

int main()
{
  static int* foo = (int[]) { 3, 4, 5 };
  // This is NOT valid; the anonymous array is likely on the stack.

  static int* foo = (static int[]) { 3, 4, 5 };
  // This is NOT valid; the static keyword is invalid before int[]
  // But it was an attempt to make the array "static"
}

Is there some way to declare a static, anonymous array within a function?

3

There are 3 best solutions below

0
Vlad from Moscow On

The difference is the storage duration of the compound literals used as initializers of static objects.

When you are using them in the file scope they have static storage duration and may be used as constant expressions to initialize objects,

When you are using them within main (in block scope) then they have automatic storage duration that is they may not be used as constant expressions to initialize objects with static storage duration.

From the C Standard )6.6 Constant expressions)

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.

7 More latitude is permitted for constant expressions in initializers. Such a constant expression shall be, or evaluate to, one of the following:

//...

an address constant, or

//...

and

9 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.

Pay attention to that the compound literals used as initializers of your array and having array types as for example

(int[]){Human, Elephant, Horse}

are implicitly converted to pointers to their first elements.

1
dbush On

You're getting the error with the declaration in a function because the initializer is not known at compile time.

From section 6.5.2.5 of the C standard regarding 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.

So when your object is declared at file scope, the compound literals have static storage duration, and therefore their addresses are known at compile time. But when you declare a static object inside of a function, the compound literals have automatic storage duration, and therefore cannot be used to initialize an object with static storage duration.

2
John Bollinger On

Why is my initializer constant in one version, but not valid another version?

Two other answers already explain this, so I'll just summarize: compound literals appearing at block scope have automatic storage duration, so their addresses are not address constants, whereas compound literals appearing at file scope have static storage duration, and their addresses are address constants. This analogous to the storage duration of named objects declared in those scopes.

Is there a simple technique to make this big table static, but declared within a function-scope?

You could declare the inner arrays as named static objects inside the function, and use those to initialize the big table.

You could perhaps make classification_t.examples an array (of sufficient length) instead of a pointer, so that you don't need to rely on compound literals to initialize it in the first place.

But ...

Is there some way to declare a static, anonymous array within a function?

No.

However, frame challenge: do you really need an array with static storage duration? In your example, you are declaring the array in main, so unless you re-enter main or the array is extremely large, there is little practical difference between a static array and an automatic one. And if the array is automatic, then the initializer is not limited to constant expressions:

int main(void) {
    // not static:
    classification_t classifications[] = {
        { Mammals,    (int[]){Human, Elephant, Horse}, 3 },
        { Amphibians, (int[]){Frog, Salamander},       2 },
        { Avians,     (int[]){Eagle, Sparrow, Duck},   3 },
        { Fish,       (int[]){Salmon, Carp, Tuna},     3 }
    };
}

Alternatively, the primary semantic difference between declaring your array at file scope and declaring it at block scope is linkage. Even when you declare it at file scope you are giving it internal linkage. Is that really not good enough? Is it essential that it have no linkage?