Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Reason behind that object addresses must differ if types are the same, but addresses can be the same if types differ

Tags:

c++

We have empty base class optimization. Look at these two cases:

Case A

struct Empty1 { };
struct Empty2 { };
struct One: Empty1, Empty2 { };

Here, sizeof(One) is 1. Addresses of Empty1 and Empty2 are the same.

Case B

struct Empty { };
struct Empty1: public Empty { };
struct Empty2: public Empty { };
struct Two: Empty1, Empty2 { };

Here, sizeof(Two) is 2. Addresses of Empty1 and Empty2 differ, because both of them have Empty (and these Emptys should have differing addresses).

Why does the standard allow to have the same address for differing types, but disallow for same types? What problem does this rule avoid?

What problem will I have, if I create a tuple implementation where all empty members will have the same address (no matter of their types)?

like image 677
geza Avatar asked Sep 21 '17 11:09

geza


2 Answers

The address is part of the identity of an object. Separate objects of the same type having separate addresses is a general rule. That's how you know that there is more than one object.

In copy constructors you can often find a test for self-assignment if (this != &other). This would fail if different objects of the same type were to have the same address.

Objects of different types cannot be compared like this anyway, so not a problem. And we also have a struct and its first member and an array and the first element that have the same address, but are of different types.

The empty base class poses a problem here, but only if you have two base classes of the same type, or the base class happens to be of the same type as the first member of the derived class. So there are special rules to forbid the overlap of two identical objects in those cases.

The idea is that they should be two separate sub-objects and work in a this != &that test.

like image 156
Bo Persson Avatar answered Nov 07 '22 23:11

Bo Persson


The rule is stated in the standard [intro.object]:

Unless an object is a bit-field or a base class subobject of zero size, the address of that object is the address of the first byte it occupies. Two objects a and b with overlapping lifetimes that are not bit-fields may have the same address if one is nested within the other, or if at least one is a base class subobject of zero size and they are of different types; otherwise, they have distinct addresses.

One reason why an empty base class fall on this rule is because one can cast an empty base class reference, to a reference of the base object.

The best is to see it in action, I have added the assembly generated by gcc with -O3 optimization:

struct Empty1 { };
struct Empty2 { };
struct One: Empty1, Empty2 { 
  int i;
};
struct Other{
  int i;
};

//an Empty and a One may have the same address                                              
int f(Empty1& a,One& b){   mov     DWORD PTR [rsi], 12  //write to *rsi
  b.i=12;                  mov     DWORD PTR [rdi], 15  //may be rsi==rdi
  auto& a_one =            mov     eax, DWORD PTR [rsi] //reload from *rsi
    static_cast<One&>(a);  ret
  a_one.i=15;                         
  return b.i;                        
}    

//an int and a One may have the same address                                              
int f(int& a,One& b){      mov     DWORD PTR [rsi], 12  //write to *rsi                           
  b.i=12;                  mov     DWORD PTR [rdi], 15  //may be rsi==rdi                         
  a=15;                    mov     eax, DWORD PTR [rsi] //reload from *rsi
  return b.i;              ret                                                   
}        

//a long can not have the same address as a One 
int f(long& a,One& b){     mov     DWORD PTR [rsi], 12                        
  b.i=12;                  mov     eax, 12  //aliasing optimization
  a=15;                    mov     QWORD PTR [rdi], 15 //sure rsi!=rdi aliasing rule
  return b.i;              ret                        
}   

//the int inside and Other can alias an int inside a One
int f(Other& a,One& b){     mov     DWORD PTR [rsi], 12                        
  b.i=12;                  mov     eax, 12  //aliasing optimization
  a.i=15;                    mov     QWORD PTR [rdi], 15 //sure rsi!=rdi aliasing rule
  return b.i;              ret                        
}                                        

Only one complete object can exist at a given address. The complete object is the one that is not nested within another.

It is impossible to succeed to have to object at the same address (without UB), with the same type. But since you object are empty, you do not need more than one in your optimized tuple. You will only need to implement a get<I> which referes to the same object for all Is that are meant to refer to object of of the same empty class.

like image 28
Oliv Avatar answered Nov 07 '22 23:11

Oliv