• Are designated initializer supposed to zero padding?

    From highcrew@high.crew3868@fastmail.com to comp.lang.c on Sun May 10 22:47:20 2026
    From Newsgroup: comp.lang.c

    Hello,

    I recently wrote a unit test, where I'm verifying that some array
    of data matches the expected value. The comparison is done by looping
    on individual items, and invoking a comparison function for each of
    them.

    struct item { long a, b; in c; };
    struct array { unsigned int n; struct item a[] }; // FAM

    const struct item expected[] = {
    {1, 2, 3},
    {4, 5, 6},
    };

    struct array *actual = get(); // using calloc under the hood
    assert(compare_array(expected, ARRSIZE(expected), actual) == 0);
    free(actual);

    Oh, and I cheated on the comparison, thinking I could get away with a
    memcmp():

    int compare(const struct item *a, const struct item *b)
    {
    return memcmp(a, b, sizeof *a);
    }

    This test worked as intended under Debian (gcc 14.2.0), and failed under
    Alpine Linux (gcc 15.2.0. But it works with clang).

    Why? Because the padding of course. It is OK in the `actual`
    array thanks to the fact I'm using `calloc()` under the hood, but it is
    not zero-initialized in `expected`, declared on the stack.
    The clean solution: don't cheat nor assume padding is zeroed out, and
    write a proper comparison function.

    ...

    But then I started to wonder: isn't a designated initializer
    supposed to wipe the memory? I definitely seen that happen, but
    this is of course not saying it *must* happen.

    I can't find anything in the standard (I checked N3220). I
    only found a mention of static and thread local objects, but
    nothing about objects declared on the stack.

    So, is the compiler only required to clear individual fields?
    Can it leave dirty memory on padding? Or did I hit a compiler
    bug?
    --
    High Crew

    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From James Kuyper@jameskuyper@alumni.caltech.edu to comp.lang.c on Sun May 10 19:15:22 2026
    From Newsgroup: comp.lang.c

    On 2026-05-10 16:47, highcrew wrote:
    Hello,

    I recently wrote a unit test, where I'm verifying that some array
    of data matches the expected value. The comparison is done by looping
    on individual items, and invoking a comparison function for each of
    them.

    struct item { long a, b; in c; };
    struct array { unsigned int n; struct item a[] }; // FAM

    const struct item expected[] = {
    {1, 2, 3},
    {4, 5, 6},
    };

    struct array *actual = get(); // using calloc under the hood
    assert(compare_array(expected, ARRSIZE(expected), actual) == 0);
    free(actual);

    Oh, and I cheated on the comparison, thinking I could get away with a memcmp():

    int compare(const struct item *a, const struct item *b)
    {
    return memcmp(a, b, sizeof *a);
    }

    This test worked as intended under Debian (gcc 14.2.0), and failed under Alpine Linux (gcc 15.2.0. But it works with clang).

    Why? Because the padding of course. It is OK in the `actual`
    array thanks to the fact I'm using `calloc()` under the hood, but it is
    not zero-initialized in `expected`, declared on the stack.
    The clean solution: don't cheat nor assume padding is zeroed out, and
    write a proper comparison function.

    ...

    But then I started to wonder: isn't a designated initializer
    supposed to wipe the memory?

    When you provide an initializer list,

    "... all subobjects that are not initialized explicitly are subject to
    default initialization." (6.7.21p23).

    When an object is default-initialized,

    "rCo if it is an aggregate, every member is initialized (recursively)
    according to these rules, and any padding is initialized to zero bits;
    rCo if it is a union, the first member that is not an unnamed bit-field is initialized (recursively) according to these rules, and any padding is initialized to zero bits." (6.7.21p15).

    While you do provide an initializer list, so those clauses should be applicable, you make no use of designated initializers in the code that
    you've shown. Why are you asking about them?

    There at two kinds of designated initializers. This provides an example
    of each, in the context of your code:

    const struct item designated[2] = {
    [1]={ 1, 2, 3},
    {.b=1}
    }

    The above is equivalent to:
    const struct item designated[2] = {
    { 0, 1, 0},
    { 1, 2, 3}
    }
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Tim Rentsch@tr.17687@z991.linuxsc.com to comp.lang.c on Sun May 10 20:01:53 2026
    From Newsgroup: comp.lang.c

    highcrew <high.crew3868@fastmail.com> writes:

    Hello,

    I recently wrote a unit test, where I'm verifying that some array
    of data matches the expected value. The comparison is done by looping
    on individual items, and invoking a comparison function for each of
    them.

    struct item { long a, b; in c; };
    struct array { unsigned int n; struct item a[] }; // FAM

    const struct item expected[] = {
    {1, 2, 3},
    {4, 5, 6},
    };

    struct array *actual = get(); // using calloc under the hood
    assert(compare_array(expected, ARRSIZE(expected), actual) == 0);
    free(actual);

    Oh, and I cheated on the comparison, thinking I could get away with a memcmp():

    int compare(const struct item *a, const struct item *b)
    {
    return memcmp(a, b, sizeof *a);
    }

    This test worked as intended under Debian (gcc 14.2.0), and failed under Alpine Linux (gcc 15.2.0. But it works with clang).

    Why? Because the padding of course. It is OK in the `actual`
    array thanks to the fact I'm using `calloc()` under the hood, but it is
    not zero-initialized in `expected`, declared on the stack.
    The clean solution: don't cheat nor assume padding is zeroed out, and
    write a proper comparison function.

    ...

    But then I started to wonder: isn't a designated initializer
    supposed to wipe the memory? I definitely seen that happen, but
    this is of course not saying it *must* happen.

    I can't find anything in the standard (I checked N3220). I
    only found a mention of static and thread local objects, but
    nothing about objects declared on the stack.

    So, is the compiler only required to clear individual fields?
    Can it leave dirty memory on padding? Or did I hit a compiler
    bug?

    You seem to be asking two different questions, one about padding
    and one about flexible array members. The example code is
    incomplete, so it's hard to be sure exactly what your questions
    are, but let me take a stab at being helpful.

    Point 1: initializers are not required to set padding (either
    padding bits or padding bytes). Don't expect padding to be
    zeroed. This statement applies to initializers in all forms -
    regular initializers, designated initializers, and compound
    literals.

    Point 2: flexible array members do not participate in any
    initialization. Usually it's a mistake to declare a struct with a
    flexible array member as an ordinary variable, but in any case there
    is no way to use a declaration of a struct-with-fam to initialize
    elements of the flexible array member.

    Point 3: if you want to initialize and zero a struct, along with
    elements of a non-trivial flexible array member, probably the best
    way to do that is as part of a union with an array of unsigned char,
    for example as follows:

    union {
    unsigned char uca[ SUITABLE_SIZE ];
    struct array fam;
    } blah = { .uca = { 0 } };

    struct array *stuff = &blah.fam;

    after which the variable 'stuff' can be used to refer to the zeroed struct-with-flexible-array-member. These two pieces can be combined
    into one, like so:

    struct array *stuff =
    &(union {
    unsigned char uca[ SUITABLE_SIZE ];
    struct array fam;
    }){ .uca = { 0 } }.fam;

    assuming I haven't made any mistakes in transcription.

    Admittedly this is ugly but maybe it can be used to accomplish
    what you want.
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From highcrew@high.crew3868@fastmail.com to comp.lang.c on Mon May 11 09:10:02 2026
    From Newsgroup: comp.lang.c

    Hello,

    On 5/11/26 5:01 AM, Tim Rentsch wrote:
    highcrew <high.crew3868@fastmail.com> writes:

    You seem to be asking two different questions, one about padding
    and one about flexible array members. The example code is
    incomplete, so it's hard to be sure exactly what your questions
    are, but let me take a stab at being helpful.

    Yes, perhaps I was a little confusing.
    The fact the "actual" array is a struct with FAM is orthogonal.
    The interesting bit is that the whole memory chunk is zeroed
    via calloc.

    ...yet it was lucky that I mentioned it, so you could show
    me that neat initialization of local variable variable with FAM.
    Thanks!

    Point 1: initializers are not required to set padding (either
    padding bits or padding bytes). Don't expect padding to be
    zeroed. This statement applies to initializers in all forms -
    regular initializers, designated initializers, and compound
    literals.

    OK, this is the confirmation that I was after.

    The problem has roots in the fact I've noticed that some compilers will
    just translate the initialization into a memset-zero or equivalent.
    But I could not find any requirement from the standard.

    tl;dr:
    This is of course not implying that the standard says so. memset-zero
    is just a reasonable way to initialize all fields.

    Thanks for confirming it.

    Point 3: if you want to initialize and zero a struct, along with
    elements of a non-trivial flexible array member, probably the best
    way to do that is as part of a union with an array of unsigned char,
    for example as follows:
    [...]

    struct array *stuff =
    &(union {
    unsigned char uca[ SUITABLE_SIZE ];
    struct array fam;
    }){ .uca = { 0 } }.fam;

    assuming I haven't made any mistakes in transcription.

    Admittedly this is ugly but maybe it can be used to accomplish
    what you want.
    I find that quite clean, as I said above.

    Thoughts:

    - I tried to experiment with using a struct instead of a union,
    by having the `struct array` first and an array of items afterwards,
    but the compiler doesn't allow that. I suppose because that would
    be a direct declaration of FAM struct, even if in my intention the
    following array of items is meant as a tail to it...
    So I understand the union is needed. Fair enough.[1]

    - If I got your idea correctly, the `uca` array is initialized
    by the `.uca = {0}` part and that is going to zero out the
    whole padding too, since it is an array of bytes.

    Thanks for sharing!


    [1] side note: I always find unions a bit ...shaky... since that day
    I discovered how different they are in C++. Now I don't use
    C++ these days, but it still feels uncomfortably wonky to use
    unions. I have been traumatized! :P
    --
    High Crew
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From highcrew@high.crew3868@fastmail.com to comp.lang.c on Mon May 11 09:11:23 2026
    From Newsgroup: comp.lang.c

    On 5/11/26 1:15 AM, James Kuyper wrote:
    When you provide an initializer list,

    "... all subobjects that are not initialized explicitly are subject to default initialization." (6.7.21p23).

    When an object is default-initialized,

    "rCo if it is an aggregate, every member is initialized (recursively) according to these rules, and any padding is initialized to zero bits;
    rCo if it is a union, the first member that is not an unnamed bit-field is initialized (recursively) according to these rules, and any padding is initialized to zero bits." (6.7.21p15).

    I've seen this section in the standard, and I thought I found what
    I was seeking for, but isn't it just referring to static and
    thread-local variables?
    --
    High Crew
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From scott@scott@slp53.sl.home (Scott Lurndal) to comp.lang.c on Mon May 11 15:34:13 2026
    From Newsgroup: comp.lang.c

    Tim Rentsch <tr.17687@z991.linuxsc.com> writes:
    highcrew <high.crew3868@fastmail.com> writes:

    Hello,



    Point 3: if you want to initialize and zero a struct, along with
    elements of a non-trivial flexible array member, probably the best
    way to do that is as part of a union with an array of unsigned char,
    for example as follows:

    union {
    unsigned char uca[ SUITABLE_SIZE ];
    struct array fam;
    } blah = { .uca = { 0 } };

    struct array *stuff = &blah.fam;

    Or use memset(3) with the appropriate length.


    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Michael S@already5chosen@yahoo.com to comp.lang.c on Mon May 11 23:22:47 2026
    From Newsgroup: comp.lang.c

    On Sun, 10 May 2026 20:01:53 -0700
    Tim Rentsch <tr.17687@z991.linuxsc.com> wrote:

    Point 1: initializers are not required to set padding (either
    padding bits or padding bytes). Don't expect padding to be
    zeroed. This statement applies to initializers in all forms -
    regular initializers, designated initializers, and compound
    literals.

    James Kuyper says that zeroing of padding is required by that standard.
    I am not an expert in lawyer-style reading of the standard, but at my
    level it looks that he is correct and the wording in unequivocal.
    For example, n3220, 6.7.11:
    11
    If an object that has automatic storage duration is not initialized
    explicitly, its representation is indeterminate. If an object that has
    static or thread storage duration is not initialized explicitly, or
    any object is initialized with an empty initializer, then it is subject
    to default initialization, which initializes an object as follows:
    rCo if it has pointer type, it is initialized to a null pointer;
    rCo if it has decimal floating type, it is initialized to positive zero,
    and the quantum exponent is implementation-defined;
    rCo if it has arithmetic type, and it does not have decimal floating
    type, it is initialized to (positive or unsigned) zero;
    rCo if it is an aggregate, every member is initialized (recursively)
    according to these rules, and any padding is initialized to zero bits;
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Keith Thompson@Keith.S.Thompson+u@gmail.com to comp.lang.c on Mon May 11 14:34:34 2026
    From Newsgroup: comp.lang.c

    Michael S <already5chosen@yahoo.com> writes:
    On Sun, 10 May 2026 20:01:53 -0700
    Tim Rentsch <tr.17687@z991.linuxsc.com> wrote:
    Point 1: initializers are not required to set padding (either
    padding bits or padding bytes). Don't expect padding to be
    zeroed. This statement applies to initializers in all forms -
    regular initializers, designated initializers, and compound
    literals.

    James Kuyper says that zeroing of padding is required by that standard.
    I am not an expert in lawyer-style reading of the standard, but at my
    level it looks that he is correct and the wording in unequivocal.
    For example, n3220, 6.7.11:

    11
    If an object that has automatic storage duration is not initialized explicitly, its representation is indeterminate. If an object that has
    static or thread storage duration is not initialized explicitly, or
    any object is initialized with an empty initializer, then it is subject
    to default initialization, which initializes an object as follows:
    rCo if it has pointer type, it is initialized to a null pointer;
    rCo if it has decimal floating type, it is initialized to positive zero,
    and the quantum exponent is implementation-defined;
    rCo if it has arithmetic type, and it does not have decimal floating
    type, it is initialized to (positive or unsigned) zero;
    rCo if it is an aggregate, every member is initialized (recursively) according to these rules, and any padding is initialized to zero bits;

    That applies only to objects with static storage duration *without*
    an initializer (or with an empty initializer {}, a new feature
    in C23). It doesn't imply that if there is an initializer, padding
    is zeroed.

    If you want to set *some* members to non-zero values and guarantee
    zero for other members and all-bits-zero for padding, there's
    no direct way to do it. You could initialize an object with {}
    (or {0} pre-C23) and then assign values to the desired members.
    --
    Keith Thompson (The_Other_Keith) Keith.S.Thompson+u@gmail.com
    void Void(void) { Void(); } /* The recursive call of the void */
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Michael S@already5chosen@yahoo.com to comp.lang.c on Tue May 12 00:55:24 2026
    From Newsgroup: comp.lang.c

    On Mon, 11 May 2026 14:34:34 -0700
    Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote:
    Michael S <already5chosen@yahoo.com> writes:
    On Sun, 10 May 2026 20:01:53 -0700
    Tim Rentsch <tr.17687@z991.linuxsc.com> wrote:
    Point 1: initializers are not required to set padding (either
    padding bits or padding bytes). Don't expect padding to be
    zeroed. This statement applies to initializers in all forms -
    regular initializers, designated initializers, and compound
    literals.

    James Kuyper says that zeroing of padding is required by that
    standard. I am not an expert in lawyer-style reading of the
    standard, but at my level it looks that he is correct and the
    wording in unequivocal. For example, n3220, 6.7.11:

    11
    If an object that has automatic storage duration is not initialized explicitly, its representation is indeterminate. If an object that
    has static or thread storage duration is not initialized
    explicitly, or any object is initialized with an empty initializer,
    then it is subject to default initialization, which initializes an
    object as follows: rCo if it has pointer type, it is initialized to a
    null pointer; rCo if it has decimal floating type, it is initialized
    to positive zero, and the quantum exponent is
    implementation-defined; rCo if it has arithmetic type, and it does
    not have decimal floating type, it is initialized to (positive or
    unsigned) zero; rCo if it is an aggregate, every member is
    initialized (recursively) according to these rules, and any padding
    is initialized to zero bits;

    That applies only to objects with static storage duration *without*
    an initializer (or with an empty initializer {}, a new feature
    in C23). It doesn't imply that if there is an initializer, padding
    is zeroed.

    If you want to set *some* members to non-zero values and guarantee
    zero for other members and all-bits-zero for padding, there's
    no direct way to do it. You could initialize an object with {}
    (or {0} pre-C23) and then assign values to the desired members.

    In this draft it is less clear. Let's look at N2310,6.7.9:
    On one hand, we have 10+19:
    If an object that has automatic storage duration is not initialized
    explicitly, its value is indeterminate.
    If an object that has static or thread storage duration is not
    initialized explicitly, then:
    rCo if it has pointer type, it is initialized to a null pointer;
    rCo if it has arithmetic type, it is initialized to (positive or
    unsigned) zero;
    rCo if it is an aggregate, every member is initialized (recursively)
    according to these rules, and any padding is initialized to zero bits;
    rCo if it is a union, the first named member is initialized (recursively) according to these rules, and any padding is initialized to zero bits;
    The initialization shall occur in initializer list order, each
    initializer provided for a particular subobject overriding any
    previously listed initializer for the same subobject) all subobjects
    that are not initialized explicitly shall be initialized implicitly the
    same as objects that have static storage duration.
    It sounds like zeroing of padding is required.
    On the other hand, we have 9:
    Except where explicitly stated otherwise, for the purposes of this
    subclause unnamed members of objects of structure and union type do not participate in initialization. Unnamed members of structure objects
    have indeterminate value even after initialization.
    It sounds like zeroing of padding is not required.
    It's quite confusing.
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Michael S@already5chosen@yahoo.com to comp.lang.c on Tue May 12 01:00:23 2026
    From Newsgroup: comp.lang.c

    On Sun, 10 May 2026 22:47:20 +0200
    highcrew <high.crew3868@fastmail.com> wrote:

    <snip>

    The question is interesting from theoretical perspective.
    However from practical perspective the answer is 'better safe than
    sorry'.
    But you probably know it.

    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Tim Rentsch@tr.17687@z991.linuxsc.com to comp.lang.c on Mon May 11 15:19:55 2026
    From Newsgroup: comp.lang.c

    Michael S <already5chosen@yahoo.com> writes:

    On Sun, 10 May 2026 20:01:53 -0700
    Tim Rentsch <tr.17687@z991.linuxsc.com> wrote:

    Point 1: initializers are not required to set padding (either
    padding bits or padding bytes). Don't expect padding to be
    zeroed. This statement applies to initializers in all forms -
    regular initializers, designated initializers, and compound
    literals.

    James Kuyper says that zeroing of padding is required by that standard.
    I am not an expert in lawyer-style reading of the standard, but at my
    level it looks that he is correct and the wording in unequivocal.
    For example, n3220, 6.7.11:

    11
    If an object that has automatic storage duration is not initialized explicitly, its representation is indeterminate. If an object that has static or thread storage duration is not initialized explicitly, or
    any object is initialized with an empty initializer, then it is subject
    to default initialization, which initializes an object as follows:
    ? if it has pointer type, it is initialized to a null pointer;
    ? if it has decimal floating type, it is initialized to positive zero,
    and the quantum exponent is implementation-defined;
    ? if it has arithmetic type, and it does not have decimal floating
    type, it is initialized to (positive or unsigned) zero;
    ? if it is an aggregate, every member is initialized (recursively)
    according to these rules, and any padding is initialized to zero bits;

    The problem is padding is none of those things. Padding

    (1) does not have pointer type;
    (2) does not have decimal floating type;
    (3) does not have arithmetic type;
    (4) is not a struct, a union, or an array;
    (5) is not a member of any struct or union;
    (6) is not an element of any array; and
    (7) is not an object, as the C standard uses the term.

    To see these are true, for 1 to 6, it's enough to note that
    all of them have a type. Padding does not have a type.

    For number 7, the C standard defines object as a region of storage,
    the contents of which can hold values. To hold a value, an object
    has to have a type. Padding does not have a type.

    We can see from other parts of the C standard that it doesn't
    consider padding to be objects. In 6.2.6.1 paragraph 6, the C
    standard says

    When a value is stored in an object of struct or union type,
    including in a member object, the bytes of the object
    representation that correspond to any padding bytes take
    unspecified values

    Note the wording used: the text doesn't talk about padding objects,
    it says "the bytes of the object representation that correspond to
    any padding bytes". The C standard doesn't consider the space
    occupied by padding to be an object, or a subobject of the struct or
    union surrounding it.

    For that matter, initializing a struct or a union stores a value
    in it. By the paragraph quoted above, any padding bytes take
    unspecified values. So not necessarily zero.

    Of course there is nothing stopping an implementation from setting
    all the padding bytes (and padding bits, if there are any) to zero;
    but the C standard does not require it.
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Tim Rentsch@tr.17687@z991.linuxsc.com to comp.lang.c on Mon May 11 15:27:11 2026
    From Newsgroup: comp.lang.c

    Michael S <already5chosen@yahoo.com> writes:

    On Mon, 11 May 2026 14:34:34 -0700
    Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote:

    Michael S <already5chosen@yahoo.com> writes:

    On Sun, 10 May 2026 20:01:53 -0700
    Tim Rentsch <tr.17687@z991.linuxsc.com> wrote:

    Point 1: initializers are not required to set padding (either
    padding bits or padding bytes). Don't expect padding to be
    zeroed. This statement applies to initializers in all forms -
    regular initializers, designated initializers, and compound
    literals.

    James Kuyper says that zeroing of padding is required by that
    standard. I am not an expert in lawyer-style reading of the
    standard, but at my level it looks that he is correct and the
    wording in unequivocal. For example, n3220, 6.7.11:

    11
    If an object that has automatic storage duration is not initialized
    explicitly, its representation is indeterminate. If an object that
    has static or thread storage duration is not initialized
    explicitly, or any object is initialized with an empty initializer,
    then it is subject to default initialization, which initializes an
    object as follows: ? if it has pointer type, it is initialized to a
    null pointer; ? if it has decimal floating type, it is initialized
    to positive zero, and the quantum exponent is
    implementation-defined; ? if it has arithmetic type, and it does
    not have decimal floating type, it is initialized to (positive or
    unsigned) zero; ? if it is an aggregate, every member is
    initialized (recursively) according to these rules, and any padding
    is initialized to zero bits;

    That applies only to objects with static storage duration *without*
    an initializer (or with an empty initializer {}, a new feature
    in C23). It doesn't imply that if there is an initializer, padding
    is zeroed.

    If you want to set *some* members to non-zero values and guarantee
    zero for other members and all-bits-zero for padding, there's
    no direct way to do it. You could initialize an object with {}
    (or {0} pre-C23) and then assign values to the desired members.

    In this draft it is less clear. Let's look at N2310,6.7.9:

    On one hand, we have 10+19:

    If an object that has automatic storage duration is not initialized explicitly, its value is indeterminate.
    If an object that has static or thread storage duration is not
    initialized explicitly, then:
    ? if it has pointer type, it is initialized to a null pointer;
    ? if it has arithmetic type, it is initialized to (positive or
    unsigned) zero;
    ? if it is an aggregate, every member is initialized (recursively)
    according to these rules, and any padding is initialized to zero bits;
    ? if it is a union, the first named member is initialized (recursively) according to these rules, and any padding is initialized to zero bits;


    The initialization shall occur in initializer list order, each
    initializer provided for a particular subobject overriding any
    previously listed initializer for the same subobject) all subobjects
    that are not initialized explicitly shall be initialized implicitly the
    same as objects that have static storage duration.

    It sounds like zeroing of padding is required.


    On the other hand, we have 9:
    Except where explicitly stated otherwise, for the purposes of this
    subclause unnamed members of objects of structure and union type do not participate in initialization. Unnamed members of structure objects
    have indeterminate value even after initialization.

    It sounds like zeroing of padding is not required.

    It's quite confusing.

    Padding and unnamed members are different things. All members,
    including unnamed members, have a type. Padding doesn't have
    a type.

    See also my other response.
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Keith Thompson@Keith.S.Thompson+u@gmail.com to comp.lang.c on Mon May 11 16:07:00 2026
    From Newsgroup: comp.lang.c

    Michael S <already5chosen@yahoo.com> writes:
    [...]
    In this draft it is less clear. Let's look at N2310,6.7.9:

    N2310 is a C2x working draft from 2018. N3220 is the newest publicly
    available C23 draft. (The C23 standard itself is expensive.)
    I'll refer to the latter in the following.

    https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3220.pdf

    On one hand, we have 10+19:

    If an object that has automatic storage duration is not initialized explicitly, its value is indeterminate.
    If an object that has static or thread storage duration is not
    initialized explicitly, then:
    rCo if it has pointer type, it is initialized to a null pointer;
    rCo if it has arithmetic type, it is initialized to (positive or
    unsigned) zero;
    rCo if it is an aggregate, every member is initialized (recursively) according to these rules, and any padding is initialized to zero bits;
    rCo if it is a union, the first named member is initialized (recursively) according to these rules, and any padding is initialized to zero bits;

    Right, all that is part of the definition of "default initialization".
    It applies to objects with static or thread storage duration with
    no initalizer, or to any object with an empty initializer, and
    in some other contexts.

    The initialization shall occur in initializer list order, each
    initializer provided for a particular subobject overriding any
    previously listed initializer for the same subobject) all subobjects
    that are not initialized explicitly shall be initialized implicitly the
    same as objects that have static storage duration.

    It sounds like zeroing of padding is required.

    N3220 changed this a bit. It says:

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

    Default initialization implies that padding is set to zero bits -- but
    it appears to apply to subobjects, not to the top-level object.
    My reading (which I suspect doesn't match the intent) is that, given:

    struct inner {
    char c_inner;
    // probably padding here
    int i;
    };
    struct outer {
    char c_outer;
    // probably padding here
    struct inner inn;
    };
    struct outer foo = { .c = '?' };

    foo.c_outer is of course set to '?'. foo.inn, because it's a
    subobject, has its padding set to zero bits; foo.inn.c_inner and
    foo.inn.i are set to 0 by default initialization. But I don't see
    a requirement for setting foo's padding to zero bits. (The padding
    is not a subobject.)

    It would make more sense to say that *all* padding is set to zero
    bits, but I don't see that stated in N3220.

    On the other hand, we have 9:
    Except where explicitly stated otherwise, for the purposes of this
    subclause unnamed members of objects of structure and union type do not participate in initialization. Unnamed members of structure objects
    have indeterminate value even after initialization.

    It sounds like zeroing of padding is not required.

    Maybe, but see the "Except where explicitly stated otherwise"
    clause. Unnamed members are subobjects, are subject to default
    initialization. As Tim points out, unnamed members and padding
    are two different things.

    It's quite confusing.

    Agreed.
    --
    Keith Thompson (The_Other_Keith) Keith.S.Thompson+u@gmail.com
    void Void(void) { Void(); } /* The recursive call of the void */
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Keith Thompson@Keith.S.Thompson+u@gmail.com to comp.lang.c on Mon May 11 16:10:27 2026
    From Newsgroup: comp.lang.c

    Tim Rentsch <tr.17687@z991.linuxsc.com> writes:
    Michael S <already5chosen@yahoo.com> writes:

    On Sun, 10 May 2026 20:01:53 -0700
    Tim Rentsch <tr.17687@z991.linuxsc.com> wrote:

    Point 1: initializers are not required to set padding (either
    padding bits or padding bytes). Don't expect padding to be
    zeroed. This statement applies to initializers in all forms -
    regular initializers, designated initializers, and compound
    literals.

    James Kuyper says that zeroing of padding is required by that standard.
    I am not an expert in lawyer-style reading of the standard, but at my
    level it looks that he is correct and the wording in unequivocal.
    For example, n3220, 6.7.11:

    11
    If an object that has automatic storage duration is not initialized
    explicitly, its representation is indeterminate. If an object that has
    static or thread storage duration is not initialized explicitly, or
    any object is initialized with an empty initializer, then it is subject
    to default initialization, which initializes an object as follows:
    ? if it has pointer type, it is initialized to a null pointer;
    ? if it has decimal floating type, it is initialized to positive zero,
    and the quantum exponent is implementation-defined;
    ? if it has arithmetic type, and it does not have decimal floating
    type, it is initialized to (positive or unsigned) zero;
    ? if it is an aggregate, every member is initialized (recursively)
    according to these rules, and any padding is initialized to zero bits;

    The problem is padding is none of those things.

    Um, padding is padding. "... and any padding is initialized to zero
    bits".

    As I wrote elsethread, it seems clear that padding within subobjects
    (except for automatic objects with no initializer) is set to zero bits.
    I haven't found wording that applies that to top-level padding.

    [...]
    --
    Keith Thompson (The_Other_Keith) Keith.S.Thompson+u@gmail.com
    void Void(void) { Void(); } /* The recursive call of the void */
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Tim Rentsch@tr.17687@z991.linuxsc.com to comp.lang.c on Mon May 11 18:13:41 2026
    From Newsgroup: comp.lang.c

    Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:

    Tim Rentsch <tr.17687@z991.linuxsc.com> writes:

    Michael S <already5chosen@yahoo.com> writes:

    On Sun, 10 May 2026 20:01:53 -0700
    Tim Rentsch <tr.17687@z991.linuxsc.com> wrote:

    Point 1: initializers are not required to set padding (either
    padding bits or padding bytes). Don't expect padding to be
    zeroed. This statement applies to initializers in all forms -
    regular initializers, designated initializers, and compound
    literals.

    James Kuyper says that zeroing of padding is required by that standard.
    I am not an expert in lawyer-style reading of the standard, but at my
    level it looks that he is correct and the wording in unequivocal.
    For example, n3220, 6.7.11:

    11
    If an object that has automatic storage duration is not initialized
    explicitly, its representation is indeterminate. If an object that has
    static or thread storage duration is not initialized explicitly, or
    any object is initialized with an empty initializer, then it is subject
    to default initialization, which initializes an object as follows:
    ? if it has pointer type, it is initialized to a null pointer;
    ? if it has decimal floating type, it is initialized to positive zero,
    and the quantum exponent is implementation-defined;
    ? if it has arithmetic type, and it does not have decimal floating
    type, it is initialized to (positive or unsigned) zero;
    ? if it is an aggregate, every member is initialized (recursively)
    according to these rules, and any padding is initialized to zero bits;

    The problem is padding is none of those things.

    Um, padding is padding. "... and any padding is initialized to zero
    bits".

    Sorry, I stand corrected. It looks like this change was made as
    part of C11. So in C99 padding is not initialized to zeros, and
    in C11 and later it is.

    As I wrote elsethread, it seems clear that padding within subobjects
    (except for automatic objects with no initializer) is set to zero bits.
    I haven't found wording that applies that to top-level padding.

    What does it mean to talk about top-level padding? Isn't it the
    case that padding (not counting padding bits in arithmetic types)
    occurs only in structs and unions? Can you give an example of a
    declaration where "top-level padding" occurs?

    I should add that I haven't yet read the other post where you
    talk about this.
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Tim Rentsch@tr.17687@z991.linuxsc.com to comp.lang.c on Mon May 11 18:15:50 2026
    From Newsgroup: comp.lang.c

    Michael S <already5chosen@yahoo.com> writes:

    On Sun, 10 May 2026 20:01:53 -0700
    Tim Rentsch <tr.17687@z991.linuxsc.com> wrote:

    Point 1: initializers are not required to set padding (either
    padding bits or padding bytes). Don't expect padding to be
    zeroed. This statement applies to initializers in all forms -
    regular initializers, designated initializers, and compound
    literals.

    James Kuyper says that zeroing of padding is required by that standard.
    I am not an expert in lawyer-style reading of the standard, but at my
    level it looks that he is correct and the wording in unequivocal.
    For example, n3220, 6.7.11:

    11
    If an object that has automatic storage duration is not initialized explicitly, its representation is indeterminate. If an object that has static or thread storage duration is not initialized explicitly, or
    any object is initialized with an empty initializer, then it is subject
    to default initialization, which initializes an object as follows:
    ? if it has pointer type, it is initialized to a null pointer;
    ? if it has decimal floating type, it is initialized to positive zero,
    and the quantum exponent is implementation-defined;
    ? if it has arithmetic type, and it does not have decimal floating
    type, it is initialized to (positive or unsigned) zero;
    ? if it is an aggregate, every member is initialized (recursively)
    according to these rules, and any padding is initialized to zero bits;

    Sorry, I had missed the crucial last part here, even after I had
    looked in multiple editions of the C standard. Mea culpa. Please
    see my response to Keith Thompson.
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Tim Rentsch@tr.17687@z991.linuxsc.com to comp.lang.c on Mon May 11 18:23:41 2026
    From Newsgroup: comp.lang.c

    scott@slp53.sl.home (Scott Lurndal) writes:

    Tim Rentsch <tr.17687@z991.linuxsc.com> writes:

    highcrew <high.crew3868@fastmail.com> writes:

    Hello,



    Point 3: if you want to initialize and zero a struct, along with
    elements of a non-trivial flexible array member, probably the best
    way to do that is as part of a union with an array of unsigned char,
    for example as follows:

    union {
    unsigned char uca[ SUITABLE_SIZE ];
    struct array fam;
    } blah = { .uca = { 0 } };

    struct array *stuff = &blah.fam;

    Or use memset(3) with the appropriate length.

    Part of the motivation of using a declarative and functional
    formulation is being able to use it in an expressional,
    rather than imperative, context. The code shown above can
    be used at file scope, which precludes the use of memset().
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Keith Thompson@Keith.S.Thompson+u@gmail.com to comp.lang.c on Mon May 11 18:28:38 2026
    From Newsgroup: comp.lang.c

    Tim Rentsch <tr.17687@z991.linuxsc.com> writes:
    Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:
    Tim Rentsch <tr.17687@z991.linuxsc.com> writes:
    Michael S <already5chosen@yahoo.com> writes:
    On Sun, 10 May 2026 20:01:53 -0700
    Tim Rentsch <tr.17687@z991.linuxsc.com> wrote:

    Point 1: initializers are not required to set padding (either
    padding bits or padding bytes). Don't expect padding to be
    zeroed. This statement applies to initializers in all forms -
    regular initializers, designated initializers, and compound
    literals.

    James Kuyper says that zeroing of padding is required by that standard. >>>> I am not an expert in lawyer-style reading of the standard, but at my
    level it looks that he is correct and the wording in unequivocal.
    For example, n3220, 6.7.11:

    11
    If an object that has automatic storage duration is not initialized
    explicitly, its representation is indeterminate. If an object that has >>>> static or thread storage duration is not initialized explicitly, or
    any object is initialized with an empty initializer, then it is subject >>>> to default initialization, which initializes an object as follows:
    ? if it has pointer type, it is initialized to a null pointer;
    ? if it has decimal floating type, it is initialized to positive zero, >>>> and the quantum exponent is implementation-defined;
    ? if it has arithmetic type, and it does not have decimal floating
    type, it is initialized to (positive or unsigned) zero;
    ? if it is an aggregate, every member is initialized (recursively)
    according to these rules, and any padding is initialized to zero bits;

    The problem is padding is none of those things.

    Um, padding is padding. "... and any padding is initialized to zero
    bits".

    Sorry, I stand corrected. It looks like this change was made as
    part of C11. So in C99 padding is not initialized to zeros, and
    in C11 and later it is.

    As I wrote elsethread, it seems clear that padding within subobjects
    (except for automatic objects with no initializer) is set to zero bits.
    I haven't found wording that applies that to top-level padding.

    What does it mean to talk about top-level padding? Isn't it the
    case that padding (not counting padding bits in arithmetic types)
    occurs only in structs and unions? Can you give an example of a
    declaration where "top-level padding" occurs?

    I should add that I haven't yet read the other post where you
    talk about this.

    By "top-level padding", I mean padding between members of the named
    object being initialized, as opposed to padding in subobjects.
    (Similar considerations apply to unions.)

    In my example in the other post, I have an outer struct with an
    inner struct as one of its members, and an object defined with an
    initializer that doesn't explicitly initialize all its members.
    I can derive from the wording in N3220 that any padding between
    members of the inner struct are set to zero bits, but not that this
    is done for padding between members in the outer struct.
    --
    Keith Thompson (The_Other_Keith) Keith.S.Thompson+u@gmail.com
    void Void(void) { Void(); } /* The recursive call of the void */
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Tim Rentsch@tr.17687@z991.linuxsc.com to comp.lang.c on Mon May 11 21:59:29 2026
    From Newsgroup: comp.lang.c

    Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:

    Tim Rentsch <tr.17687@z991.linuxsc.com> writes:

    Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:

    Tim Rentsch <tr.17687@z991.linuxsc.com> writes:

    Michael S <already5chosen@yahoo.com> writes:

    On Sun, 10 May 2026 20:01:53 -0700
    Tim Rentsch <tr.17687@z991.linuxsc.com> wrote:

    Point 1: initializers are not required to set padding (either
    padding bits or padding bytes). Don't expect padding to be
    zeroed. This statement applies to initializers in all forms -
    regular initializers, designated initializers, and compound
    literals.

    James Kuyper says that zeroing of padding is required by that standard. >>>>> I am not an expert in lawyer-style reading of the standard, but at my >>>>> level it looks that he is correct and the wording in unequivocal.
    For example, n3220, 6.7.11:

    11
    If an object that has automatic storage duration is not initialized
    explicitly, its representation is indeterminate. If an object that has >>>>> static or thread storage duration is not initialized explicitly, or
    any object is initialized with an empty initializer, then it is subject >>>>> to default initialization, which initializes an object as follows:
    ? if it has pointer type, it is initialized to a null pointer;
    ? if it has decimal floating type, it is initialized to positive zero, >>>>> and the quantum exponent is implementation-defined;
    ? if it has arithmetic type, and it does not have decimal floating
    type, it is initialized to (positive or unsigned) zero;
    ? if it is an aggregate, every member is initialized (recursively)
    according to these rules, and any padding is initialized to zero bits; >>>>
    The problem is padding is none of those things.

    Um, padding is padding. "... and any padding is initialized to zero
    bits".

    Sorry, I stand corrected. It looks like this change was made as
    part of C11. So in C99 padding is not initialized to zeros, and
    in C11 and later it is.

    As I wrote elsethread, it seems clear that padding within subobjects
    (except for automatic objects with no initializer) is set to zero bits.
    I haven't found wording that applies that to top-level padding.

    What does it mean to talk about top-level padding? Isn't it the
    case that padding (not counting padding bits in arithmetic types)
    occurs only in structs and unions? Can you give an example of a
    declaration where "top-level padding" occurs?

    I should add that I haven't yet read the other post where you
    talk about this.

    By "top-level padding", I mean padding between members of the named
    object being initialized, as opposed to padding in subobjects.
    (Similar considerations apply to unions.)

    In my example in the other post, I have an outer struct with an
    inner struct as one of its members, and an object defined with an
    initializer that doesn't explicitly initialize all its members.
    I can derive from the wording in N3220 that any padding between
    members of the inner struct are set to zero bits, but not that this
    is done for padding between members in the outer struct.

    Okay, I understand now what you're asking about. Just a couple of
    small detours before getting to your question.

    First, I have confirmed that the change to the C standard about
    zeroing padding bits was made in C11, and was not present in C99.

    Second, I agree with your conclusion that a contained struct or
    union that has not been initialized explicitly must have any
    padding set to all zero bits.

    Third, as best I can determine, the rules under N3220 have the same
    meaning as the corresponding rules in C11, of course modulo some
    parts of C23 that were added after C11 and are not part of N1570.

    Now for the question. Given this type definition:

    typedef struct { struct { char c; short s; } inner; long k; } Outer;

    and a subsequent declaration

    Outer outer = { .k = 1 };

    we agree that any padding in outer.inner will have been initialized
    to all zero bits, but what about padding in outer (and outside of
    outer.inner)?

    My reading of N3220, which coincides with my reading of N1570, is
    that the bytes of the object representation of outer that correspond
    to padding bytes (of outer) take unspecified values. That result is
    consistent with 6.2.6.1 paragraph 6, and I don't seen anything in
    N3220's 6.7.11 (which is 6.7.9 in N1570) that overrides that or that contradicts it.

    I don't see anything in either standard that talks about padding
    bits (as opposed to padding bytes) that occur as the result of
    bitfields. Presumbly padding bits (in structs and unions) get the
    same treatment as padding bytes: set to zero if their containing
    struct or union is not initialized, and given unspecified values if
    any member is initialized.

    In C23, my understanding is "default initialization" is the same as
    what initialization is done for static objects with no initializer;
    which is to say, the declared (sub)object is initialized, but not
    with an explicit initializer (and thus padding in structs or unions
    with a default initializer must be set to zero).

    I haven't tried to answer the question of what happens when a struct
    or union is initialized as a whole, by assignment. For example, if
    we have

    typedef struct { struct { char c; short s; } inner; long k; } Outer;
    Outer foo = (Outer){ .k = 1 };

    I haven't tried to find out whether foo.inner might be allowed to
    have non-zero padding bytes. I think an argument could be made
    either way, but I haven't spent any time trying to really track it
    down. In C99 it seems clear that any padding is unspecified, so
    erring on the side of caution it's probably best to assume that.

    Thank you for the correction upstream about rules for initializing
    padding.
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From David Brown@david.brown@hesbynett.no to comp.lang.c on Tue May 12 09:15:35 2026
    From Newsgroup: comp.lang.c

    On 12/05/2026 06:59, Tim Rentsch wrote:
    Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:

    Tim Rentsch <tr.17687@z991.linuxsc.com> writes:

    Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:

    Tim Rentsch <tr.17687@z991.linuxsc.com> writes:

    Michael S <already5chosen@yahoo.com> writes:

    <snip>
    11
    If an object that has automatic storage duration is not initialized >>>>>> explicitly, its representation is indeterminate. If an object that has >>>>>> static or thread storage duration is not initialized explicitly, or >>>>>> any object is initialized with an empty initializer, then it is subject >>>>>> to default initialization, which initializes an object as follows: >>>>>> ? if it has pointer type, it is initialized to a null pointer;
    ? if it has decimal floating type, it is initialized to positive zero, >>>>>> and the quantum exponent is implementation-defined;
    ? if it has arithmetic type, and it does not have decimal floating >>>>>> type, it is initialized to (positive or unsigned) zero;
    ? if it is an aggregate, every member is initialized (recursively) >>>>>> according to these rules, and any padding is initialized to zero bits; >>>>>
    The problem is padding is none of those things.

    Um, padding is padding. "... and any padding is initialized to zero
    bits".

    Sorry, I stand corrected. It looks like this change was made as
    part of C11. So in C99 padding is not initialized to zeros, and
    in C11 and later it is.

    <snip>
    Okay, I understand now what you're asking about. Just a couple of
    small detours before getting to your question.

    First, I have confirmed that the change to the C standard about
    zeroing padding bits was made in C11, and was not present in C99.

    I note that there is nothing in the list of "major" changes in C11 that reference this change. That may mean that the standards committee saw
    this as a change of wording for clarification, rather than a change in specified behaviour. Or it may just mean they did not consider it
    important enough to get a line by itself - perhaps it considered part of
    the standardisation of anonymous structures and unions (which does make
    the list of major changes).


    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Keith Thompson@Keith.S.Thompson+u@gmail.com to comp.lang.c on Tue May 12 00:27:11 2026
    From Newsgroup: comp.lang.c

    Tim Rentsch <tr.17687@z991.linuxsc.com> writes:
    Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:
    [...]
    In my example in the other post, I have an outer struct with an
    inner struct as one of its members, and an object defined with an
    initializer that doesn't explicitly initialize all its members.
    I can derive from the wording in N3220 that any padding between
    members of the inner struct are set to zero bits, but not that this
    is done for padding between members in the outer struct.

    Okay, I understand now what you're asking about. Just a couple of
    small detours before getting to your question.

    First, I have confirmed that the change to the C standard about
    zeroing padding bits was made in C11, and was not present in C99.

    Second, I agree with your conclusion that a contained struct or
    union that has not been initialized explicitly must have any
    padding set to all zero bits.

    Third, as best I can determine, the rules under N3220 have the same
    meaning as the corresponding rules in C11, of course modulo some
    parts of C23 that were added after C11 and are not part of N1570.

    Now for the question. Given this type definition:

    typedef struct { struct { char c; short s; } inner; long k; } Outer;

    and a subsequent declaration

    Outer outer = { .k = 1 };

    we agree that any padding in outer.inner will have been initialized
    to all zero bits, but what about padding in outer (and outside of outer.inner)?

    My reading of N3220, which coincides with my reading of N1570, is
    that the bytes of the object representation of outer that correspond
    to padding bytes (of outer) take unspecified values. That result is consistent with 6.2.6.1 paragraph 6, and I don't seen anything in
    N3220's 6.7.11 (which is 6.7.9 in N1570) that overrides that or that contradicts it.

    I don't see anything in either standard that talks about padding
    bits (as opposed to padding bytes) that occur as the result of
    bitfields. Presumbly padding bits (in structs and unions) get the
    same treatment as padding bytes: set to zero if their containing
    struct or union is not initialized, and given unspecified values if
    any member is initialized.

    The standard uses the term "padding bits" to mostly refer to bits
    within an integer representation that do not contribute to its value.
    I speculate that that section did not intend to talk about how those
    bits are initialized. But the term could also apply to extra bits
    between bit-fields.

    I have mixed feelings about the fact that you agree with my
    interpretation, that padding (bytes) is set to zero in nested
    structs but not necessarily in an outermost struct. I speculate
    that that was unintentional. I can't think of any good reason
    to have such a rule intentionally.

    [...]

    Thank you for the correction upstream about rules for initializing
    padding.

    My pleasure.
    --
    Keith Thompson (The_Other_Keith) Keith.S.Thompson+u@gmail.com
    void Void(void) { Void(); } /* The recursive call of the void */
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Tim Rentsch@tr.17687@z991.linuxsc.com to comp.lang.c on Tue May 12 06:44:19 2026
    From Newsgroup: comp.lang.c

    Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:

    Tim Rentsch <tr.17687@z991.linuxsc.com> writes:

    Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:

    [...]

    In my example in the other post, I have an outer struct with an
    inner struct as one of its members, and an object defined with an
    initializer that doesn't explicitly initialize all its members.
    I can derive from the wording in N3220 that any padding between
    members of the inner struct are set to zero bits, but not that this
    is done for padding between members in the outer struct.

    Okay, I understand now what you're asking about. Just a couple of
    small detours before getting to your question.

    First, I have confirmed that the change to the C standard about
    zeroing padding bits was made in C11, and was not present in C99.

    Second, I agree with your conclusion that a contained struct or
    union that has not been initialized explicitly must have any
    padding set to all zero bits.

    Third, as best I can determine, the rules under N3220 have the same
    meaning as the corresponding rules in C11, of course modulo some
    parts of C23 that were added after C11 and are not part of N1570.

    Now for the question. Given this type definition:

    typedef struct { struct { char c; short s; } inner; long k; } Outer;

    and a subsequent declaration

    Outer outer = { .k = 1 };

    we agree that any padding in outer.inner will have been initialized
    to all zero bits, but what about padding in outer (and outside of
    outer.inner)?

    My reading of N3220, which coincides with my reading of N1570, is
    that the bytes of the object representation of outer that correspond
    to padding bytes (of outer) take unspecified values. That result is
    consistent with 6.2.6.1 paragraph 6, and I don't seen anything in
    N3220's 6.7.11 (which is 6.7.9 in N1570) that overrides that or that
    contradicts it.

    I don't see anything in either standard that talks about padding
    bits (as opposed to padding bytes) that occur as the result of
    bitfields. Presumbly padding bits (in structs and unions) get the
    same treatment as padding bytes: set to zero if their containing
    struct or union is not initialized, and given unspecified values if
    any member is initialized.

    The standard uses the term "padding bits" to mostly refer to bits
    within an integer representation that do not contribute to its value.
    I speculate that that section did not intend to talk about how those
    bits are initialized. But the term could also apply to extra bits
    between bit-fields.

    The phrase "padding bits" is used in connection both with struct and
    union types and with integer types. Where the C standard says,
    about struct and union types, that "any padding is initialized to
    zero bits", presumably that is meant to apply both to padding caused
    by ordinary members and to padding caused by bit-field members. I
    see no reason to suppose it applies only to one and not the other.

    I have mixed feelings about the fact that you agree with my
    interpretation, that padding (bytes) is set to zero in nested
    structs but not necessarily in an outermost struct. I speculate
    that that was unintentional. I can't think of any good reason
    to have such a rule intentionally.

    For the same reason that assigning to a struct or union member gives
    padding unspecified values. Initializing individual members is very
    much like a series of assignments to those members; the semantics
    are more symmetric if assignment and initialization are treated the
    same way in the two situations. Conversely, if the rules were not
    the same in the two cases, the language would be less consistent.
    Obviously, unless there is some specific reason not to be, being
    more consistent it better. Initializing individual members is like
    assignment; default initialization is not like assignment. It is
    because of this difference that it makes sense to treat the two
    cases differently.
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Tim Rentsch@tr.17687@z991.linuxsc.com to comp.lang.c on Tue May 12 09:36:41 2026
    From Newsgroup: comp.lang.c

    highcrew <high.crew3868@fastmail.com> writes:

    Hello,

    On 5/11/26 5:01 AM, Tim Rentsch wrote:

    highcrew <high.crew3868@fastmail.com> writes:

    You seem to be asking two different questions, one about padding
    and one about flexible array members. The example code is
    incomplete, so it's hard to be sure exactly what your questions
    are, but let me take a stab at being helpful.

    Yes, perhaps I was a little confusing.
    The fact the "actual" array is a struct with FAM is orthogonal.
    The interesting bit is that the whole memory chunk is zeroed
    via calloc.

    ...yet it was lucky that I mentioned it, so you could show
    me that neat initialization of local variable variable with FAM.

    Point 1: initializers are not required to set padding (either
    padding bits or padding bytes). Don't expect padding to be
    zeroed. This statement applies to initializers in all forms -
    regular initializers, designated initializers, and compound
    literals.

    OK, this is the confirmation that I was after.

    The problem has roots in the fact I've noticed that some compilers will
    just translate the initialization into a memset-zero or equivalent.
    But I could not find any requirement from the standard.

    tl;dr:
    This is of course not implying that the standard says so. memset-zero
    is just a reasonable way to initialize all fields.

    Thanks for confirming it.

    I'm sorry for my earlier bad answer. It turns out that the rule
    changed between C99 and C11. In C99, a static struct with no
    initializer had its padding bits set to unspecified values. In
    C11 and later, the same situation DOES set padding bits to zero.
    There are some subtleties when initializing a struct when one
    of its members has an initializer, and I won't try to describe
    those here. If you are interested, please read my responses to
    Keith Thompson's posts. (Again my thanks to Keith for pointing
    out a key passage that I missed.)

    Point 3: if you want to initialize and zero a struct, along with
    elements of a non-trivial flexible array member, probably the best
    way to do that is as part of a union with an array of unsigned char,
    for example as follows:
    [...]

    struct array *stuff =
    &(union {
    unsigned char uca[ SUITABLE_SIZE ];
    struct array fam;
    }){ .uca = { 0 } }.fam;

    assuming I haven't made any mistakes in transcription.

    Admittedly this is ugly but maybe it can be used to accomplish
    what you want.

    I find that quite clean, as I said above.

    Thoughts:

    - I tried to experiment with using a struct instead of a union,
    by having the `struct array` first and an array of items afterwards,
    but the compiler doesn't allow that. I suppose because that would
    be a direct declaration of FAM struct, even if in my intention the
    following array of items is meant as a tail to it...
    So I understand the union is needed. Fair enough.[1]

    Yeah, it's important to use a union.

    - If I got your idea correctly, the `uca` array is initialized
    by the `.uca = {0}` part and that is going to zero out the
    whole padding too, since it is an array of bytes.

    Exactly right.

    After posting it occurred to me it would be good to wrap this
    pattern in a macro. Here is a full example:

    //// start here ////

    /* macros to help with declaring structs with flexible array
    members at file scope or in expressional contexts.
    */

    typedef struct {
    unsigned n; // size of array
    char s[]; // the flexible array member
    } ExampleFAMstruct;


    /* the macros */

    #define FAM_STRUCT_POINTER( T, id, fa_member, n, ucaname, structname ) \
    T *id = &FAM_STRUCT( T, fa_member, n, ucaname, structname )

    #define FAM_STRUCT( T, fa_member, n, ucaname, structname ) ( \
    (union { \
    unsigned char ucaname[ FAM_STRUCT_SIZE( T, fa_member, n ) ]; \
    T structname; \
    }){ .ucaname = { 0 } }.structname \
    )

    #define FAM_STRUCT_SIZE( T, fa_member, n ) ( \
    sizeof (T) + (n) * sizeof ((T*)0)->fa_member[0] \
    )


    /* an example use */

    FAM_STRUCT_POINTER( ExampleFAMstruct, file_scope_p, s, 100, uca, the_struct );


    #include <stdio.h>

    int
    main(){
    /* a second example use */
    FAM_STRUCT_POINTER( ExampleFAMstruct, p, s, 20, uca, the_struct );
    printf( " the pointer is %p\n", (void*)p );
    printf( "the file scope pointer is %p\n", (void*)file_scope_p );
    }

    //// end here ////

    If anyone is curious, the reason for the ucaname and structname macro parameters it to avoid accidental collisions (even if they might be
    unlikely) with names used elsewhere. It should be easy to change the
    macro definitions to take them out if they aren't wanted.


    Thanks for sharing!


    [1] side note: I always find unions a bit ...shaky... since that day
    I discovered how different they are in C++. Now I don't use
    C++ these days, but it still feels uncomfortably wonky to use
    unions. I have been traumatized! :P

    Here is my rule of thumb for unions:

    In C, unions work.
    In C++, they don't.

    Probably that isn't exactly right, but it's a good first
    approximation.
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From James Kuyper@jameskuyper@alumni.caltech.edu to comp.lang.c on Tue May 12 16:02:19 2026
    From Newsgroup: comp.lang.c

    On 2026-05-11 03:11, highcrew wrote:
    On 5/11/26 1:15 AM, James Kuyper wrote:
    When you provide an initializer list,

    "... all subobjects that are not initialized explicitly are subject to
    default initialization." (6.7.21p23).

    That citation has a typo, it should have been 6.7.11p23.

    When an object is default-initialized,

    "rCo if it is an aggregate, every member is initialized (recursively)
    according to these rules, and any padding is initialized to zero bits;
    rCo if it is a union, the first member that is not an unnamed bit-field is >> initialized (recursively) according to these rules, and any padding is
    initialized to zero bits." (6.7.21p15).

    And that one should have been 6.7.11p14

    I've seen this section in the standard, and I thought I found what
    I was seeking for, but isn't it just referring to static and
    thread-local variables?

    No, earlier in the same section it says quite explicitly: "... If an
    object that has static or thread storage duration is not initialized explicitly, or any object is initialized with an empty initializer,
    ...". Objects with automatic storage duration are still covered by the
    second clause of that sentence.


    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Keith Thompson@Keith.S.Thompson+u@gmail.com to comp.lang.c on Tue May 12 21:10:03 2026
    From Newsgroup: comp.lang.c

    James Kuyper <jameskuyper@alumni.caltech.edu> writes:
    On 2026-05-11 03:11, highcrew wrote:
    [...]
    I've seen this section in the standard, and I thought I found what
    I was seeking for, but isn't it just referring to static and
    thread-local variables?

    No, earlier in the same section it says quite explicitly: "... If an
    object that has static or thread storage duration is not initialized explicitly, or any object is initialized with an empty initializer,
    ...". Objects with automatic storage duration are still covered by the
    second clause of that sentence.

    Yes, but only automatic storage duration objects with an empty
    initializer. An "empty initializer" is `{}`, which wasn't even
    legal pre-C23. `{0}` is often used to (try to) achieve the same
    effect; it specifies a value of 0 for the (recursively) first scalar
    subobject, and leaves everything else to be default-initialized.

    I have a feeling that there's something inconsistent about the
    C23 rules. I'm working on nailing down the specifics.

    As a programmer, perhaps the best approach is to arrange things so
    you don't have to care about what's stored in padding. That might
    mean that you can't use memcmp() to confirm that a struct has the
    right value; you have to compare member by member.

    [...]
    --
    Keith Thompson (The_Other_Keith) Keith.S.Thompson+u@gmail.com
    void Void(void) { Void(); } /* The recursive call of the void */
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Tim Rentsch@tr.17687@z991.linuxsc.com to comp.lang.c on Wed May 13 15:51:33 2026
    From Newsgroup: comp.lang.c

    Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:

    James Kuyper <jameskuyper@alumni.caltech.edu> writes:

    On 2026-05-11 03:11, highcrew wrote:

    [...]

    I've seen this section in the standard, and I thought I found what
    I was seeking for, but isn't it just referring to static and
    thread-local variables?

    No, earlier in the same section it says quite explicitly: "... If an
    object that has static or thread storage duration is not initialized
    explicitly, or any object is initialized with an empty initializer,
    ...". Objects with automatic storage duration are still covered by the
    second clause of that sentence.

    Yes, but only automatic storage duration objects with an empty
    initializer. An "empty initializer" is `{}`, which wasn't even
    legal pre-C23. `{0}` is often used to (try to) achieve the same
    effect; it specifies a value of 0 for the (recursively) first scalar subobject, and leaves everything else to be default-initialized.

    I have a feeling that there's something inconsistent about the
    C23 rules. I'm working on nailing down the specifics.

    As a programmer, perhaps the best approach is to arrange things so
    you don't have to care about what's stored in padding. That might
    mean that you can't use memcmp() to confirm that a struct has the
    right value; you have to compare member by member.

    My preference would be for a compiler option (eg -fzero-padding) or
    a pragma (eg #pragma ZERO_PADDING=ON) or perhaps both. Either of
    those has advantages over changing the language definition, not the
    least of which is they can be retrofitted to earlier versions of C
    without needing to break conformance.
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From David Brown@david.brown@hesbynett.no to comp.lang.c on Thu May 14 11:36:10 2026
    From Newsgroup: comp.lang.c

    On 14/05/2026 00:51, Tim Rentsch wrote:
    Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:

    James Kuyper <jameskuyper@alumni.caltech.edu> writes:

    On 2026-05-11 03:11, highcrew wrote:

    [...]

    I've seen this section in the standard, and I thought I found what
    I was seeking for, but isn't it just referring to static and
    thread-local variables?

    No, earlier in the same section it says quite explicitly: "... If an
    object that has static or thread storage duration is not initialized
    explicitly, or any object is initialized with an empty initializer,
    ...". Objects with automatic storage duration are still covered by the
    second clause of that sentence.

    Yes, but only automatic storage duration objects with an empty
    initializer. An "empty initializer" is `{}`, which wasn't even
    legal pre-C23. `{0}` is often used to (try to) achieve the same
    effect; it specifies a value of 0 for the (recursively) first scalar
    subobject, and leaves everything else to be default-initialized.

    I have a feeling that there's something inconsistent about the
    C23 rules. I'm working on nailing down the specifics.

    As a programmer, perhaps the best approach is to arrange things so
    you don't have to care about what's stored in padding. That might
    mean that you can't use memcmp() to confirm that a struct has the
    right value; you have to compare member by member.

    My preference would be for a compiler option (eg -fzero-padding) or
    a pragma (eg #pragma ZERO_PADDING=ON) or perhaps both. Either of
    those has advantages over changing the language definition, not the
    least of which is they can be retrofitted to earlier versions of C
    without needing to break conformance.

    I'd prefer any changes or improvements to be in the standards, and also
    for compilers to have flags supporting the feature in previous C
    standards. There's no reason not to have both - especially when AFAICS providing more situations where padding is zeroed cannot harm correct
    code (though it could conceivably affect efficiency).

    I have a feeling that I read somewhere about a proposed standard library function that could be used to zero all padding bits and bytes in a
    struct. Unfortunately, I can't remember where I saw it - and there is a
    fair chance it was a C++ proposal rather than a C proposal.

    Anyway, for Tim's benefit (assuming he reads my posts), gcc 15 has a "-fzero-init-padding-bits" flag. From the list of changes for gcc 15:

    """
    {0} initializer in C or C++ for unions no longer guarantees clearing of
    the whole union (except for static storage duration initialization), it
    just initializes the first union member to zero. If initialization of
    the whole union including padding bits is desirable, use {} (valid in
    C23 or C++) or use -fzero-init-padding-bits=unions option to restore the
    old GCC behavior.
    """


    And from the manual:


    """
    -fzero-init-padding-bits=value

    Guarantee zero initialization of padding bits in automatic variable initializers. Certain languages guarantee zero initialization of padding
    bits in certain cases, e.g. C23 when using empty initializers ({}), or
    C++ when using zero-initialization or C guarantees that fields not
    specified in an initializer have their padding bits zero initialized.
    This option allows to change when padding bits in initializers are
    guaranteed to be zero initialized. The default is -fzero-init-padding-bits=standard, which makes no further guarantees
    than the corresponding standard. E.g.

    struct A { char a; unsigned long long b; char c; };
    union B { char a; unsigned long long b; };
    struct A a = {}; // C23 guarantees padding bits are zero.
    struct A b = { 1, 2, 3 }; // No guarantees.
    union B c = {}; // C23 guarantees padding bits are zero.
    union B d = { 1 }; // No guarantees.

    -fzero-init-padding-bits=unions guarantees zero initialization of
    padding bits in unions on top of what the standards guarantee, if the initializer of an union is empty (then all bits of the union are zero initialized) or if the initialized member of the union is smaller than
    the size of the union (in that case guarantees padding bits outside of
    the initialized member to be zero initialized). This was the GCC
    behavior before GCC 15 and in the above example guarantees zero
    initialization of last sizeof (unsigned long long) - 1 bytes in the union.

    -fzero-init-padding-bits=all guarantees additionally zero
    initialization of padding bits of other aggregates, so the padding in
    between b.a and b.b (if any) and tail padding in the structure (if any).
    """



    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Richard Harnden@richard.nospam@gmail.invalid to comp.lang.c on Thu May 14 11:47:54 2026
    From Newsgroup: comp.lang.c

    On 14/05/2026 10:36, David Brown wrote:
    """
    {0} initializer in C or C++ for unions no longer guarantees clearing of
    the whole union (except for static storage duration initialization), it
    just initializes the first union member to zero. If initialization of
    the whole union including padding bits is desirable, use {} (valid in
    C23 or C++) or use -fzero-init-padding-bits=unions option to restore the
    old GCC behavior.
    """

    "except for static storage duration initialization"

    Does that guarentee that this will always be zeroed, even for padding,
    and even if NULL is not all-bits-zero ... ?

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>

    struct foo
    {
    int a;
    unsigned long b;
    char c;
    void *d;
    double e;
    };

    static struct foo FOO_INIT;

    int main(void)
    {
    struct foo *foo = malloc(sizeof *foo);
    unsigned char *x = (unsigned char *) foo;

    memcpy(foo, &FOO_INIT, sizeof *foo);

    printf("foo->a = %d\n", foo->a);
    printf("foo->b = %lu\n", foo->b);
    printf("foo->c = '%c'\n", foo->c);
    printf("foo->d = %p\n", foo->d);
    printf("foo->e = %f\n", foo->e);

    for (size_t i=0; i<sizeof *foo; i++)
    printf("%2.2x", x[i]);
    printf("\n");

    return 0;
    }

    I'd assume that there will be padding between foo.c and foo.d at the
    minimum. Anyway, I get:

    foo->a = 0
    foo->b = 0
    foo->c = ''
    foo->d = (nil)
    foo->e = 0.000000 00000000000000000000000000000000000000000000000000000000000000000000000000000000


    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From David Brown@david.brown@hesbynett.no to comp.lang.c on Thu May 14 14:11:47 2026
    From Newsgroup: comp.lang.c

    On 14/05/2026 12:47, Richard Harnden wrote:
    On 14/05/2026 10:36, David Brown wrote:
    """
    {0} initializer in C or C++ for unions no longer guarantees clearing
    of the whole union (except for static storage duration
    initialization), it just initializes the first union member to zero.
    If initialization of the whole union including padding bits is
    desirable, use {} (valid in C23 or C++) or use -fzero-init-padding-
    bits=unions option to restore the old GCC behavior.
    """

    "except for static storage duration initialization"


    I read it that the "except" part means static duration padding zeroing
    is not affected by the change - static duration objects have their
    padding zeroed as before. Others here are more reliable sources of
    standards requirements than I am, but I know enough about how the initialisation of static duration objects works in gcc to talk about
    that. Static duration data (file-scope objects and local static
    objects) are either explicitly initialised, or default initialised to zero.

    In the first case, objects are allocated in ".data" sections and prior
    to main(), their initialisation data is copied verbatim from a read-only
    part of the binary. This read-only copy is generated by the compiler
    and covers all the bytes of the object - including padding. So it is
    natural that the padding bytes here are zero. In the second case, the
    objects are allocated in the ".bss" sections and are zeroed by,
    effectively, a memset() operation. (On some platforms the OS will
    provide the memory ready-cleared, but the effect is the same.) gcc does
    not support any targets for which bytes of zero are unsuitable for
    floating point or pointer zeroing.

    So for anything of static duration, zero padding initialisation is
    guaranteed. (This is specifically for gcc, but I believe it applies to
    all real-world compilers. I have seen some compilers with more
    sophisticated pre-main initialisation, but all gave the same result.)

    Does that guarentee that this will always be zeroed, even for padding,
    and even if NULL is not all-bits-zero ... ?

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>

    struct foo
    {
    -a-a-a int a;
    -a-a-a unsigned long b;
    -a-a-a char c;
    -a-a-a void *d;
    -a-a-a double e;
    };

    static struct foo FOO_INIT;

    int main(void)
    {
    -a-a-a struct foo *foo = malloc(sizeof *foo);
    -a-a-a unsigned char *x = (unsigned char *) foo;

    -a-a-a memcpy(foo, &FOO_INIT, sizeof *foo);

    -a-a-a printf("foo->a = %d\n", foo->a);
    -a-a-a printf("foo->b = %lu\n", foo->b);
    -a-a-a printf("foo->c = '%c'\n", foo->c);
    -a-a-a printf("foo->d = %p\n", foo->d);
    -a-a-a printf("foo->e = %f\n", foo->e);

    -a-a-a for (size_t i=0; i<sizeof *foo; i++)
    -a-a-a-a-a-a-a printf("%2.2x", x[i]);
    -a-a-a printf("\n");

    -a-a-a return 0;
    }

    I'd assume that there will be padding between foo.c and foo.d at the minimum.-a Anyway, I get:

    foo->a = 0
    foo->b = 0
    foo->c = ''
    foo->d = (nil)
    foo->e = 0.000000 00000000000000000000000000000000000000000000000000000000000000000000000000000000



    To my knowledge, you will always get this behaviour in real-world
    compilers. That includes gcc 15 onwards - the change there is purely
    for local variables.



    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Keith Thompson@Keith.S.Thompson+u@gmail.com to comp.lang.c on Thu May 14 15:55:32 2026
    From Newsgroup: comp.lang.c

    David Brown <david.brown@hesbynett.no> writes:
    [...]
    I have a feeling that I read somewhere about a proposed standard
    library function that could be used to zero all padding bits and bytes
    in a struct. Unfortunately, I can't remember where I saw it - and
    there is a fair chance it was a C++ proposal rather than a C proposal.

    You're probably thinking of memset_explicit(), standardized in C23.

    N3220 7.26.6.2:

    The memset_explicit function copies the value of c (converted to an
    unsigned char) into each of the first n characters of the object
    pointed to by s. The purpose of this function is to make sensitive
    information stored in the object inaccessible.

    Footnote:

    The intention is that the memory store is always performed
    (i.e. never elided), regardless of optimizations. This is in
    contrast to calls to the memset function (7.26.6.1)

    The only difference in the standard between the descriptions
    of memset() and memset_explicit() is the second sentence and
    the footnote. I'm not convinced that that's enough to formally
    specify that an implementation that optimizes away a call to
    memset_explicit() is non-conforming. But I think implementations
    will follow the intent.

    Perhaps the first parameter parameter should have been defined as
    "volatile void *s".

    [...]
    --
    Keith Thompson (The_Other_Keith) Keith.S.Thompson+u@gmail.com
    void Void(void) { Void(); } /* The recursive call of the void */
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From David Brown@david.brown@hesbynett.no to comp.lang.c on Fri May 15 10:20:07 2026
    From Newsgroup: comp.lang.c

    On 15/05/2026 00:55, Keith Thompson wrote:
    David Brown <david.brown@hesbynett.no> writes:
    [...]
    I have a feeling that I read somewhere about a proposed standard
    library function that could be used to zero all padding bits and bytes
    in a struct. Unfortunately, I can't remember where I saw it - and
    there is a fair chance it was a C++ proposal rather than a C proposal.

    You're probably thinking of memset_explicit(), standardized in C23.

    No, that's a different function for a different purpose. But it could
    be used to clear all padding data in a struct - along with all
    non-padding data. The point of this legendary function is that it only
    clears padding, not data fields.

    The more I think about it, the more I am convinced it was for C++ - the natural interface there would be a template function that took a pointer
    to a struct. I am not sure it if something like this would be feasible
    in C (except as a "magic" macro in the standard library).


    N3220 7.26.6.2:

    The memset_explicit function copies the value of c (converted to an
    unsigned char) into each of the first n characters of the object
    pointed to by s. The purpose of this function is to make sensitive
    information stored in the object inaccessible.

    Footnote:

    The intention is that the memory store is always performed
    (i.e. never elided), regardless of optimizations. This is in
    contrast to calls to the memset function (7.26.6.1)

    The only difference in the standard between the descriptions
    of memset() and memset_explicit() is the second sentence and
    the footnote. I'm not convinced that that's enough to formally
    specify that an implementation that optimizes away a call to memset_explicit() is non-conforming. But I think implementations
    will follow the intent.

    Perhaps the first parameter parameter should have been defined as
    "volatile void *s".


    Yes, that would have made sense.

    --- Synchronet 3.22a-Linux NewsLink 1.2