https://gcc.gnu.org/bugzilla/show_bug.cgi?id=116469
Bug ID: 116469
Summary: Inconsistent Zero Initialization of Nested Structures
Product: gcc
Version: 14.1.0
Status: UNCONFIRMED
Severity: normal
Priority: P3
Component: c++
Assignee: unassigned at gcc dot gnu.org
Reporter: jonassonarvid02 at gmail dot com
Target Milestone: ---
Created attachment 58980
--> https://gcc.gnu.org/bugzilla/attachment.cgi?id=58980&action=edit
Example 1 preprocessed
Description:
Following up on Bug 112666, I've discovered inconsistencies in GCC's
zero-initialization behavior for structs containing subobjects with
user-provided default constructors. The behavior varies depending on the
struct's composition and the size of arrays within inner structs, contradicting
the expected behavior based on the C++ standard and the previous bug
discussion.
-----------------------------------Examples-----------------------------------
Example 1:
------------------------------------------------------------------------------
#include <iostream>
struct Inner {
Inner(){}
unsigned char arr[10];
};
// Struct 1: Zero-initialized
struct Outer1 {
int dummy;
Inner inner;
};
// Struct 2: Not zero-initialized
struct Outer2 {
Inner inner;
};
// Struct 3: Not zero-initialized
struct Outer3 {
Inner inner;
int dummy;
};
int main() {
std::cout << "Outer1:\n";
for(int i = 0; i < 2; ++i) {
unsigned char counter = 0;
Outer1 outer{};
for(auto &c : outer.inner.arr) {
std::cout << int(c) << ' ';
c = counter++;
}
std::cout << '\n';
}
std::cout << "Outer2:\n";
for(int i = 0; i < 2; ++i) {
unsigned char counter = 0;
Outer2 outer{};
for(auto &c : outer.inner.arr) {
std::cout << int(c) << ' ';
c = counter++;
}
std::cout << '\n';
}
std::cout << "Outer3:\n";
for(int i = 0; i < 2; ++i) {
unsigned char counter = 0;
Outer3 outer{};
for(auto &c : outer.inner.arr) {
std::cout << int(c) << ' ';
c = counter++;
}
std::cout << '\n';
}
}
------------------------------------------------------------------------------
Example 2:
------------------------------------------------------------------------------
#include <iostream>
#include <utility>
#include <vector>
template<unsigned int N>
struct Inner {
Inner() {}
unsigned char arr[N];
};
struct Outer1 {
template<unsigned int N>
struct Outer {
int dummy;
Inner<N> inner;
};
};
struct Outer2 {
template<unsigned int N>
struct Outer {
Inner<N> inner;
};
};
struct Outer3 {
template<unsigned int N>
struct Outer {
Inner<N> inner;
int dummy;
};
};
template<typename T, unsigned int N>
bool isZeroInit() {
for(int i = 0; i < 2; i++) {
typename T::template Outer<N> outer{};
for(auto &c : outer.inner.arr) {
if(c != 0) {
return false;
}
c = 1;
}
}
return true;
}
template <typename T, unsigned int N>
auto checkZeroInit(std::vector<bool> v, std::integer_sequence<unsigned int, N>)
{
if constexpr (N != 0)
v.push_back(isZeroInit<T, N>());
return v;
}
template <typename T, unsigned int N, unsigned int... M>
auto checkZeroInit(std::vector<bool> v, std::integer_sequence<unsigned int, N,
M...>) {
if constexpr (N != 0)
v.push_back(isZeroInit<T, N>());
return checkZeroInit<T>(std::move(v), std::integer_sequence<unsigned int,
M...>{});
}
int main() {
auto v = checkZeroInit<Outer1>(std::vector<bool>{},
std::make_integer_sequence<unsigned int, 300>{});
std::cout << "Outer1: ";
for(auto b : v)
std::cout << b;
std::cout << std::endl;
v = checkZeroInit<Outer2>(std::vector<bool>{},
std::make_integer_sequence<unsigned int, 300>{});
std::cout << "Outer2: ";
for(auto b : v)
std::cout << b;
std::cout << std::endl;
v = checkZeroInit<Outer3>(std::vector<bool>{},
std::make_integer_sequence<unsigned int, 300>{});
std::cout << "Outer3: ";
for(auto b : v)
std::cout << b;
std::cout << std::endl;
return 0;
}
------------------------------------------------------------------------------
Expected Behavior:
According to the C++ standard and the discussion in Bug 112666, structs without
user-provided constructors should have all their members zero-initialized
during value initialization, regardless of the struct's composition or the size
of array members.
Actual Behavior:
The zero-initialization behavior is inconsistent and depends on:
* The struct's composition (presence and position of other members)
* The size of array members within inner structs
Observations:
Outer1 (int member before Inner): Inconsistent for most array lengths,
consistent only for larger arrays
Outer2 (only Inner member): Zero-initialized only for small array sizes
Outer3 (Inner member before int): Zero-initialized for small and large array
sizes, but not for medium sizes
-----------------------------------Outputs-----------------------------------
arvidjonasson@Arvids-MacBook-Air ~/testZeroInit
% g++-14 -O3 -std=c++11 -Wall -Wextra example1.cpp -o example1.out
arvidjonasson@Arvids-MacBook-Air ~/testZeroInit
% ./example1.out
Outer1:
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
Outer2:
0 0 0 0 0 1 2 3 4 5
0 1 2 3 4 5 6 7 8 9
Outer3:
0 1 2 3 4 5 6 7 8 9
0 1 2 3 4 5 6 7 8 9
arvidjonasson@Arvids-MacBook-Air ~/testZeroInit
% g++-14 -O3 -std=c++17 -Wall -Wextra example2.cpp -o example2.out
---[ lots of -Wmaybe-uninitialized warnings ]---
inlined from 'int main()' at example2.cpp:77:30:
example2.cpp:40:18: warning:
'outer.Outer3::Outer<17>::inner.Inner<17>::arr[16]' may be used uninitialized
[-Wmaybe-uninitialized]
40 | if(c != 0) {
| ~~^~~~
example2.cpp: In function 'int main()':
example2.cpp:38:39: note: 'outer' declared here
38 | typename T::template Outer<N> outer{};
|
arvidjonasson@Arvids-MacBook-Air ~/testZeroInit
% ./example2.out
Outer1:
11010000000000000110111011101110111011101110111011101110111011101110111011101110111011101110111011101110111011101110111011101110111011101110111011101110111011101110111011101110111011101110111011101110111011101110111011101110111011101110111011101110111011111111111111111111111111111111111111111111111
Outer2:
11111111111111101000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
Outer3:
11111111111111111000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000011111111111111111111111111111111111111111111111
------------------------------------------------------------------------------
Additional Notes:
This issue expands on Bug 112666, revealing a more complex behavior than
initially discussed. The observed dependency on array size and struct
composition raises important questions about the correct interpretation of the
C++ standard in these cases:
* Should the size of an array member influence whether its containing struct
is zero-initialized?
* Does the standard specify different behavior for structs with different
compositions (e.g., order of members, presence of non-array members) during
zero-initialization?
* Is the current behavior a bug in GCC or an intentional implementation detail
that adheres to a specific interpretation of the standard?
These inconsistencies could lead to subtle, hard-to-diagnose bugs, especially
in performance-critical code. Clarification on the expected behavior would be
greatly appreciated.
Compiler and system information:
% g++-14 -v
Using built-in specs.
COLLECT_GCC=g++-14
COLLECT_LTO_WRAPPER=/opt/homebrew/Cellar/gcc/14.1.0_2/bin/../libexec/gcc/aarch64-apple-darwin23/14/lto-wrapper
Target: aarch64-apple-darwin23
Configured with: ../configure --prefix=/opt/homebrew/opt/gcc
--libdir=/opt/homebrew/opt/gcc/lib/gcc/current --disable-nls
--enable-checking=release --with-gcc-major-version-only
--enable-languages=c,c++,objc,obj-c++,fortran,m2 --program-suffix=-14
--with-gmp=/opt/homebrew/opt/gmp --with-mpfr=/opt/homebrew/opt/mpfr
--with-mpc=/opt/homebrew/opt/libmpc --with-isl=/opt/homebrew/opt/isl
--with-zstd=/opt/homebrew/opt/zstd --with-pkgversion='Homebrew GCC 14.1.0_2'
--with-bugurl=https://github.com/Homebrew/homebrew-core/issues
--with-system-zlib --build=aarch64-apple-darwin23
--with-sysroot=/Library/Developer/CommandLineTools/SDKs/MacOSX14.sdk
Thread model: posix
Supported LTO compression algorithms: zlib zstd
gcc version 14.1.0 (Homebrew GCC 14.1.0_2)