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)