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?
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?
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.
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 3: if you want to initialize and zero a struct, along withI find that quite clean, as I said above.
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.
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).
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;
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.
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;
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.
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;
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.
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.
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.
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.
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;
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.
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.
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 -0700The problem is padding is none of those things.
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; >>>>
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 <Keith.S.Thompson+u@gmail.com> writes:<snip>
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>11The problem is padding is none of those things.
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; >>>>>
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.
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.
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.
Thank you for the correction upstream about rules for initializing
padding.
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.
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.
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
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?
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.
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 <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.
"""
{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.
"""
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
{
-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
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.
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".
| Sysop: | Amessyroom |
|---|---|
| Location: | Fayetteville, NC |
| Users: | 65 |
| Nodes: | 6 (1 / 5) |
| Uptime: | 01:05:01 |
| Calls: | 862 |
| Files: | 1,311 |
| D/L today: |
10 files (20,373K bytes) |
| Messages: | 264,187 |