[lnkForumImage]
TotalShareware - Download Free Software

Confronta i prezzi di migliaia di prodotti.
Asp Forum
 Home | Login | Register | Search 


 

Forums >

comp.lang.c++

Class layout

Hendrik Schober

11/10/2008 5:37:00 PM

Hi,

the following code is the boiled-down version of something that
gives us trouble on GCC. In the real code, 'F' is some fixed-size
array class, 'Q' and 'V' are derived classes that probably aren't
very interesting for this question, 'B' is a 4x4 matrix base and
'M' is the matrix class in use.
The code works with VC and several versions of GCC, but in one
test using GCC it breaks with nonsensical values for the 2nd-4th
row of the matrix. The trouble is, it only does so when no
additional statements are added to the function (that's mimicked
here in 'main()') and not, if the code is run under a debugger.
The problem doesn't appear with this boiled-down example code, but
looking at the code and the symptoms, I have the suspicion that
this invokes UB anyway and would like the group's opinion whether
my suspicion is right.
'M<T>::data()'assumes that all of data members of the inherited
'F's are laid out contiguously. Can we assume this regarding to
the standard? My suspicion is, we can't. But I don't know why we
either can and can't. I believe struct layout in C is rather
strict and reliable, but I don't know what's needed of the C++-
only features (inheritance, templates etc.) is needed to make
C++ deviate from that.

TIA,

Schobi


--8<----8<----8<----8<----8<----8<----8<----8<----8<----8<----8<----8<----8<--

#include <iostream>
#include <string>

template< typename T >
struct F {
T val[4];
F() {val[0]=T();val[1]=T();val[2]=T();val[3]=T();}
F(T a0, T a1, T a2, T a3) {val[0]=a0 ;val[1]=a1 ;val[2]=a2 ;val[3]=a3 ;}
T& operator[](std::size_t i) {return val[i];}
const T& operator[](std::size_t i) const {return val[i];}
T* begin() {return &val[0];}
const T* begin() const {return &val[0];}
T* end () {return &val[4];}
const T* end () const {return &val[4];}
};

template< typename T >
struct Q : public F<T> {
Q() : F() {}
Q(T a0, T a1, T a2, T a3) : F(a0,a1,a2,a3) {}
};

template< typename T >
struct V : public Q<T> {
V() : Q() {}
V(T a0, T a1, T a2, T a3) : Q(a0,a1,a2,a3) {}
};

template< typename T >
struct B : public V< V<T> > {
typedef V<T> Row;

B() {assign( 0, 0, 0, 0
, 0, 0, 0, 0
, 0, 0, 0, 0
, 0, 0, 0, 0 );}

B( T a00, T a01, T a02, T a03
, T a10, T a11, T a12, T a13
, T a20, T a21, T a22, T a23
, T a30, T a31, T a32, T a33 ) {assign( a00, a01, a02, a03
, a10, a11, a12, a13
, a20, a21, a22, a23
, a30, a31, a32, a33 );}

void assign( T a00, T a01, T a02, T a03
, T a10, T a11, T a12, T a13
, T a20, T a21, T a22, T a23
, T a30, T a31, T a32, T a33 )
{
this->val[0][0]=a00;this->val[0][1]=a01;this->val[0][2]=a02;this->val[0][3]=a03;
this->val[1][0]=a10;this->val[1][1]=a11;this->val[1][2]=a12;this->val[1][3]=a13;
this->val[2][0]=a20;this->val[2][1]=a21;this->val[2][2]=a22;this->val[2][3]=a23;
this->val[3][0]=a30;this->val[3][1]=a31;this->val[3][2]=a32;this->val[3][3]=a33;
}

};

template< typename T >
struct M : public B<T> {
M() : B<T>(), i() {}
const T* data() const {return B<T>::begin()->begin();}
int i;
};

int main()
{
M<float> matrix;
matrix.assign(0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15);
const float* data = matrix.data();

for( std::size_t i = 0; i < 16; ++i ) {
std::cout << data[i] << '\n';
}

return 0;
}
4 Answers

Victor Bazarov

11/11/2008 1:02:00 AM

0

Hendrik Schober wrote:
> Hi,
>
> the following code is the boiled-down version of something that
> gives us trouble on GCC. In the real code, 'F' is some fixed-size
> array class, 'Q' and 'V' are derived classes that probably aren't
> very interesting for this question, 'B' is a 4x4 matrix base and
> 'M' is the matrix class in use.
> The code works with VC and several versions of GCC, but in one
> test using GCC it breaks with nonsensical values for the 2nd-4th
> row of the matrix. The trouble is, it only does so when no
> additional statements are added to the function (that's mimicked
> here in 'main()') and not, if the code is run under a debugger.
> The problem doesn't appear with this boiled-down example code, but
> looking at the code and the symptoms, I have the suspicion that
> this invokes UB anyway and would like the group's opinion whether
> my suspicion is right.
> 'M<T>::data()'assumes that all of data members of the inherited
> 'F's are laid out contiguously. Can we assume this regarding to
> the standard?

No. Who knows what kind of padding your compiler sticks in your
'F' class right after the 'val'...

> My suspicion is, we can't. But I don't know why we
> either can and can't. I believe struct layout in C is rather
> strict and reliable, but I don't know what's needed of the C++-
> only features (inheritance, templates etc.) is needed to make
> C++ deviate from that.
>
> TIA,
>
> Schobi
>
>
> --8<----8<----8<----8<----8<----8<----8<----8<----8<----8<----8<----8<----8<--
>
> #include <iostream>
> #include <string>
>
> template< typename T >
> struct F {
> T val[4];
> F()
> {val[0]=T();val[1]=T();val[2]=T();val[3]=T();} F(T a0, T a1, T
> a2, T a3) {val[0]=a0 ;val[1]=a1
> ;val[2]=a2 ;val[3]=a3 ;} T& operator[](std::size_t i) {return
> val[i];} const T& operator[](std::size_t i) const {return val[i];} T*
> begin() {return
> &val[0];} const T* begin() const {return
> &val[0];} T* end () {return
> &val[4];} const T* end () const {return
> &val[4];} };

Your use of 'flowed' format is apparent here... :-)

But let me just say, ugh! Why couldn't you initialise the 'val' at
least in the default constructor? You could do

F() : val() {}

right?

>
> template< typename T >
> struct Q : public F<T> {
> Q() : F() {}
> Q(T a0, T a1, T a2, T a3) : F(a0,a1,a2,a3) {}
> };
>
> template< typename T >
> struct V : public Q<T> {
> V() : Q() {}
> V(T a0, T a1, T a2, T a3) : Q(a0,a1,a2,a3) {}
> };
>
> template< typename T >
> struct B : public V< V<T> > {
> typedef V<T> Row;

I don't think you're using this typedef. Are you?

>
> B() {assign( 0, 0, 0, 0
> , 0, 0, 0, 0
> , 0, 0, 0, 0
> , 0, 0, 0, 0
> );}
> B( T a00, T a01, T a02, T a03
> , T a10, T a11, T a12, T a13
> , T a20, T a21, T a22, T a23
> , T a30, T a31, T a32, T a33 ) {assign( a00, a01,
> a02, a03 ,
> a10, a11, a12,
> a13 , a20,
> a21, a22, a23 , a30, a31, a32, a33 );}
> void assign( T a00, T a01, T a02, T a03
> , T a10, T a11, T a12, T a13
> , T a20, T a21, T a22, T a23
> , T a30, T a31, T a32, T a33 )
> {
>
> this->val[0][0]=a00;this->val[0][1]=a01;this->val[0][2]=a02;this->val[0][3]=a03;
> this->val[1][0]=a10;this->val[1][1]=a11;this->val[1][2]=a12;this->val[1][3]=a13;
> this->val[2][0]=a20;this->val[2][1]=a21;this->val[2][2]=a22;this->val[2][3]=a23;
> this->val[3][0]=a30;this->val[3][1]=a31;this->val[3][2]=a32;this->val[3][3]=a33;
> }
> };
>
> template< typename T >
> struct M : public B<T> {
> M() : B<T>(), i() {}
> const T* data() const {return
> B<T>::begin()->begin();} int i;
> };
>
> int main()
> {
> M<float> matrix;
> matrix.assign(0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15);
> const float* data = matrix.data();
>
> for( std::size_t i = 0; i < 16; ++i ) {
> std::cout << data[i] << '\n';
> }
>
> return 0;
> }

You could simply verify that the layout is like you expect. In the
constructor of the 'V' class, for example, you could simply do

if (sizeof(this) != sizeof(T) * 4)
throw "A-a-a-a-a-a-a";

V
--
Please remove capital 'A's when replying by e-mail
I do not respond to top-posted replies, please don't ask


Hendrik Schober

11/11/2008 8:39:00 AM

0

Victor Bazarov wrote:
> Hendrik Schober wrote:
> [...]
>> 'M<T>::data()'assumes that all of data members of the inherited
>> 'F's are laid out contiguously. Can we assume this regarding to
>> the standard?
>
> No. Who knows what kind of padding your compiler sticks in your
> 'F' class right after the 'val'...

Thanks. I feared so.
What's needed to turn a class into a non-POD type?

> [...]
>>
>> template< typename T >
>> struct F {
>> T val[4];
>> F()
>> {val[0]=T();val[1]=T();val[2]=T();val[3]=T();} F(T a0, T a1, T
>> a2, T a3) {val[0]=a0 ;val[1]=a1
>> ;val[2]=a2 ;val[3]=a3 ;} T& operator[](std::size_t i) {return
>> val[i];} const T& operator[](std::size_t i) const {return val[i];} T*
>> begin() {return
>> &val[0];} const T* begin() const {return
>> &val[0];} T* end () {return
>> &val[4];} const T* end () const {return
>> &val[4];} };
>
> Your use of 'flowed' format is apparent here... :-)

???

> But let me just say, ugh! Why couldn't you initialise the 'val' at
> least in the default constructor? You could do
>
> F() : val() {}
>
> right?

I didn't know you could do this with an array.
(I started with the original code. When I had removed
everything to make it a self-contained example, it already
didn't expose the problem anymore, so I only did a little
cleaning before presenting it here, without going into much
detail. I probably hadn't even looked consciously once at
this ctor.)

> [...]
>> template< typename T >
>> struct B : public V< V<T> > {
>> typedef V<T> Row;
>
> I don't think you're using this typedef. Are you?

It seems it's an oversight of the stripping-down process. :)

> [...]
> You could simply verify that the layout is like you expect. In the
> constructor of the 'V' class, for example, you could simply do
>
> if (sizeof(this) != sizeof(T) * 4)
> throw "A-a-a-a-a-a-a";

We already tried. But as soon as any other code is added
to the mix, the problem is going away. It might be GCC's
optimizer choking.

> V

Schobi

The Horny Goat

6/22/2014 4:15:00 PM

0

On Sat, 21 Jun 2014 20:13:38 -0700 (PDT), Rob
<raharris1973@my-deja.com> wrote:

>My preferences at this stage (Stalin firmly in power, Hitler firmly in power having just finished the "Night of the Long Knives"), would be almost exactly the opposite, for these reasons:
>My approach of first resort by this point if I'm still dealing with the Hitler and Stalin regimes post-Rhineland and post-Anschluss is to just risk fullest possible collaboration with Stalin rather than make concessions to Hitler or treat them as equal menaces. Stalin's maximal goals include brutal revolutionary transformation of my country, not a targeting of my ethnic groups for extermination or slavery.
>
>I'd try to avoid unnecessary frictions with the Soviets and unilaterally cease anti-Soviet official pronouncements from the moment Hitler takes over.
>
>I'd hope in the first instance to deter Hitler with backing from France, Britain and the Soviet Union. In the second instance if Hitler remains in a position to drag his country into war, I would allow Soviet armed forces unlimited movement in Polish territory and hope to avoid having to make territorial concessions. In the third instance, I'd let the Soviets in even if I know for sure they are going to take everything up to the Curzon Line by fait accompli.
>
>Betting on Hitler and the Nazi's genocidal racism, and Stalin's greater caution, accepting Soviet help is far less risky. If the Soviet troops in Poland are not inclined to leave after defeating Germany, and if the Soviets are aggressively trying to back Polish communists, Poland's population and society is still better off if the war and German occupation are much shortened. All hope is not necessarrily lost even once Soviet troops are in Poland en masse. If Polish-Soviet cooperation can help prevent France from falling, and there's a decent fraction of a non-communist Polish army left on Polish soil at the end of the war, there's a chance that Stalin will settle for less than a fully communist or subordinate Poland.

That's rational though of questionable effectiveness - note that the
portion of Poland taken by the Soviets under the Nazi-Soviet pact was
almost entirely in German hands by the end of June 1941. Which given
the attack started on June 22 is remarkable to put it mildly.

Rob Harris

6/24/2014 3:47:00 AM

0

On Sunday, June 22, 2014 12:14:48 PM UTC-4, The Horny Goat wrote:

>
> That's rational though of questionable effectiveness - note that the
>
> portion of Poland taken by the Soviets under the Nazi-Soviet pact was
>
> almost entirely in German hands by the end of June 1941. Which given
>
> the attack started on June 22 is remarkable to put it mildly.

It seems to me that the range of plausible outcomes from this altered, pro-Soviet, anti-Nazi policy by Smigly-Rydz is uniformly more beneficial for the Polish population than the OTL results.

The Soviet Union that lost territory including its Polish buffer space, so rapidly in June 1941, was facing a Germany that was much better resourced than it had to be. The Soviets lost as much territory as they did because: A) They were taken by surprise, B) They were facing a Germany that had been given Soviet raw materials for almost two years and C) They were facing a Germany strengthened by acquisition of Czech and French armored vehicles, fuel stocks, etc.

A policy of military cooperation with the USSR pre-war seems to me to make A, B and C unlikely.
In the best case, Poland will receive Soviet guarantees in addition to western, and this will deter Hitler or alarm the German military enough that he is overthrown. It could possibly butterfly away the Nazi's bloodless gains of Czechoslovakian resources if Poland ends up supporting Prague in 1938.
Even if it leaves Munich unchanged, and fails to deter the German military or Hitler, the most likely reaction by Hitler in 1939 would be to strike west first, against France.

A German offensive undertaken at that time would be much less likely to succeed in conquering France.

A Germany bogged down fighting in France and Belgium, will not have the forces to steamroll over all Poland and the western USSR at the same time. It would likely be held west of the Vistula at a minimum. It also is on the road to earlier defeat.

Even if the Germans achieve a spectacular victory against France in 1939, it allows both the Poles and Soviets to become more prepared and also perhaps gain from British air support. Even if German gains to the east in the alt-1940 go as deep from the German starting point of Barbarossa, this is still an improvement for the Poles.

Sure Poland could get overrun completely in that case, but the Germans never get as deep into the USSR or damage it as much. The USSR can recover much more quickly, and even with a murderous Nazi occupation, it is likely in place for a much shorter timespan before the Soviets' counteroffensives drive out the Nazis. Every year or month less the Polish population suffers under Nazi occupation is a good thing from that population's perspective.