LinuxQuestions.org

LinuxQuestions.org (/questions/)
-   Programming (https://www.linuxquestions.org/questions/programming-9/)
-   -   C++: Size of a class containing a virtual function (https://www.linuxquestions.org/questions/programming-9/c-size-of-a-class-containing-a-virtual-function-909311/)

Aquarius_Girl 10-21-2011 04:51 AM

C++: Size of a class containing a virtual function
 
Code:

#include <iostream>
using namespace std;

class Z
{
  public:
    int a;
    virtual void x () {}
};

int main()
{
  cout << "\nZ's size: "  << sizeof (Z);
}

Int's size: 4
void ptr inserted by virtual function's size: 8

So, the size of Z should be shown 12, isn't it?
But instead, its being shown 16!

What's the point that I am missing?

NevemTeve 10-21-2011 05:03 AM

Quote:

Originally Posted by Anisha Kaul (Post 4504148)
So, the size of Z should be shown 12, isn't it?
But instead, its being shown 16!

It is 8 on my linux-box.

Aquarius_Girl 10-21-2011 05:14 AM

I have gcc version 4.5.0, with OpenSuse 11.3 (64 bit).

millgates 10-21-2011 05:34 AM

As far as I know, the standard doesn't really specify completely the layout of the class in memory. I guess the first 8 bytes or so will be the pointer to vtable for Z, then 4 bytes member a. if you look at the memory (the 16 bytes taken by an instance of Z), you'll see the last 4 bytes are zero(at least they're 0's on my computer). Now if you add one more int member
Code:

class Z
{
  public:
    int a;
    int b;
    virtual void x () {}
};

the b member will take the last 4 bytes of Z and the size of Z will be also 16.
So I guess it's some kind of padding, the compiler uses 16 bytes instead of 12 for Z with the last 4 being unused. Perhaps it's some kind of optimization for x86_64?

SigTerm 10-21-2011 05:40 AM

Quote:

Originally Posted by Anisha Kaul (Post 4504148)
Int's size: 4
void ptr inserted by virtual function's size: 8

So, the size of Z should be shown 12, isn't it?

Wrong. It is implementation-dependent behavior. The size shown can be anything.

Quote:

Originally Posted by Anisha Kaul (Post 4504148)
What's the point that I am missing?

It is implementation-dependent behavior. Size can be anything (you can expect that it'll be >= sizeof(int), but that's it) and things get really interesting if you use multiple inheritance and the object may have multiple virtual function tables. You should never try to predict how big your virtual class will be. If you need to be sure about size of data, use POD struct for data, and extra class to process the data (or derive that extra class from POD struct).

As for "why it is 16 instead of 12", here are possible scenarios:
  • sizeof(int) == 8, you're on 64bit machine (most likely scenario, by the way)
  • sizeof(int) == 4, you're on 64bit machine, and compiler decided to align structure to 8-byte boundaries.
  • gcc stores extra bytes of data you're not aware of.

Citations from standard:
Quote:

expr.sizeof
...
sizeof(char), sizeof(signed char) and sizeof(unsigned char) are 1; the
result of sizeof applied to any other fundamental type (3.9.1) is implementation-defined.
...
When applied to a class, the result is the number of bytes in an object of that class including any padding required for
placing objects of that type in an array. The size of a most derived class shall be greater than zero
Nothing else is guaranteed.

NevemTeve 10-21-2011 06:09 AM

Quote:

Originally Posted by SigTerm (Post 4504182)
sizeof(int) == 8

If by "sizeof (int)" you mean "sizeof (int)", it won't ever be 8. sizeof (long) can be 4 or 8 depending on platform and OS.

millgates 10-21-2011 06:13 AM

Quote:

Originally Posted by NevemTeve (Post 4504200)
If by "sizeof (int)" you mean "sizeof (int)", it won't ever be 8. sizeof (long) can be 4 or 8 depending on platform and OS.

On x86_64, 64bit linux and gcc sizeof(int) is 4. But where did you get that sizeof(int) will never be 8? As far as I know, the standard specifies nothing except that sizeof(long) >= sizeof(int) >= sizeof(short)>= sizeof(char).

NevemTeve 10-21-2011 06:38 AM

Quote:

Originally Posted by millgates (Post 4504207)
On x86_64, 64bit linux and gcc sizeof(int) is 4. But where did you get that sizeof(int) will never be 8?

You're right, I should have written something like this: 'On any existing windows*/unix platform sizeof(int) will be 4'

*: Okay, except for Win16, where sizeof(int)=2

Sergei Steshenko 10-21-2011 06:41 AM

Quote:

Originally Posted by Anisha Kaul (Post 4504148)
[CODE]
...
What's the point that I am missing?

The fundamental thing you are missing is that the standard doesn't prescribe a mechanism to be used to implement virtual functions.

SigTerm 10-21-2011 09:59 AM

Quote:

Originally Posted by NevemTeve (Post 4504200)
If by "sizeof (int)" you mean "sizeof (int)", it won't ever be 8. sizeof (long) can be 4 or 8 depending on platform and OS.

Sorry, thats not true, and and if you keep believing in fairy-tales like that, one day it'll backfire horribly, you'll lose your job, and the next code maintainer will hate you.

Quote:

Originally Posted by NevemTeve (Post 4504230)
*: Okay, except for Win16, where sizeof(int)=2

Still not true.

There are platforms where int is 64bit, and int is not guaranteed to be 4 bytes big. One of the most important rules in C++ that you should NEVER expect anything except char to have certain size. One of the examples of the problem is that on linux sizeof(wchar_t) == 4, while on windows ms compiler sizeof(wchar_t)==2. Now guess what'll happen when you try to port wchar-related code that has an assumption about sizeof(wchar_t). If you need to be sure about size of certain type, then you should either use types provided by cross-platform framework (such as qint32 in Qt4, or Qint32 in libSDL) OR you should use compiler that supports uint32_t types (they aren't in 2003 standard, but will be added in future). Linux (kernel) provides such types in "types.h" (/usr/include/linux/types.h, I think), but they should be supported by gcc.

NevemTeve 10-21-2011 10:23 AM

Quote:

Originally Posted by SigTerm (Post 4504400)
There are platforms where int is 64bit

Well, I haven't seen such platform yet, and this link doesn't seem to give example.

Quote:

int is not guaranteed to be 4 bytes big. One of the most important rules in C++ that you should NEVER expect anything except char to have certain size.
Very true. I didn't mean one should expect sizeof (int) to be 4 when writing a program -- but when you are DEBUGGING (or "solving a puzzle", like in this topic), you should know that actually sizeof (int) _is_ 4.

Quote:

One of the examples of the problem is that on linux sizeof(wchar_t) == 4, while on windows ms compiler sizeof(wchar_t)==2. Now guess what'll happen when you try to port wchar-related code that has an assumption about sizeof(wchar_t).
Well, I don't think wchar_t would lead anywhere, have never used it myself.

Quote:

If you need to be sure about size of certain type, then you should either use types provided by cross-platform framework (such as qint32 in Qt4, or Qint32 in libSDL) OR you should use compiler that supports uint32_t types (they aren't in 2003 standard, but will be added in future). Linux (kernel) provides such types in "types.h" (/usr/include/linux/types.h, I think), but they should be supported by gcc.
I think every modern compiler has inttypes.h, but if not, it is easy to hack one.

r.osmanov 10-21-2011 11:00 AM

This is because of "space-time tradeoff", data alignment.

Consider the following:

main.c
Code:

#include <stdio.h>
#include <string.h>

typedef struct
#ifdef PACKED
__attribute__ ((packed))
#endif
{
    char *str;      // 8
    char ch ;      // 1
    int a;          // 4
    int b;          // 4
    int c;          // 4
    // Packed total size should be: 21
    // Aligned total size should be: 24
} test_t;

static void print_sizes(test_t *t)
{
    printf("DATA TYPE SIZES\n");
    printf("char*: %ld\n", sizeof(char*));
    printf("char: %ld\n", sizeof(char));
    printf("int: %ld\n", sizeof(int));

    printf("\nSIZE OF *t: %ld\n", sizeof(*t));
}

int main (int argc, char const* argv[]) {
    test_t t;
    t.str = "test string";

    print_sizes(&t);

    return 0;
}

Makefile
Code:

ifeq ($(PACKED), true)
    PACK=-DPACKED
else
    PACK=
endif

ifdef OUT
    OUTFILE=$(OUT)
else
    OUTFILE=test
endif

CC=gcc
CFLAGS=-Wall -c $(PACK)

all: test

test: main.o
    $(CC) main.o -o $(OUT)

main.o: main.c
    $(CC) $(CFLAGS) main.c -o main.o

clean:
    rm -f *.o *~

Compile versions with packed and padded memory:

Code:


$ make PACKED=true OUT=test_packed
$ make OUT=test_padded
$ ./test_packed
DATA TYPE SIZES
char*: 8
char: 1
int: 4

SIZE OF *t: 21
$ ./test_padded
DATA TYPE SIZES
char*: 8
char: 1
int: 4

SIZE OF *t: 24

This is on x86_64 Gentoo, gcc (Gentoo 4.4.4-r2 p1.4, pie-0.4.5) 4.4.4

SigTerm 10-21-2011 12:26 PM

Quote:

Originally Posted by NevemTeve (Post 4504424)
Well, I haven't seen such platform yet, and this link doesn't seem to give example.

http://www.unix.org/version2/whatsnew/lp64_wp.html
Cray supercomputers.

Quote:

Originally Posted by NevemTeve (Post 4504424)
you should know that actually sizeof (int) _is_ 4.

Nope. YOu should know that you can ask the debugger what the size of (int) is.

Quote:

Originally Posted by NevemTeve (Post 4504424)
Well, I don't think wchar_t would lead anywhere, have never used it myself.

wchar_t is char with unicode support. You'll have to use it for every character code that doesn't fit into ASCII.

Quote:

Originally Posted by NevemTeve (Post 4504424)
I think every modern compiler has inttypes.h, but if not,

This is a C99 header (afaik you need to use stdint.h instead), which means it doesn't need to be available in C++ compiler (it should be available in next standard which isn't here yet). C and C++ are not the same language. MSVC2008 doesn't have it. MSVC2005 doesn't have it. For maximum/minimum values there's std::numeric_limits.

Quote:

Originally Posted by NevemTeve (Post 4504424)
it is easy to hack one.

Please do not give such advices. Somebody is bound to take it seriously and produce another batch of buggy software as a result.
This implementation is naive and will cause problems. I'd advise to avoid using this particular header at all costs.
MSVC compiler has __int8, __int32 and __int64 type (non-standard), so if you're going to provide typedefs, you should use THEM instead - their size is guaranteed. "Hack one" is a bad idea - header that provides such types should come with a large framework that is frequently updated and reviewed by a team of people - this guarantees that the header is somewhat reliable and should work on whatever obscure compiler your code will be compiled.
"missing header" written by a "unnamed lone hero" is a BAD idea, since it does not guarantee that the header has been tested properly. If you're going to use external header, use the one provided with boost.

NevemTeve 10-21-2011 07:25 PM

Okay, you have some fair points here, others could be argued (maybe in separate topics), but perhaps we go could back to the original question:

Q: Why is sizeof(Z) 16 instead of 12, when sizeof(int) is 4 and sizeof(VMT-pointer) is 8?
A: Because of the alignment requirement: four byte padding added by the compiler.
http://en.wikipedia.org/wiki/Data_structure_alignment

r.osmanov 10-22-2011 12:06 AM

Quote:

Originally Posted by NevemTeve (Post 4504831)
Okay, you have some fair points here, others could be argued (maybe in separate topics), but perhaps we go could back to the original question:

Q: Why is sizeof(Z) 16 instead of 12, when sizeof(int) is 4 and sizeof(VMT-pointer) is 8?
A: Because of the alignment requirement: four byte padding added by the compiler.
http://en.wikipedia.org/wiki/Data_structure_alignment

That's exactly what I wrote above


All times are GMT -5. The time now is 03:06 PM.