3

"Inside the C++ Object Model" says that the offset of a data member in a class is always 1 more than the actual offset in order to distinguish between the pointer to 0 and the pointer to the first data member, here is the example:

class Point3d {
public:
     virtual ~Point3d();

public:
    static Point3d origin;
    float x, y, z;
};
//to be used after, ignore it for the first question
int main(void) {        
    /*cout << "&Point3d::x = " << &Point3d::x << endl;
    cout << "&Point3d::y = " << &Point3d::y << endl;
    cout << "&Point3d::z = " << &Point3d::z << endl;*/
    printf("&Point3d::x = %p\n", &Point3d::x);
    printf("&Point3d::y = %p\n", &Point3d::y);
    printf("&Point3d::z = %p\n", &Point3d::z);
    getchar();
}

So in order to distinguish the two pointers below, the offset of a data member is always 1 more.

float Point3d::*p1 = 0;
float Point3d::*p2 = &Point3d::x;

The main function above is attempt to get the offset of the members to verify this argument, which is supposed to output: 5, 9, 13(Consider the vptr of 4bytes at the beginning). In MS Visual Studio 2012 however, the output is:

&Point3d::x = 00000004
&Point3d::y = 00000008
&Point3d::z = 0000000C

Question: So is MS C++ compiler did some optimization or something to prevent this mechanism?

Joey.Z
  • 4,492
  • 4
  • 38
  • 63
  • seems some gotcha..is it because that there is already a vptr there in the beginning of the class, so we don't need this mechanism any more? – Joey.Z Apr 27 '13 at 15:49
  • 1
    if "Inside the C++ Object Model" says that the address of the object is different from the address of it's first member, it's wrong. I think more likely, you misunderstood what it was saying. – Mooing Duck Apr 27 '13 at 15:50
  • @MooingDuck No, this is a syntax that gives the offset of data members, don't need a object at all. – Joey.Z Apr 27 '13 at 15:51
  • @MooingDuck: Yes, you can get a pointer-to-member without an object. – Mike Seymour Apr 27 '13 at 15:52
  • @MikeSeymour: Huh, I never knew that. [Seems to work](http://coliru.stacked-crooked.com/view?id=3e1f3732c0da58fd62b07962bfe592fc-50d9cfc8a1d350e7409e81e87c2653ba), but are the pointers are being converted to booleans? – Mooing Duck Apr 27 '13 at 15:52
  • @MooingDuck you can refer to this http://stackoverflow.com/questions/3478025/c-pointer-to-data-member-address-doubt – Joey.Z Apr 27 '13 at 15:52
  • @MikeSeymour sorry, I should use printf instead of cout, because cout didn't take a float Point3d::* as it's right operant, I have update the code, you can try again! – Joey.Z Apr 27 '13 at 15:55
  • @MooingDuck: It looks like your compiler converts them to booleans, but perhaps the OP's has a non-standard overload of `<<` for member-pointers. – Mike Seymour Apr 27 '13 at 15:55
  • @MikeSeymour I have edit the code at the shared link you give – Joey.Z Apr 27 '13 at 15:59

6 Answers6

4

tl;dr

Inside the C++ Object Model is a very old book, and most of its contents are implementation details of a particular compiler anyway. Don't worry about comparing your compiler to some ancient compiler.

Full version

An answer to the question linked to in a comment on this question addresses this quite well.

The offset of something is how many units it is from the start. The first thing is at the start so its offset is zero.

[...]

Note that the ISO standard doesn't specify where the items are laid out in memory. Padding bytes to create correct alignment are certainly possible. In a hypothetical environment where ints were only two bytes but their required alignment was 256 bytes, they wouldn't be at 0, 2 and 4 but rather at 0, 256 and 512.


And, if that book you're taking the excerpt from is really Inside the C++ Object Model, it's getting a little long in the tooth.

The fact that it's from '96 and discusses the internals underneath C++ (waxing lyrical about how good it is to know where the vptr is, missing the whole point that that's working at the wrong abstraction level and you should never care) dates it quite a bit.

[...]

The author apparently led the cfront 2.1 and 3 teams and, while this books seems of historical interest, I don't think it's relevant to the modern C++ language (and implementation), at least those bits I've read.

Community
  • 1
  • 1
  • I am just curious about how the c++ program is built to a executable. And I think the "Inside the C++ Object Model" is very attractive to me , although it's old ...hah – Joey.Z Apr 27 '13 at 16:10
3

The language doesn't specify how member-pointers are represented, so anything you read in a book will just be an example of how they might be represented.

In this case, as you say, it sounds like the vptr occupies the first four bytes of the object; again, this is not something specified by the language. If that is the case, no accessible members would have an offset of zero, so there's no need to adjust the offsets to avoid zero; a member-pointer could simply be represented by the member's offset, with zero reserved for "null". It sounds like that is what your compiler does.

You may find that the offsets for non-polymorphic types are adjusted as you describe; or you may find that the representation of "null" is not zero. Either would be valid.

Mike Seymour
  • 249,747
  • 28
  • 448
  • 644
  • **I think** what the author said in the book is similarly to the other implementations in many ways, in fact many other implementations had taken advantage of their's. Some like the virtual table is of general significance. Still, it is helpful for me to understand what's going on underneath my program, it's a good book I think. And thank you for your answer. – Joey.Z Apr 29 '13 at 00:35
2
class Point3d {
public:
     virtual ~Point3d();

public:
    static Point3d origin;
    float x, y, z;
};

Since your class contains a virtual destructor, and (most of) the compiler(s) typically puts a pointer to the virtual function table as the first element in the object, it makes sense that the first of your data is at offset 4 (I'm guessing your compiler is a 32-bit compiler).

Note however that the C++ standard does not stipulate how data members should be stored inside the class, and even less how much space, if any, the virtual function table should take up.

[And yes, it's invalid (undefined behaviour) to take the address of a element that is not to a "real" member object, but I don't think this is causing an issue in this particular example - it may with a different compiler or on a different processor architecture, etc]

Mats Petersson
  • 126,704
  • 14
  • 140
  • 227
1

Unless you specify a different alignment, your expectation of the offset bing 5, ... would be wwong anyway. Normaly the adresses of bigger elements than char are usually aligned on even adresses and I guess even to the next 4-byte boundary. The reason is efficiency of accessing the memory in the CPU. On some architectures, accessing an odd address could cause an exception (i.e. Motorola 68000), depending on the member, or at least a performance slowdown.

Devolus
  • 21,661
  • 13
  • 66
  • 113
1

While it's true that the a null pointer of type "pointer to member of a given type" must be different from any non-null value of that type, offsetting non-null pointers by one is not the only way that a compiler can ensure this. For example, my compiler uses a non-zero representation of null pointer-to-members.

namespace {
struct a {
    int x, y;
};
}

#include <iostream>

int main() {
    int a::*p = &a::x, a::*q = &a::y, a::*r = nullptr;

    std::cout << "sizeof(int a::*) = " << sizeof(int a::*)
              << ", sizeof(unsigned long) = " << sizeof(long);

    std::cout << "\n&a::x = " << *reinterpret_cast<long*>(&p)
              << "\n&a::y = " << *reinterpret_cast<long*>(&q)
              << "\nnullptr = " << *reinterpret_cast<long*>(&r)
              << '\n';
}

Produces the following output:

sizeof(int a::*) = 8, sizeof(unsigned long) = 8
&a::x = 0
&a::y = 4
nullptr = -1

Your compiler is probably doing something similar, if not identical. This scheme is probably more efficient for most 'normal' use cases for the implementation because it won't have to do an extra "subtract 1" every time you use a non-null pointer-to-member.

CB Bailey
  • 755,051
  • 104
  • 632
  • 656
1

That book (available at this link) should make it much clearer that it is just describing a particular implementation of a C++ compiler. Details like the one you mention are not part of the C++ language specification -- it's just how Stanley B. Lippman and his colleagues decided to implement a particular feature. Other compilers are free to do things a different way.

TonyK
  • 16,761
  • 4
  • 37
  • 72
  • Yeah, I know that, but what the author said in the book is similarly to the other implementations in many ways, in fact many other implementations had taken advantage of their's. Some like the virtual table is of general significance. Still, it is helpful for me to understand what's going on underneath my program, it's a good book I think. – Joey.Z Apr 29 '13 at 00:30
  • You know that? Well what are you posting here for then?! – TonyK May 01 '13 at 16:44
  • What I said is just my experience gain from the book, I mean that I know this book is implementation specific, actually it's mentioned in the very beginning of the book that the it's basically a detailed description of cfront. I am arguing that despite of this implementation specific issue, I still think this is a good book. BTW, I did NOT ask why is my experiment disagree with the book. Actually I am asking are there some other different tricks used in Microsoft Visual C++. So, didn't mean to offensive :) – Joey.Z May 02 '13 at 02:31