Memory layout question

M

Markus Ilmola

Is this safe:

class vector
{
public:
float x, y, z;
float *pointer() { return &x; }
};

vector v;
v.pointer()[0] = 123862; // Set x
v.pointer()[1] = 227364; // Set y
v.pointer()[2] = 3495; // Set z


(Are x, y and z always ordered in the memory like members of an array?)
 
V

Victor Bazarov

Markus said:
Is this safe:

class vector
{
public:
float x, y, z;
float *pointer() { return &x; }
};

vector v;
v.pointer()[0] = 123862; // Set x
v.pointer()[1] = 227364; // Set y
v.pointer()[2] = 3495; // Set z


(Are x, y and z always ordered in the memory like members of an
array?)

The object is organized that way, yes. The address of 'y' in
fact succeeds the address of 'x'. I am not sure about padding,
however. It is implementation-defined whether there is any
padding between elements of the class/struct.

V
 
C

Craig Scott

Is this safe:

class vector
{
public:
float x, y, z;
float *pointer() { return &x; }

};

To prevent anyone from misreading your code, I'd recommend that you
call your class something other than "vector" since it could be
mistaken for std::vector all too easily. For a class with behavior (ie
not just data members but also has member functions), most people
would advise that you make the variables private and include accessor
(ie getX()/setX() ) functions.

These two recommendations are not "unsafe" from an "undefined
behavior" point of view, but they will lead to more robust,
maintainable code.
vector v;
v.pointer()[0] = 123862; // Set x
v.pointer()[1] = 227364; // Set y
v.pointer()[2] = 3495; // Set z

(Are x, y and z always ordered in the memory like members of an array?)

As Victor has mentioned in his posted reply, order is guaranteed,
padding is not. Perhaps you may want to consider overloading the []
operator if you want to treat your class like this. For example (and
this is not necessarily a good implementation!):

class MyVector
{
float x, y, z;
public:
float& operator[](int i)
{
assert(i >= 0);
assert(i < 3);
switch (i)
{
case 0: return x;
case 1: return y;
default: return z;
}
}
const float& operator[](int i) const
{
// Same code as above....
}
};

MyVector v;
v[0] = 123862; // Set x
v[1] = 227364; // Set y
v[2] = v[0]; // Set z to same value as x
 
J

Jack Klein

Is this safe:

class vector

Well, it's not particularly safe to name a class of your own "vector",
but I suppose that the poor choice for an example name is not relevant
to you real question.
{
public:
float x, y, z;
float *pointer() { return &x; }
};

vector v;
v.pointer()[0] = 123862; // Set x
v.pointer()[1] = 227364; // Set y
v.pointer()[2] = 3495; // Set z


(Are x, y and z always ordered in the memory like members of an array?)

The answer to your question is yes and no.

Member objects inside a class or struct in C++ are located in
ascending address as long as they are not separated by an access
specifier, so in your case x, y, and z are located at ascending
addresses.

If you defined your class like this:

class not_vector
{
public: float x;
private: float y;
public: float z;
};

....then the C++ language gives you no guarantees at all about the
relative location of the members. Not even between x and z, which are
both public.

The no part of the answer is that while x, y, and z will be located at
ascending addresses in an object of the class type, they will not
necessarily be "ordered in the memory like members of an array".

No padding is allowed between the members of an array, but the
compiler is allowed to insert arbitrary padding between the members of
a struct or class.

It is not likely, but it is possible, that the compiler could place
the members in an object of your class like this:

float x;
/* sizeof (float) padding bytes */
float y;
/* sizeof (float) padding bytes */
float z;
/* maybe more padding bytes */

The point is that the pointer returned by your member function will
indeed point to x, but there is no guarantee that v.pointer()[1]
points to y and/or that v.pointer()[2] points to z.

Don't do this. If you need to access the data in your class as an
array, define it as an array.

--
Jack Klein
Home: http://JK-Technology.Com
FAQs for
comp.lang.c http://c-faq.com/
comp.lang.c++ http://www.parashift.com/c++-faq-lite/
alt.comp.lang.learn.c-c++
http://www.club.cc.cmu.edu/~ajo/docs/FAQ-acllc.html
 
K

Kai-Uwe Bux

Markus said:
Is this safe:

class vector
{
public:
float x, y, z;
float *pointer() { return &x; }
};

vector v;
v.pointer()[0] = 123862; // Set x
v.pointer()[1] = 227364; // Set y
v.pointer()[2] = 3495; // Set z

No, for two reasons:

a) There might be padding.

b) The implementation is allowed to do range checking on pointers. Since x
is not part of an array, the expression v.pointer()[1] may trigger an
out-of-bounds assert or exception. Formally, you have undefined behavior.

(Are x, y and z always ordered in the memory like members of an array?)

Yes, as far as the order is concerned; but that does not cover padding.


As for your problem, you could do something like this:

#include <cstddef>
#include <cassert>

struct point {

float x, y, z;

float const & operator[] ( std::size_t i ) const {
assert( i < 3 );
return ( (*this).*proxy() );
}

float & operator[] ( std::size_t i ) {
assert( i < 3 );
return ( (*this).*proxy() );
}

private:

static
float point::* * proxy ( void ) {
static float point::* dummy [3] =
{ &point::x, &point::y, &point::z };
return ( dummy );
}

};


#include <iostream>

int main ( void ) {
std::cout << sizeof( point ) << '\n';
point p;
p.x = 3.0;
p.y = 2.0;
p.z = 1.0;
std::cout << p[1] << '\n';
}


Best

Kai-Uwe Bux
 
J

Jerry Coffin

Is this safe:

class vector
{
public:
float x, y, z;
float *pointer() { return &x; }
};

vector v;
v.pointer()[0] = 123862; // Set x
v.pointer()[1] = 227364; // Set y
v.pointer()[2] = 3495; // Set z


(Are x, y and z always ordered in the memory like members of an array?)

As has already been pointed out, they can be separated by padding. You
can achieve (nearly) the same thing by reversing the roles though:

struct vector {
float pointer[3];
float &x, &y, &z;

vector() : x(pointer[0]), y(pointer[1]), z(pointer[2]) {}
};
 
K

Kai-Uwe Bux

Jerry said:
Is this safe:

class vector
{
public:
float x, y, z;
float *pointer() { return &x; }
};

vector v;
v.pointer()[0] = 123862; // Set x
v.pointer()[1] = 227364; // Set y
v.pointer()[2] = 3495; // Set z


(Are x, y and z always ordered in the memory like members of an array?)

As has already been pointed out, they can be separated by padding. You
can achieve (nearly) the same thing by reversing the roles though:

struct vector {
float pointer[3];
float &x, &y, &z;

vector() : x(pointer[0]), y(pointer[1]), z(pointer[2]) {}
};

That can be costly. I tried

#include <iostream>

struct vector_a {
float pointer[3];
float &x, &y, &z;

vector_a() : x(pointer[0]), y(pointer[1]), z(pointer[2]) {}
};

struct vector_b {
float pointer [3];
};


int main ( void ) {
std::cout << "sizeof( vector_a ) = " << sizeof( vector_a ) << '\n'
<< "sizeof( vector_b ) = " << sizeof( vector_b ) << '\n';
}


and found that vector_a has twice the size of vector_b on my platform.


Best

Kai-Uwe Bux
 
L

LR

Kai-Uwe Bux said:
Jerry said:
Is this safe:

class vector
{
public:
float x, y, z;
float *pointer() { return &x; }
};

vector v;
v.pointer()[0] = 123862; // Set x
v.pointer()[1] = 227364; // Set y
v.pointer()[2] = 3495; // Set z


(Are x, y and z always ordered in the memory like members of an array?)
As has already been pointed out, they can be separated by padding. You
can achieve (nearly) the same thing by reversing the roles though:

struct vector {
float pointer[3];
float &x, &y, &z;

vector() : x(pointer[0]), y(pointer[1]), z(pointer[2]) {}
};

That can be costly.


What about:

struct vector {
float x,y,z;
float &operator[](const size_t index) {
if(index > 2)
throw std::eek:ut_of_range("Index out of range");
return
index == 0 ? x :
index == 1 ? y :
z;
}
};

Can be used like:

vector v;
v[0] = 123862;
v[1] = 227364;
v[2] = 3495;

I guess if you really have to do something like:
v.pointer()[0] = ...
you could have pointer return some object that has an operator[] member.

I'm curious though as to why not either have member functions like:
float &xr() { return x; }
float &yr() { return y; }
float &zr() { return z; }

or have a ctor like:
vector(const float f1, const float f2, const float f3)
: x(f1), y(f2), z(f3)
{}

LR
 
J

Jerry Coffin

Jerry Coffin wrote:

[ ... ]
struct vector {
float pointer[3];
float &x, &y, &z;

vector() : x(pointer[0]), y(pointer[1]), z(pointer[2]) {}
};

That can be costly. I tried

[ test code elided ... ]
and found that vector_a has twice the size of vector_b on my platform.

Quite true -- when/if this is a concern, you can use functions instead:

struct vector {
float pointer[3];

float &x() { return pointer[0]; }
float &y() { return pointer[1]; }
float &z() { return pointer[2]; }
};

Of course, as noted elsethread, overloading operator[] works perfectly
well also.
 
K

Kai-Uwe Bux

LR said:
Kai-Uwe Bux said:
Jerry said:
Is this safe:

class vector
{
public:
float x, y, z;
float *pointer() { return &x; }
};

vector v;
v.pointer()[0] = 123862; // Set x
v.pointer()[1] = 227364; // Set y
v.pointer()[2] = 3495; // Set z


(Are x, y and z always ordered in the memory like members of an array?)
As has already been pointed out, they can be separated by padding. You
can achieve (nearly) the same thing by reversing the roles though:

struct vector {
float pointer[3];
float &x, &y, &z;

vector() : x(pointer[0]), y(pointer[1]), z(pointer[2]) {}
};

That can be costly.


What about:

struct vector {
float x,y,z;
float &operator[](const size_t index) {
if(index > 2)
throw std::eek:ut_of_range("Index out of range");
return
index == 0 ? x :
index == 1 ? y :
z;
}
};

Can be used like:

vector v;
v[0] = 123862;
v[1] = 227364;
v[2] = 3495;

That's fine. I gave a similar solution elsethread.

One should remark, however, that overloading operator[] is not really all
that useful. The nice thing about the original idea of having an array of
values is that one could use the STL algorithms on the sequence. For
instance, one could use std::lexicographic_compare() to implement operator<
for this vector thingy.

I guess if you really have to do something like:
v.pointer()[0] = ...
you could have pointer return some object that has an operator[] member.

I'm curious though as to why not either have member functions like:
float &xr() { return x; }
float &yr() { return y; }
float &zr() { return z; }

Personally, I like that (one should add const versions of these). The
drawback is that certain expressions get cluttered with empty parentheses
pairs. However, it allows for all that is needed.
or have a ctor like:
vector(const float f1, const float f2, const float f3)
: x(f1), y(f2), z(f3)
{}

That's not "or". One should have this _and_ some of the other stuff.


Thus, here is a new proposal:

#include <cstddef>
#include <cassert>
#include <algorithm>

class point {

float coordinate [3];

public:

typedef float value_type;
typedef value_type & reference;
typedef value_type const & const_reference;
typedef value_type * pointer;
typedef value_type const * const_pointer;


reference x ( void ) {
return ( coordinate[0] );
}

const_reference x ( void ) const {
return ( coordinate[0] );
}

reference y ( void ) {
return ( coordinate[1] );
}

const_reference y ( void ) const {
return ( coordinate[1] );
}

reference z ( void ) {
return ( coordinate[2] );
}

const_reference z ( void ) const {
return ( coordinate[2] );
}

point ( float x_, float y_, float z_ )
{
x() = x_;
y() = y_;
z() = z_;
}


pointer begin ( void ) {
return ( &coordinate[0] );
}

const_pointer begin ( void ) const {
return ( &coordinate[0] );
}

pointer end ( void ) {
return ( begin() + 3 );
}

const_pointer end ( void ) const {
return ( begin() + 3 );
}

reference operator[] ( std::size_t i ) {
assert ( i < 3 );
return ( coordinate );
}

const_reference operator[] ( std::size_t i ) const {
assert( i < 3 );
return ( coordinate );
}

};

bool operator< ( point const & lhs, point const & rhs ) {
return ( std::lexicographical_compare( lhs.begin(), lhs.end(),
rhs.begin(), rhs.end() ) );
}

#include <iostream>
#include <iomanip>


int main ( void ) {
point p ( 1, 1, 1 );
point q ( 1, 1, 1.1 );
std::cout << std::boolalpha << ( p < q ) << '\n';
}


Alternatively, one can cook up an iterator class to go with the other
solutions based upon doing something funny (like a switch statement) with
operator[].


Best

Kai-Uwe Bux
 
J

Juha Nieminen

Craig said:
As Victor has mentioned in his posted reply, order is guaranteed,

I wonder why. Assume that a compiler sees that by reordering the
elements in a class/struct it could become more efficient. But since
the standard guarantees that the order is preserved, the compiler
can't do it. But why does the standard say this?
 
V

Victor Bazarov

Juha said:
I wonder why. Assume that a compiler sees that by reordering the
elements in a class/struct it could become more efficient. But since
the standard guarantees that the order is preserved, the compiler
can't do it. But why does the standard say this?

Consider asking the "why" questions in 'comp.std.c++'. They know
(and discuss) the rationales behind all elements of the Standard,
even the smallest ones. I can only speculate that reordering of
members in a struct would definitely interfere with how 'offsetof'
macro would work and how binary streaming of C structs would be
totally broken (once the struct is written, there would be no way
to read it back if you don't know the order of members).

V
 
J

James Kanze

Consider asking the "why" questions in 'comp.std.c++'. They know
(and discuss) the rationales behind all elements of the Standard,
even the smallest ones. I can only speculate that reordering of
members in a struct would definitely interfere with how 'offsetof'
macro would work and how binary streaming of C structs would be
totally broken (once the struct is written, there would be no way
to read it back if you don't know the order of members).

There's no way now, since you don't know where and if padding is
present. (I'll admit that I have no idea why this rule is
present, either. I can't think of any real use one could
possibly make of it in a program.)
 
?

=?ISO-8859-1?Q?Erik_Wikstr=F6m?=

There's no way now, since you don't know where and if padding is
present. (I'll admit that I have no idea why this rule is
present, either. I can't think of any real use one could
possibly make of it in a program.)

I would guess for binary compatibility, there are times when you might
want to write data in a binary format to files but do not require
platform independence. One example might be to communicate large amounts
of data between different processes, on disk temp storage, or simply to
store data. Efficiency can be gained if one can simply read/write
structs instead of going through all the steps needed for proper
serialisation, of course if it's non-PODs or the structs have pointers
to other variables this is not possible. What I am trying to say is that
if the order of the members are not well defined, different
configurations (optimisations etc.) would make that impossible.
 
J

James Kanze

I would guess for binary compatibility, there are times when you might
want to write data in a binary format to files but do not require
platform independence.

But imposing the order doesn't change anything here. If you're
compiling both the writer and the reader with exactly the same
compiler (same version, same options, on the same machine), then
the layouts (and representations) will be exactly the same, both
with regards to order and with regards to padding. Throw in a
different option, or update the compiler, and who knows. As
I've mentionned before, in at least one case, a compiler changed
the byte order of long between versions. And a lot of compilers
have options which affect the padding (or, for that matter, the
size of a long).
One example might be to communicate large amounts
of data between different processes, on disk temp storage, or simply to
store data. Efficiency can be gained if one can simply read/write
structs instead of going through all the steps needed for proper
serialisation, of course if it's non-PODs or the structs have pointers
to other variables this is not possible. What I am trying to say is that
if the order of the members are not well defined, different
configurations (optimisations etc.) would make that impossible.

To make such code work, you need more than just the same order.
You need the same padding, and the same representation. If the
layout and the representation are allowed to vary in any way, I
don't see what one minor restriction buys you.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
474,201
Messages
2,571,049
Members
47,654
Latest member
LannySinge

Latest Threads

Top