What rules in the C++ and C standards make initialization of static objects from other static objects valid in C++ but not C

165 Views Asked by At

Why does the code below compile as C++ without complaint, but a C compiler complains that an initializer is not a compile-time constant?

int x = 2;
int y = 1;
    
int a[2] = {x, y};

#include <stdio.h>

int main()
{
    printf("Hello world\n");
    return 0;
}
3

There are 3 best solutions below

0
user17732522 On

In C, static storage duration objects (such as the global variable a) must be initialized with constant expressions, which x and y are not (in either C or C++).

In C++ such objects can be initialized with any expression that they could also be initialized by as automatic storage duration variables. However, for global variables the initialization potentially happens at runtime, usually before main is entered, and largely in unspecified order. Therefore non-constexpr global variables like this should be generally avoided if possible or special care needs to be taken that dependencies are initialized in the correct order. C doesn't do any dynamic initialization at runtime.

3
Vlad from Moscow On

From the C11 Standard (6.7.9 Initialization)

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

In C these declarations

int x = 2;
int y = 1;

do not provide constant expressions. Even if you will write

const int x = 2;
const int y = 1;

then again they do not provide compile-time constant expressions allowed to be used as initializers of an object with static storage duration..

From the C11 Standard (6.6 Constant expressions)

7 More latitude is permitted for constant expressions in initializers. Such a constant expression shall be, or 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.

and

8 An arithmetic constant expression shall have arithmetic type and shall only have operands that are integer constants, floating constants, enumeration constants, character constants, and sizeof expressions. Cast operators in an arithmetic constant expression shall only convert arithmetic types to arithmetic types, except as part of an operand to a sizeof operator whose result is an integer constant.

If your compiler supports C23 Standard then you can write

constexpr int x = 2;
constexpr int y = 1;

int a[2] = { x, y };

In C23 identifier x and y are called like named constants and named constants may be used as initializers of objects with static storage duration.

In C++ such an initialization of an object with static storage duration

int x = 2;
int y = 1;

int a[2] = { x, y };

is called dynamic initialization and occurs at run-time.

0
dbush On

This expression:

int a[2] = {x, y};

Requires runtime code to evaluate the values of x and y, and running code outside of a function is not allowed in C but is in C++.

Given the following C++ code:

#include <iostream>

class test {
private:
    int a;
    int b;
public:
    abc() {
        cin >> a;
        cin >> b;
    }
};

test x;

int main()
{
    return 0;
}

The file scope object x has class type with a constructor. This means that it has to be initialized before main is called, and that initialization requires code to run, so this therefore requires a mechanism in place to run code outside of the main function.

In contrast, C doesn't have a core need to run code outside of a function, so it was never introduced. As such, any initialization of static objects in C requires the initializers to be fixed at compile time.

When C++ was created from C, adding the ability to run code outside of main became required for the core language to function.