[Bug c++/116469] New: Inconsistent Zero Initialization of Nested Structures

2024-08-23 Thread jonassonarvid02 at gmail dot com via Gcc-bugs
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

2024-08-23 Thread jonassonarvid02 at gmail dot com via Gcc-bugs
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

2024-08-23 Thread jonassonarvid02 at gmail dot com via Gcc-bugs
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

2024-08-23 Thread jonassonarvid02 at gmail dot com via Gcc-bugs
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

2024-08-23 Thread jonassonarvid02 at gmail dot com via Gcc-bugs
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

2024-08-23 Thread jonassonarvid02 at gmail dot com via Gcc-bugs
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.