Ask a questionAsk a question
 

AnswerVC++ wrong pointer size ..?

  • Sunday, April 24, 2005 6:03 AMKwon-il Lee Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     

    Hi. I heard this problem from our forum. Some of our guys wrote that this problem occur all VC++ 6.0, VS.NET 2003, VS.NET 2005 Beta2 compilers but not in gcc and dev-C++. Here is code sample and it have to be in saperated files to repro this error. If it is compiled with these three header and two source files, I get wrong class size and memeber variable pointer like this.

    C:24 32
    C:0x003e0f60 0x003e0f78 24
    main:1 8 12
    main:0x0012fed4 0x003e0f68 8

    at C.cpp file, it report sizeof(B) is 24 byte.
    at C.cpp file, it report sizeof(C) is 32 byte.
    at main.cpp file, it report sizeof(C) is 8 byte.
    at main.cpp file, it report sizeof(C) is 12 byte.
    at c.cpp file, constructor of clear C::c member variable at ["offset of C" + 24]
    at main.cpp file, new operator only allocate 12 byte because sizeof(C) is 12 byte.

    /////////////////////////////////////////////////////////////////////////////////////////
    // A.h file
    class A // empty class
    {
    };

    /////////////////////////////////////////////////////////////////////////////////////////
    // B.h file
    #pragma once
    class A;
    class B
    {
        void(A::*fp)(); // should have class function pointer
        virtual void f() {} // should have virtual function call
    };


    /////////////////////////////////////////////////////////////////////////////////////////
    // C.h
    #include "B.h"
    class C : public B
    {
    public:
        int c;
        C();
    };
    [/code]

    /////////////////////////////////////////////////////////////////////////////////////////
    // C.cpp
    #include "C.h"
    #include <stdio.h>
    C::C()
    : c(0) // by listing file this make wrong memory access. it will break heap memory.
    {
        printf("C:%d %d\n", sizeof(B), sizeof(C));
        printf("C:0x%08x    0x%08x  %d\n", this, &this->c, (char*)&this->c - (char*)this);
    }
    [/code]

    /////////////////////////////////////////////////////////////////////////////////////////
    // main.cpp file
    #include "A.h" // if it is not included, this problem will not occur.
    #include "C.h"
    #include <stdio.h>

    int main()
    {
        printf(""); // if it is not placed, it will raise error at calling first printf(). seem like problem from heap memory corruption.
        C * c = new C;
        printf("main:%d %d %d\n", sizeof(A), sizeof(B), sizeof(C));
        printf("main:0x%08x 0x%08x  %d\n", &c, &c->c, (char*)&c->c - (char*)c);
        char temp;
        scanf("%c", &temp);
        delete c; // fails on delete. both Release and Debug mode.
    }

Answers

  • Sunday, April 24, 2005 8:00 AMMigrationUser 1 Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     Answer
    > Hi. I heard this problem from our forum. Some of our guys wrote that
    > this problem occur all VC++ 6.0, VS.NET 2003, VS.NET 2005 Beta2
    > compilers but not in gcc and dev-C++. Here is code sample and it have
    > to be in saperated files to repro this error. If it is compiled with
    > these three header and two source files, I get wrong class size and
    > member variable pointer like this.

    This is a known issue. See #pragma pointers_to_members, /vm[bgmsv] compiler switches, and __single_inheritance, __multiple_inheritance and __virtual_inheritance keywords. To be on the safe side, use /vmg. The situation is as follows.

    In VC++, under the default /vmb option, the size of the pointer-to-member-function can be 4, 8, 12 or 16 bytes, depending on the complexity of the class hierarchy. It's 4 bytes for the class unsing only single inheritance, 8 for a class with multiple base classes, 12 for a class with virtual base classes, and 16 when, as in your example, a pointer to member is declared for a class whose definition is not known (only forward declaration is available).

    This arrangement works well most of the time for most programs - you get the most compact representation possible, and save on memory overhead. But there are two problems.

    First, it is in violation of C++ standard - the standard requires that one should be able to reinterpret_cast a pointer-to-member-fuction of any type to a pointer-to-member-fuction of any other type and back without loss of information, which obviously can only be done if all such pointers are of the same size. Second, as in your case, great confusion ensues when you declare the pointer-to-member-function in different translation units that don't have the same knowledge about the class (just a forward declaration in one translation unit, full definition in the other).

    There are three ways for you to deal with the problem. First, you can reorganize your sources so that you don't declare pointers-to-members of forward-declared class. Second, you can mark the forward declaration with one of __*_inheritance keywords (the one that matches the actual inheritance complexity of the class, of course). Finally, you can compile with /vmg switch to make all pointers 12 bytes large in all cases.

    -- With best wishes, Igor Tandetnik

All Replies

  • Sunday, April 24, 2005 8:00 AMMigrationUser 1 Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     Answer
    > Hi. I heard this problem from our forum. Some of our guys wrote that
    > this problem occur all VC++ 6.0, VS.NET 2003, VS.NET 2005 Beta2
    > compilers but not in gcc and dev-C++. Here is code sample and it have
    > to be in saperated files to repro this error. If it is compiled with
    > these three header and two source files, I get wrong class size and
    > member variable pointer like this.

    This is a known issue. See #pragma pointers_to_members, /vm[bgmsv] compiler switches, and __single_inheritance, __multiple_inheritance and __virtual_inheritance keywords. To be on the safe side, use /vmg. The situation is as follows.

    In VC++, under the default /vmb option, the size of the pointer-to-member-function can be 4, 8, 12 or 16 bytes, depending on the complexity of the class hierarchy. It's 4 bytes for the class unsing only single inheritance, 8 for a class with multiple base classes, 12 for a class with virtual base classes, and 16 when, as in your example, a pointer to member is declared for a class whose definition is not known (only forward declaration is available).

    This arrangement works well most of the time for most programs - you get the most compact representation possible, and save on memory overhead. But there are two problems.

    First, it is in violation of C++ standard - the standard requires that one should be able to reinterpret_cast a pointer-to-member-fuction of any type to a pointer-to-member-fuction of any other type and back without loss of information, which obviously can only be done if all such pointers are of the same size. Second, as in your case, great confusion ensues when you declare the pointer-to-member-function in different translation units that don't have the same knowledge about the class (just a forward declaration in one translation unit, full definition in the other).

    There are three ways for you to deal with the problem. First, you can reorganize your sources so that you don't declare pointers-to-members of forward-declared class. Second, you can mark the forward declaration with one of __*_inheritance keywords (the one that matches the actual inheritance complexity of the class, of course). Finally, you can compile with /vmg switch to make all pointers 12 bytes large in all cases.

    -- With best wishes, Igor Tandetnik
  • Sunday, April 24, 2005 2:32 PMCarl DanielMVP, ModeratorUsers MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     
    VC++ implements an optimization (strictly speaking, a non-conforming optimization) that provides for 4 different representations of pointer to member.  Which representation is used depends on two factors:  1. The complexity of the class (virtual function, multiple inheritance, virtual bases) and 2. How much the compiler knows about #1.

    In a case where you declare a pointer to member of a class before the class has been defined, the compiler must choose the most general representation.  In a case where you declare a pointer to member after the class definition has been seen, the compiler will choose the appropriate representation.  The compiler will never choose two different representations in a single translation unit, but if you have your code structured such that in some translation units the pmf is declared before the class and in some transation units it's declared after, you'll end up with two different pmf representations for members of a single class.

    The most effective way to deal with this optimization (in terms of final program performance and size) is to never declare pointers to members of incomplete (forward declared) classes.

    If you're unable to do that, VC++ provides two mechanisms to deal with this.  First, there's the /vm_ command-line options.  If you compile with /vmg, for example, VC++ will use the most general representation for all pointers to members.  This is exactly what GCC does all the time.  For a more fine-grained approach, you can use #pragma pointers_to_members to instruct the compiler to use a less general representation.

    #pragma pointers_to_memebers(full_generality, single_inheritance)
    class A;
    class B
    {
      void (A::*fp)();
    };