2011-11-08 40 views
24

这个问题很简单。为了清楚起见,可以考虑下面的例子:类方法是否会增加类实例的大小?

// Note that none of the class have any data members 
// Or if they do have data members, they're of equal size, type, and quantity 
class Foo { 
public: 
    void foo1(); 
    void foo2(); 
    // 96 other methods ... 
    void foo99(); 
}; 

class Bar { 
public: 
    // Only one method 
    void bar(); 
}; 

class Derived1 : public Foo { }; 
class Derived2 : public Bar { }; 

int main() { 
    Foo f; 
    Bar b; 
    Derived1 d1; 
    Derived2 d2; 
    return 0; 
} 

执行情况fbd1d2所有占据的空间相同数量的内存?作为对这个问题的延伸,理论上来说,在传递Foo的时候复制实例需要的时间比Bar长。

+0

99种方法? arrgh .. – Nim

+0

@Nim:我想真正明白这一点。;) – Zeenobit

回答

21

唯一实例数据增加了一个类的实例的大小(即我所知道的所有的实现),但如果从虚拟函数的类添加虚函数或继承,那么你采取一次性击中了v表指针。

而且,别人正确地提到了一个类的最小大小为1个字节。

一些例子:

// size 1 byte (at least) 
class cls1 
{ 
}; 

// size 1 byte (at least) 
class cls2 
{ 
    // no hit to the instance size, the function address is used directly by calling code. 
    int instanceFunc(); 
}; 

// sizeof(void*) (at least, for the v-table) 
class cls3 
{ 
    // These functions are indirectly called via the v-table, a pointer to which must be stored in each instance. 
    virtual int vFunc1(); 
    // ... 
    virtual int vFunc99(); 
}; 

// sizeof(int) (minimum, but typical) 
class cls4 
{ 
    int data; 
}; 

// sizeof(void*) for the v-table (typical) since the base class has virtual members. 
class cls5 : public cls3 
{ 
}; 

编译器实现可以处理具有多个v表指针或其他其他方法的多个虚拟继承,所以这些也都会对类尺寸效应。

最后,构件数据对齐选项可以具有影响。编译器可能有一些选项或者#pragma来指定成员数据的起始地址应该是指定字节数的倍数。例如,具有4个字节边界对齐并假设sizeof(int) = 4

// 12 bytes since the offset of c must be at least 4 bytes from the offset of b. (assuming sizeof(int) = 4, sizeof(bool) = 1) 
class cls6 
{ 
    int a; 
    bool b; 
    int c; 
}; 
+0

更接近,但空的类型通常至少需要一个字节。很少有编译器会使这些字节少于一个字节。 –

+4

@MooingDuck它必须在字节可寻址的机器上至少有一个字节,以便两个对象不能有相同的地址。编译器无法做出决定。 (Stroustrup写这个地方是我读的地方,但是不记得它在哪里) –

+0

@SethCarnegie:是的,但是标准的_also_说编译器可以打破所有的规则,只要它使得相同的IO调用和volatile以相同的顺序读取/写入相同的参数,这允许GCC有时使对象小于一个字节,只要这不会改变程序的执行。 –

4

严格来说,这是依赖于实现的。但是方法的数量不应该改变类对象的大小。对于非虚拟方法,对象内存中没有任何与方法指针相关的内容。如果你有虚拟方法,那么每个对象都有一个指向虚表的指针。添加更多方法时,vtable会增长,但指针大小保持不变。

进一步信息:用于非虚拟方法,编译器跟踪方法指针为每个类。当您调用非虚方法时,编译器会将指向指向该对象的方法的指针传递给隐藏参数或堆栈。这就是一种方法如何“知道”它的对象,并访问指针this。对于虚拟方法,方法指针实际上是vtable的索引,因此编译器将this传递给vtable中的解除引用的条目。

+0

对于非虚方法,编译器跟踪每个类的方法指针。这是一个牺牲时间换取空间的权衡吗? – Djvu

+0

@Djvu,不,非虚拟方法在时间和空间上都更高效。 – ThomasMcLeod

3

是他们会。实际上,在你的情况下,大小将为1.在C++中,即使没有任何数据成员,类也将具有大小1.

1

FooBar应该各自为一个字节。 Derived1Derived2可能是一个或两个字节。

原因是编译器存储在可执行代码的所有静态信息,包括所有的成员函数。当你的代码上的Foo一个实例调用foo1,它只是调用Foo::foo1(this),这是在程序中的每个Foo相同。虚拟函数是一个例外,但不会为每个成员函数添加额外的大小,如果有虚拟函数,则不会增加一次性额外大小(通常为4/8字节)。