[Bug c++/116469] New: Inconsistent Zero Initialization of Nested Structures
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 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 #include #include template struct Inner { Inner() {} unsigned char arr[N]; }; struct Outer1 { template struct Outer { int dummy; Inner inner; }; }; struct Outer2 { template struct Outer { Inner inner; }; }; struct Outer3 { template struct Outer { Inner inner; int dummy; }; }; template bool isZeroInit() { for(int i = 0; i < 2; i++) { typename T::template Outer outer{}; for(auto &c : outer.inner.arr) { if(c != 0) { return false; } c = 1; } } return true; } template auto checkZeroInit(std::vector v, std::integer_sequence) { if constexpr (N != 0) v.push_back(isZeroInit()); return v; } template auto checkZeroInit(std::vector v, std::integer_sequence) { if constexpr (N != 0) v.push_back(isZeroInit()); return checkZeroInit(std::move(v), std::integer_sequence{}); } int main() { auto v = checkZeroInit(std::vector{}, std::make_integer_sequence{}); std::cout << "Outer1: "; for(auto b : v) std::cout << b; std::cout << std::endl; v = checkZeroInit(std::vector{}, std::make_integer_sequence{}); std::cout << "Outer2: "; for(auto b : v) std::cout << b; std::cout << std::endl; v = checkZeroInit(std::vector{}, std::make_integer_sequence{}); 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.
[Bug c++/116469] Inconsistent Zero Initialization of Nested Structures
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=116469 --- Comment #1 from Arvid Jonasson --- Created attachment 58981 --> https://gcc.gnu.org/bugzilla/attachment.cgi?id=58981&action=edit Example 2 preprocessed
[Bug c++/112666] Missed optimization: Value initialization zero-initializes members with user-defined constructor
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=112666 Arvid Jonasson changed: What|Removed |Added CC||jonassonarvid02 at gmail dot com --- Comment #6 from Arvid Jonasson --- I've submitted a new bug report (bug 116469) that expands on this issue. The zero-initialization behavior appears to also depend on struct composition and array member sizes, not just user-provided constructors.
[Bug c++/116469] Inconsistent Zero Initialization of Nested Structures
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=116469 --- Comment #2 from Arvid Jonasson --- Quick update: I initially overlooked that the classes were aggregate types, which don't require zero-initialization. However, the issue persists with non-aggregate types. To demonstrate this, I've modified example 1 for Outer1 and Outer3 by making the dummy integer private, which makes them non-aggregate (https://eel.is/c++draft/dcl.init.aggr#1.2). -- #include struct Inner { Inner(){} unsigned char arr[10]; }; // Struct 1: Zero-initialized struct Outer1 { private: int dummy; public: Inner inner; }; // Struct 3: Not zero-initialized struct Outer3 { public: Inner inner; private: 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 << "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'; } } -- Output: arvidjonasson@Arvids-MacBook-Air ~/testZeroInit % g++-14 -O3 -std=c++11 -Wall -Wextra example1.cpp -o example1.out -save-temps 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 Outer3: 0 0 0 0 0 1 2 3 4 5 0 1 2 3 4 5 6 7 8 9 -- Expected behavior: Both Outer1 and Outer3 objects should be list-initialized (https://eel.is/c++draft/dcl.init#list-3.5), leading to value-initialization. This should zero-initialize the objects before default-initializing them (https://eel.is/c++draft/dcl.init#general-9), resulting in all zeroes being printed. Actual behaviour: * Outer1 is properly zero-initialized. * Outer3 is not properly zero-initialized.
[Bug c++/112666] Missed optimization: Value initialization zero-initializes members with user-defined constructor
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=112666 --- Comment #7 from Arvid Jonasson --- (In reply to Jonathan Wakely from comment #1) > > C does not have a user-provided default constructor, so value-initialization > means: > > "- the object is zero-initialized and the semantic constraints for > default-initialization are checked, and if T has a non-trivial default > constructor, the object is default-initialized;" > >From my understanding, since C is an aggregate type, it should be initialized using aggregate initialization as outlined in [dcl.init.list] (https://eel.is/c++draft/dcl.init#list-3.4). This process should construct the b member without first zero-initializing it, as specified in [dcl.init.aggr] (https://eel.is/c++draft/dcl.init#aggr-5.2), [dcl.init.list] (https://eel.is/c++draft/dcl.init#list-3.5) and [dcl.init.general] (https://eel.is/c++draft/dcl.init#general-9.1). Given this, it seems that the inclusion of the memset instruction might be unnecessary. Could someone confirm whether this interpretation is correct? If so, it might be worth revisiting and potentially reopening the issue.
[Bug c++/116469] Inconsistent Zero Initialization of Nested Structures
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=116469 --- Comment #3 from Arvid Jonasson --- Modified Example 2 with non-aggregate types: -- #include #include #include template struct Inner { Inner() {} unsigned char arr[N]; }; struct Outer1 { template class Outer { int dummy; Inner inner; public: Inner& getInner() { return inner; } }; }; struct Outer2 { template class Outer { Inner inner; public: Inner& getInner() { return inner; } }; }; struct Outer3 { template class Outer { Inner inner; int dummy; public: Inner& getInner() { return inner; } }; }; template bool isZeroInit() { for(int i = 0; i < 2; i++) { typename T::template Outer outer{}; for(auto &c : outer.getInner().arr) { if(c != 0) { return false; } c = 1; } } return true; } template auto checkZeroInit(std::vector v, std::integer_sequence) { if constexpr (N != 0) v.push_back(isZeroInit()); return v; } template auto checkZeroInit(std::vector v, std::integer_sequence) { if constexpr (N != 0) v.push_back(isZeroInit()); return checkZeroInit(std::move(v), std::integer_sequence{}); } int main() { auto v = checkZeroInit(std::vector{}, std::make_integer_sequence{}); std::cout << "Outer1: "; for(auto b : v) std::cout << b; std::cout << std::endl; v = checkZeroInit(std::vector{}, std::make_integer_sequence{}); std::cout << "Outer2: "; for(auto b : v) std::cout << b; std::cout << std::endl; v = checkZeroInit(std::vector{}, std::make_integer_sequence{}); std::cout << "Outer3: "; for(auto b : v) std::cout << b; std::cout << std::endl; return 0; } -- Output: 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:83:30: example2.cpp:46:18: warning: 'outer.Outer3::Outer<17>::inner.Inner<17>::arr[16]' may be used uninitialized [-Wmaybe-uninitialized] 46 | if(c != 0) { |~~^~~~ example2.cpp: In function 'int main()': example2.cpp:44:39: note: 'outer' declared here 44 | typename T::template Outer outer{}; | arvidjonasson@Arvids-MacBook-Air ~/testZeroInit % ./example2.out Outer1: 110101101110111011101110111011101110111011101110111011101110111011101110111011101110111011101110111011101110111011101110111011101110111011101110111011101110111011101110111011101110111011101110111011101110111011101110111011101110111011101110111 Outer2: 1110100 Outer3: 1000111 -- Expected behaviour: Program should output all 1's since all objects should be zero initialized. Actual behaviour: Program doesn't output all 1's since all objects are not zero initialized.