• GCC bug

    From Andrey Tarasevich@[email protected] to comp.lang.c on Wed Sep 10 06:51:49 2025
    From Newsgroup: comp.lang.c

    An attempt to use designated initializers to explicitly re-initialize a sub-member `y` of a struct member `b.a`

    #include <stdio.h>

    struct A { int x, y; };
    struct B { struct A a; };

    int main(void)
    {
    struct A ia = { 1, 2 };
    struct B b = { .a = ia, .a.y = 42 };
    printf("%d %d\n", b.a.x, b.a.y);
    }

    GCC outputs:

    0 42

    I.e. it does initialize `b.a.y` with `42`, but for some reason also
    produces zero in `b.a.x`. Meanwhile, Clang, MSVC output

    1 42

    as expected.

    The funny part is that this exact functionality is actually directly illustrated by the standard in "6.7.11 Initialization" example 12.

    Is it just a bug or is there some defiant reasoning (e.g. "we know
    better") for GCC's behavior?
    --
    Best regards,
    Andrey
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Michael S@[email protected] to comp.lang.c on Wed Sep 10 17:45:14 2025
    From Newsgroup: comp.lang.c

    On Wed, 10 Sep 2025 06:51:49 -0700
    Andrey Tarasevich <[email protected]> wrote:

    An attempt to use designated initializers to explicitly re-initialize
    a sub-member `y` of a struct member `b.a`

    #include <stdio.h>

    struct A { int x, y; };
    struct B { struct A a; };

    int main(void)
    {
    struct A ia = { 1, 2 };
    struct B b = { .a = ia, .a.y = 42 };
    printf("%d %d\n", b.a.x, b.a.y);
    }

    GCC outputs:

    0 42

    I.e. it does initialize `b.a.y` with `42`, but for some reason also
    produces zero in `b.a.x`. Meanwhile, Clang, MSVC output

    1 42

    as expected.


    Are you sure about MSVC?
    I tested both with VS2017 and with VS2022. Both printed '0 42'.
    The same for all versions of ICC* that I found on godbolt.

    For easier observation on Godbolt I reduced you code to following:

    struct A { int x, y; };
    struct B { struct A a; };

    struct B foo(void)
    {
    struct A ia = { 1, 2 };
    struct B b = { .a = ia, .a.y = 42 };
    return b;
    }



    * - not to be confused with ICX which is Intel's distro of clang/LLVM.



    The funny part is that this exact functionality is actually directly illustrated by the standard in "6.7.11 Initialization" example 12.

    Is it just a bug or is there some defiant reasoning (e.g. "we know
    better") for GCC's behavior?


    Try their bugzilla and please post the link to conversion here.
    I am grabbing the popcorn.


    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Andrey Tarasevich@[email protected] to comp.lang.c on Wed Sep 10 21:15:00 2025
    From Newsgroup: comp.lang.c

    On Wed 9/10/2025 7:45 AM, Michael S wrote:

    Are you sure about MSVC?
    I tested both with VS2017 and with VS2022. Both printed '0 42'.

    I simply tried it on whatever was currently installed on my machine,
    without giving it too much thought, and it printed `1 42` right away. It
    is VS2022.

    Just checked the version. It identifies itself as "Version 17.14.6 (June 2025)".
    --
    Best regards,
    Andrey





    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Damien Wyart@[email protected] to comp.lang.c on Thu Sep 11 09:36:38 2025
    From Newsgroup: comp.lang.c

    * Andrey Tarasevich <[email protected]> in comp.lang.c:
    [...]
    Is it just a bug or is there some defiant reasoning (e.g. "we know
    better") for GCC's behavior?

    Indeed, I found this which seems to confirm: https://stackoverflow.com/questions/40920714/is-full-followed-by-partial-initialization-of-a-subobject-undefined-behavior

    (but I could't find anything in gcc bugzilla)
    --
    DW
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Michael S@[email protected] to comp.lang.c on Thu Sep 11 11:57:41 2025
    From Newsgroup: comp.lang.c

    On Wed, 10 Sep 2025 21:15:00 -0700
    Andrey Tarasevich <[email protected]> wrote:

    On Wed 9/10/2025 7:45 AM, Michael S wrote:

    Are you sure about MSVC?
    I tested both with VS2017 and with VS2022. Both printed '0 42'.

    I simply tried it on whatever was currently installed on my machine,
    without giving it too much thought, and it printed `1 42` right away.
    It is VS2022.

    Just checked the version. It identifies itself as "Version 17.14.6
    (June 2025)".


    I tested several x64 version that I have on two computers on my desk.

    VS2013
    Microsoft (R) C/C++ Optimizing Compiler Version 18.00.31101 for x64


    VS2017
    Microsoft (R) C/C++ Optimizing Compiler Version 19.16.27054 for x64


    VS2022 LTSC 17.2
    Microsoft (R) C/C++ Optimizing Compiler Version 19.32.31342 for x64


    VS2022
    Microsoft (R) C/C++ Optimizing Compiler Version 19.44.35215 for x64

    All printed "0 42"
    With -W4 or -Wall they gave interesting warning:
    1.c(9): warning C4189: 'ia': local variable is initialized but not
    referenced


    I don't know if compiler in "Visual Studio 2022 Version 17.13.6" is
    newer or older than 19.44.35215. Microsoft's site does not report full
    compiler versions, so it seems that the only way to know is to open
    command prompt and run 'cl'. But my guess is that it is a little older.
    So, obviously, we are doing something differently.

    I compile from command line as following:
    cl -MD -Wall -O1 1.c

    My guess is that you compile from VS GUI and that your GUI configured
    to use clang/LLVM tools instead of MSVC.


    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Andrey Tarasevich@[email protected] to comp.lang.c on Thu Sep 11 05:11:03 2025
    From Newsgroup: comp.lang.c

    On Thu 9/11/2025 1:57 AM, Michael S wrote:

    My guess is that you compile from VS GUI and that your GUI configured
    to use clang/LLVM tools instead of MSVC.

    You are spot on. I'm sorry for misinformation.

    I don't normally use clang/LLVM toolchain under MSVC, but apparently I switched my scratch-test project to clang a while ago - just to test
    something - and forgot about it entirely.

    Indeed, MSVC toolchain suffers from the same issue as GCC: it also
    prints `0 42`.
    --
    Best regards,
    Andrey
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Andrey Tarasevich@[email protected] to comp.lang.c on Thu Sep 11 05:28:30 2025
    From Newsgroup: comp.lang.c

    On Thu 9/11/2025 12:36 AM, Damien Wyart wrote:
    * Andrey Tarasevich <[email protected]> in comp.lang.c:
    [...]
    Is it just a bug or is there some defiant reasoning (e.g. "we know
    better") for GCC's behavior?

    Indeed, I found this which seems to confirm: https://stackoverflow.com/questions/40920714/is-full-followed-by-partial-initialization-of-a-subobject-undefined-behavior

    (but I could't find anything in gcc bugzilla)

    Yes, that's the same issue.

    It appears that full `.a = ia` initializer leaves/resets `.a` member in
    some sort of pseudo-uninitilized state, which causes a subsequent `.a.y
    = 42` initializer to fully re-initialize `.a`.

    This version

    struct B b = { .a = ia, .a.y = 42, .a.x = 43 };
    printf("%d %d\n", b.a.x, b.a.y);

    outputs

    43 42

    indicating that `.a.x = 43` further down the line did not trigger full re-initialization of `.a`.

    Meanwhile, this version

    struct B b = { .a = ia, .a.y = 42, .a.x = 43, .a = ia, .a.x = 43 };
    printf("%d %d\n", b.a.x, b.a.y);

    outputs

    43 0

    i.e. the second `.a = ia` initializer "reset" this anomalous behavior.
    --
    Best regards,
    Andrey
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Andrey Tarasevich@[email protected] to comp.lang.c on Thu Sep 11 19:04:56 2025
    From Newsgroup: comp.lang.c

    On Wed 9/10/2025 6:51 AM, Andrey Tarasevich wrote:
    An attempt to use designated initializers to explicitly re-initialize a sub-member `y` of a struct member `b.a`

      #include <stdio.h>

      struct A { int x, y; };
      struct B { struct A a; };

      int main(void)
      {
        struct A ia = { 1, 2 };
        struct B b = { .a = ia, .a.y = 42 };
        printf("%d %d\n", b.a.x, b.a.y);
      }

    GCC outputs:

      0 42

    I.e. it does initialize `b.a.y` with `42`, but for some reason also
    produces zero in `b.a.x`. Meanwhile, Clang, MSVC output

      1 42

    as expected.


    An interesting detail is that the issue disappears when plain
    initializer list is used to initialize `.a`

    struct B b = { .a = { 1, 2 }, .a.y = 42 };
    printf("%d %d\n", b.a.x, b.a.y);
    // Outputs: 1 42

    With a compound literal as initializer, the bug reappears

    struct B b = { .a = (struct A) { 1, 2 }, .a.y = 42 };
    printf("%d %d\n", b.a.x, b.a.y);
    // Outputs: 0 42

    I.e. the issue reveals itself when initializer is represented by an
    object (named or unnamed), an lvalue.
    --
    Best regards,
    Andrey
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From Kaz Kylheku@[email protected] to comp.lang.c on Fri Sep 12 03:45:49 2025
    From Newsgroup: comp.lang.c

    On 2025-09-10, Andrey Tarasevich <[email protected]> wrote:
    An attempt to use designated initializers to explicitly re-initialize a sub-member `y` of a struct member `b.a`

    #include <stdio.h>

    struct A { int x, y; };
    struct B { struct A a; };

    int main(void)
    {
    struct A ia = { 1, 2 };
    struct B b = { .a = ia, .a.y = 42 };
    printf("%d %d\n", b.a.x, b.a.y);
    }

    GCC outputs:

    0 42

    I.e. it does initialize `b.a.y` with `42`, but for some reason also
    produces zero in `b.a.x`. Meanwhile, Clang, MSVC output

    1 42

    as expected.

    The funny part is that this exact functionality is actually directly illustrated by the standard in "6.7.11 Initialization" example 12.

    The reason given is "because implicit initialization does not override
    explicit initialization".

    If only the .a.y = 42 appeared, implicit initialization would
    set the other members of .a to zero.

    If the requirement is not correctly implemented that implicit
    initialization does not override explicit, then that could result in the behavior observed.

    The requirement is given in paragraph 20:

    The initialization shall occur in initializer list order, each
    initializer provided for a particular subobject overriding any
    previously listed initializer for the same subobject;(footnote 170) all
    subobjects that are not initialized explicitly are subject to default
    initialization.

    The last clause has to be interpreted as "all subobjects across
    the entire master object that are not initialized explicitly
    by any initializer are subject to default initialization".

    In other words, the initialization process first has to calculate what
    is explicitly initialized, and then perform a reckoning in which
    everything else is default initialized. (Or else, default-initialize everything first, and then perform the explicit initializations
    coming from the initializers.)

    Is it just a bug or is there some defiant reasoning (e.g. "we know
    better") for GCC's behavior?

    We can hypothesize that someone correctly implemented a certain
    interpretation of the requirement.

    Suppose we ignore example 12. Then, suppose we interpret the initializer
    .a.y as being an initializer for the entire subobject .a, but providing
    an explicit value for the .a.y sub-subobject.

    Then we could argue that GCC is conforming with the requirement "each initializer provided for a particular subobject overriding any
    previously listed initializer for the same subobject". I.e. .a.y means "initialize all of .a, (forgetting any prior initialization of .a), such
    that .a.y is explicitly initialized with the given value 42, and the
    rest of .a is default-initialized according ot the requirement "all
    subobjects that are not initialized explicitly are subject to default initialization'.

    When an initializer for a subobject is repeated, the iplementation is
    permitted to act as if the first initialization didn't happen and is
    even allowed not to evaluate the initializing expression! So
    that if we have:

    struct foo = { .a = i++, .a = j++ };

    it is permissible that only j is incremented and not i. Because
    the second .a replaces the initialization of the previous one,
    that initialization can completely vanish.

    (This is a very, very bad requirement; all expressions should be
    evaluated for their side effects, regardless of whether their values are
    used. Wouldn't happen in language design under my watch. Not in a
    language feature whose primary purpose isn't evaluation control!)

    Anyway, under this replacement paradigm, if the GCC implementor of these requirements believed that .a.y was a replacement initializer for all of
    .a, they would be justified in regarding the earlier .a initialization
    as not existing at all, so that, effectively, no part of .a is
    initialized, and .a.y = 42 handles the entire initialization, giving
    an explicit value to a subobject, such that the others are defaulted.

    Again, just a wild-assed bug hypothesis without looking at a shred
    of code or documentation.
    --
    TXR Programming Language: http://nongnu.org/txr
    Cygnal: Cygwin Native Application Library: http://kylheku.com/cygnal
    Mastodon: @[email protected]
    --- Synchronet 3.21a-Linux NewsLink 1.2