is this portable

  • Thread starter Martin Vorbrodt
  • Start date
G

Ganesh

Hi there. Is this code portable between platforms? Is it also 100% standard
compliant?
Is it save to get the address of the first member, and do pointer arithmetic
on it to get to all 3 elements?

You are assuming that the three members will be allocated contiguously
and hence access it using array indexing from the address of first
member. Though standard (9.2 para 12) ensures that "a (non-union)
class declared without an intervening access-specifier are allocated
so that later members have higher addresses within a class object",
the problem is "Implementation alignment requirements might cause two
adjacent members not to be allocated immediately after each other".
And treating class members as if it were an array doing pointer
arithmetic is undefined behaviour, and hence is unsafe, and
non-portable.

-Ganesh
 
J

James Dennett

Martin said:
Hi there. Is this code portable between platforms? Is it also 100% standard
compliant?

#include <iostream>
using namespace std;

class Point {
public:
enum COORDS { X = 0, Y, Z };
Point(int x, int y, int z) : _x(x), _y(y), _z(z) {}

const int& operator[](COORDS c) const
{ return (&_x)[c]; }

private:
int _x, _y, _z;
};

int main() {
Point p(1, 2, 3);

cout << p[Point::X] << endl;
cout << p[Point::Y] << endl;
cout << p[Point::Z] << endl;
}

Is it save to get the address of the first member, and do pointer arithmetic
on it to get to all 3 elements?

No, there may be padding between _x, _y and _z.

-- James
 
F

Francis Glassborow

Chris said:
Martin Vorbrodt said:
Hi there. Is this code portable between platforms? Is it also 100% standard
compliant? ...

class Point {
public:
enum COORDS { X = 0, Y, Z };
Point(int x, int y, int z) : _x(x), _y(y), _z(z) {}

const int& operator[](COORDS c) const
{ return (&_x)[c]; }

private:
int _x, _y, _z;
};

No. Absolutely not. The address of _x tells you where _x is located.

And though it might work in the context of a class, it is a dangerous
way to think. I once had to do major maintenance on some C code because
the original author assumed that:

int a, array[10];

And used that assumption for 11 global combinations of simple and array
variable.


allowed him to access array[0] as a[1], which was untrue on the compiler
I was using (I think it put arrays somewhere else entirely to minimise
stack use) In C++ you can do something like this:

int array[11];
int & a = array[0];

Or in your case, if you really want to go down this path:

int data[3]
int & _x;
int & _y;
int & _z;

and define the relationship in the ctor init list. But I cannot imagine
what it would gain you.
 
D

Duane Hebert

Chris Uzdavinis said:
Or alternately,

class Point {
public:
enum COORDS { X = 0, Y, Z };
Point(int x, int y, int z) { xyz[X] = x; xyz[Y] = y; xyz[Z]
= z;}

const int& operator[](COORDS c) const
{ return xyz[c]; }

private:
int xyz[3];
};

In this second case, member xyz is an array and it's thereforce safe
to index into. (Assuming the argument to operator[] is in range, of
course.)

And it has the (IMO) additional benefit of not implicitly casting
the enum to an int.
 
P

Phlip

Alf said:
* Martin Vorbrodt:
Hi there. Is this code portable between platforms? Is it also 100% standard
compliant?

#include <iostream>
using namespace std;

class Point {
public:
enum COORDS { X = 0, Y, Z };
Point(int x, int y, int z) : _x(x), _y(y), _z(z) {}

const int& operator[](COORDS c) const
{ return (&_x)[c]; }

No (because of padding & alignment issues), and no (it's Undefined
Behavior).

I think this is a "yes". Point is a PODs, and padding is
implementation-defined. Hence, on platforms where it's not undefined
behavior, it's well-defined.
 
A

Alf P. Steinbach

* Phlip:
Alf said:
* Martin Vorbrodt:
Hi there. Is this code portable between platforms? Is it also 100%
standard compliant?

#include <iostream>
using namespace std;

class Point {
public:
enum COORDS { X = 0, Y, Z };
Point(int x, int y, int z) : _x(x), _y(y), _z(z) {}

const int& operator[](COORDS c) const
{ return (&_x)[c]; }
private:
int _x, _y, _z;
};

No (because of padding & alignment issues), and no (it's Undefined
Behavior).

I think this is a "yes". Point is a PODs, and padding is
implementation-defined. Hence, on platforms where it's not undefined
behavior, it's well-defined.

The term "Undefined Behavior" usually refers to the Holy Standard, and that's
the way I used it -- UB in that sense is absolute, not platform-dependent.

As I understand it what you mean by "Undefined Behavior", in this case, is
whether a given C++ implementation defines the behavior.

That does not affect portability between platforms (the first "no"), nor does
it affect standard compliance (the second "no").
 
P

Phlip

Alf said:
The term "Undefined Behavior" usually refers to the Holy Standard, and that's
the way I used it -- UB in that sense is absolute, not platform-dependent.

As I understand it what you mean by "Undefined Behavior", in this case, is
whether a given C++ implementation defines the behavior.

That does not affect portability between platforms (the first "no"), nor does
it affect standard compliance (the second "no").

No, I mean "the implementors shall define the padding in a PODs." Defined
behavior. _Not_ "whatever my compiler happens to do."

I don't know why nobody mentioned PODs in the original answers. They cover
this situation.

AFAIK, x[1] is the same as i[x] is the same as *(x + 1), hence IF a pointer
points to a valid object, dereferencing the pointer is defined, hence
indexing the pointer can also be defined.

The IF part is covered by the rules for a PODs and for implementation
defined padding rules.

And I don't have the Standard to hand, so I await a real Language Lawyer to
trump us both (without the moderation delay).
 
W

Walt Karas

Martin Vorbrodt said:
Hi there. Is this code portable between platforms? Is it also 100% standard
compliant?

#include <iostream>
using namespace std;

class Point {
public:
enum COORDS { X = 0, Y, Z };
Point(int x, int y, int z) : _x(x), _y(y), _z(z) {}

const int& operator[](COORDS c) const
{ return (&_x)[c]; }

private:
int _x, _y, _z;
};

int main() {
Point p(1, 2, 3);

cout << p[Point::X] << endl;
cout << p[Point::Y] << endl;
cout << p[Point::Z] << endl;
}

Is it save to get the address of the first member, and do pointer arithmetic
on it to get to all 3 elements?

Thanx
Martin
....

Whether it's standard or not, it's very desirable that this sort
of code should work with a compiler that's going to be used in
an embedded environment. It's a fairly common technique to use
a struct to represent the register map of a memory-mapped IC, then
cast the IC's base address to be a pointer to the struct type.

More generally, there is alot of legacy C code that assumes that
if two structs have a sequence of data members as a "common prefix",
then these data members will be at the same offsets in the two
structs. It's kind of a neaderthal version of a base class.
 
A

Alf P. Steinbach

* Phlip:
No, I mean "the implementors shall define the padding in a PODs." Defined
behavior. _Not_ "whatever my compiler happens to do."

There is no such wording in the standard.


I don't know why nobody mentioned PODs in the original answers. They cover
this situation.

PODness isn't relevant to the questions asked (portability, conformance).


AFAIK, x[1] is the same as i[x] is the same as *(x + 1),

Correct for the built-in indexing operator, yes.

hence IF a pointer
points to a valid object, dereferencing the pointer is defined,
Correct.


hence indexing the pointer can also be defined.

Correct apart from the word "hence".

Can be defined, yes.

Is defined in general, no.

The IF part is covered by the rules for a PODs
Incorrect.


and for implementation defined padding rules.
Incorrect.


And I don't have the Standard to hand, so I await a real Language Lawyer to
trump us both (without the moderation delay).

That would probably not have passed moderation in clc++m, which is why
it was posted here?
 
S

Seungbeom Kim

Walt said:
Whether it's standard or not, it's very desirable that this sort
of code should work with a compiler that's going to be used in
an embedded environment. It's a fairly common technique to use
a struct to represent the register map of a memory-mapped IC, then
cast the IC's base address to be a pointer to the struct type.

The technique must have been provided by the specific implementation,
which defines the (originally undefined) behaviour and knows what it's
doing. Therefore, it neither is portable nor should be portable. The
desirability does not lead to the necessity of the standard's support.
More generally, there is alot of legacy C code that assumes that
if two structs have a sequence of data members as a "common prefix",
then these data members will be at the same offsets in the two
structs. It's kind of a neaderthal version of a base class.

I would say that, if they wanted to emulate inheritance in C, they
should have made the common (base) part into a struct and let it
included as the first member of each derived ones.

struct base { /* ... */ };
struct derived_A { struct base base_part; /* A part ... */ };
struct derived_B { struct base base_part; /* B part ... */ };

struct derived_A* pD = /* get some derived object */;
struct base* pB = &pD->base_part;

Isn't this much better than relying on undefined behaviour?

I'm not sure whether "(struct base*) pD" is guaranteed to work or not,
though.
 
K

kanze

Seungbeom Kim said:
Walt Karas wrote:
The technique must have been provided by the specific implementation,
which defines the (originally undefined) behaviour and knows what it's
doing. Therefore, it neither is portable nor should be portable. The
desirability does not lead to the necessity of the standard's support.
I would say that, if they wanted to emulate inheritance in C, they
should have made the common (base) part into a struct and let it
included as the first member of each derived ones.

The question isn't so much what you should do, today. Today, if you
want base classes, it shouldn't be to hard to find a C++ compiler, and
do them right.

The question is what people actually did, many years ago, before there
was even a C standard. The C++ standard explicitly contains support for
common initial sequences, at least in certain conditions, because C
gives that support. And C gives it because it was a common technique in
C programs when C was being standardized.
struct base { /* ... */ };
struct derived_A { struct base base_part; /* A part ... */ };
struct derived_B { struct base base_part; /* B part ... */ };
struct derived_A* pD = /* get some derived object */;
struct base* pB = &pD->base_part;
Isn't this much better than relying on undefined behaviour?

In one frequent case, "struct base" was actually just an enum, e.g.:

enum NodeType
{
nt_constant,
nt_variable,
nt_operator,
/* ... */
} ;

struct AbstractNode { NodeType type ; } ;
struct ConstantNode { NodeType type ; /* ... */ } ;
struct VariableNode { NodeType type ; /* ... */ } ;

The code would be something like:

void
processNode( AbstractNode* node )
{
switch ( node->type )
{
case nt_constant :
processConstantNode( (ConstantNode*)node ) ;
break ;
/* ... */
}
}
I'm not sure whether "(struct base*) pD" is guaranteed to work or not,
though.

I'm not sure in this particular case, but there are cases where it is
guaranteed. In practice, it will work, and things like the above were
not all that rare before we got C++.
 
W

Walt Karas

Seungbeom Kim said:
The technique must have been provided by the specific implementation,
which defines the (originally undefined) behaviour and knows what it's
doing. Therefore, it neither is portable nor should be portable. The
desirability does not lead to the necessity of the standard's support.


I would say that, if they wanted to emulate inheritance in C, they
should have made the common (base) part into a struct and let it
included as the first member of each derived ones.

struct base { /* ... */ };
struct derived_A { struct base base_part; /* A part ... */ };
struct derived_B { struct base base_part; /* B part ... */ };

struct derived_A* pD = /* get some derived object */;
struct base* pB = &pD->base_part;

Isn't this much better than relying on undefined behaviour?

I'm not sure whether "(struct base*) pD" is guaranteed to work or not,
though.

Having the common prefix be a struct is more readable, but it
isn't really relevant to the issue of whether the compiler is
permitted to reorder data members of a class in the memory
layout of class instances.

Obviously there are cases where reordering data members would
save wasted "pad" bytes, for example:

class X
{
char a;
int b;
char c;
};

To avoid breaking old C code, maybe the rule should be
that reordering is permitted except in structs with no base
classes and no virtual functions.
 
E

E. Robert Tisdale

Martin said:
Is this code portable between platforms?
Yes.

Is it also 100% standard compliant?
No.

#include <iostream>
using namespace std;

class Point {
public:
enum COORDS { X = 0, Y, Z };
Point(int x, int y, int z) : _x(x), _y(y), _z(z) { }

const int& operator[](COORDS c) const {
return (&_x)[c]; }

private:
int _x, _y, _z;
};

int main() {
Point p(1, 2, 3);

cout << p[Point::X] << endl;
cout << p[Point::Y] << endl;
cout << p[Point::Z] << endl;
}

Is it [safe] to get the address of the first member,
and do pointer arithmetic on it to get to all 3 elements?

Probably.
 

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
473,769
Messages
2,569,580
Members
45,054
Latest member
TrimKetoBoost

Latest Threads

Top