skip to Main Content

Please consider the following absurd but compiling code:

int main(int argc, char *argv[]) {
    void *a, *b, *c, *d;

    void *the_good_array[] = { a, b, c, d };
    void *the_bad_array    = { a, b, c, d };

    return 0;
}

I accidentally produced something similar and, to my astonishment, it fully compiles.

  • Why does it compile?
  • What is { a, b, c, d } supposed to mean as an initializer for a non array? Without the braces, I would understand it’s an accidental use of the , operator resulting in the value of the last element (d). But, braces? What are they supposed to mean here?

If possible, please refer to the C standard when answering.

Edit 1:
Compiles with both:

gcc -std=iso9899:1990 /tmp/bad_array.c     # gcc (Debian 12.2.0-14)`
clang -std=iso9899:1990 /tmp/bad_array.c   # Debian clang version 14.0.6

Edit 2:
Apparently

int i = { 0 };

is perfectly valid C.

I would like to know why but don’t have the standard to read it.

2

Answers


  1. What is { a, b, c, d } supposed to mean as an initializer for a non array?

    Having multiple intializer elements for single object is a constraint violation. N2176 (C17 standard draft) §6.7.9 Initialization:

    Constraints

    1. No initializer shall attempt to provide a value for an object not contained within the entity being
      initialized.

    In this case it’s depends on compiler what happens.

    Why does it compile?

    C compilers take backwards compatibility quite seriosly, and will accept a lot of code that would be considered invalid nowadays.

    But, braces? What are they supposed to mean here?

    Braces are allowed allowed even around single expression:

    Syntax

      • initializer:
        • assignment-expression
        • { initializer-list }
        • { initializer-list , }
      • initializer-list:
        • designationopt initializer
        • initializer-list , designationopt initializer

    With rules above, initializer can after a few steps be expanded to { assignment-expression }.

    Login or Signup to reply.
  2. Any initializer may have braces.
    You can just omit them. From https://port70.net/~nsz/c/c11/n1570.html#6.7.9p11 :

    The initializer for a scalar shall be a single expression, optionally enclosed in braces.

    Example:

    int i = {1};
    

    The compiler informs you that:

    <source>:5:35: warning: excess elements in scalar initializer [-Wexcess-initializers]
        5 |     void *the_bad_array    = { a, b, c, d };
    

    Looks like the compiler chooses to ignore the "excess", taking only a.

    Why does it compile?

    The short answer: the compiler was created by people in the way that compiles it. The GCC compiler writers chose to handle excess elements in scalar initializer in this particular way.

    The language-lawyer answer: the code is invalid. It breaks the "shall" from the link above:

    The initializer for a scalar shall be a single expression

    From https://port70.net/~nsz/c/c11/n1570.html#4p2 we know that:

    If a ”shall” or ”shall not” requirement that appears outside of a constraint or runtime- constraint is violated, the behavior is undefined.

    The behavior is not defined, which means that anything can happen. Compiling is "anything".

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search