The reason for this has to do with how types are represented.
Section 6.7.9p10 of the C standard describes how fields are initialized as follows:
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
And p21 also states:
If there are fewer initializers in a brace-enclosed list than there
are elements or members of an aggregate, or fewer characters in a
string literal used to initialize an array of known
size than there are elements in the array, the remainder of
the aggregate shall be
initialized implicitly the same as objects that have static storage
duration
The difference between this and setting all bytes to zero is that some of the above values may not necessarily be represented by all bits zero.
For example, there are some architectures where the address 0 is a valid address. This means that a null pointer is not represented as all bits zero. (Note: (void *)0
is specified as a null pointer constant by the standard, however the implementation will treat this as whatever the representation of a null pointer is)
The standard also doesn't mandate a particular representation for floating point types. While the most common representation, IEEE754, does use all bits 0 to represent the value +0, this is not necessarily true for other representations.