空类的大小

空类就是没有任何数据成员的类,这种类占用的内存大小在不同的语言里面有不同的实现

c

struct A {};

printf("sizeof(A): %lu\n", sizeof(struct A));
// sizeof(A): 0

这个结果输出是0,也就是说 c 语言中的空类大小为 0

struct A a1;
struct A a2;
printf("address(a1): %p\n", &a1);
printf("address(a2): %p\n", &a2);
printf("&a1 == &a2: %d\n", &a1 == &a2);
// address(a1): 0x7ffdead15ff0
// address(a2): 0x7ffdead15ff0
// &a1 == &a2: 0

在 gcc 中,两个空类拥有相同的地址,但是比较的结果却是不同的……这个我也不知道咋解释……

c++

class A {};

std::cout << "sizeof(A): " << sizeof(A) << std::endl;
// sizeof(A): 1

c++ 的空类大小为1,因为 c++ 中规定不同的对象必须拥有不同的地址,如果为0会导致两个空类的地址一样

class B {
    A a1;
    A a2;
};

std::cout << "sizeof(B): " << sizeof(B) << std::endl;
// sizeof(B): 2

空类作为成员,按照大小为 1 来处理

class C : public A {};

std::cout << "sizeof(C): " << sizeof(C) << std::endl;
// sizeof(C): 1

class D : public A {
    int i;
};

std::cout << "sizeof(D): " << sizeof(D) << std::endl;
// sizeof(D): 4

基类为空类不占空间,这个就是空白基类优化 EBO (empty base optimization)

golang

type A struct{}

fmt.Println("sizeof(A):", unsafe.Sizeof(A{}))
// sizeof(A): 0

golang 中空类的大小为 0

a1 := A{}
a2 := A{}
fmt.Printf("a1 == a2: %v\n", &a1 == &a2)
// a1 == a2: false

两个空类对象在栈上拥有不同的地址

a3 := A{}
a4 := A{}
fmt.Printf("address(a3): %p\n", &a3) // a3 逃逸到堆区
fmt.Printf("address(a4): %p\n", &a4) // a4 逃逸到堆区
fmt.Printf("a3 == a4: %v\n", &a3 == &a4)
// address(a3): 0x1190fd0
// address(a4): 0x1190fd0
// a3 == a4: true

调用 printf,a3、a4 逃逸到了堆区,在堆区拥有相同的地址

这个地方确实很怪,仅仅因为调用了一次 printf,导致 &a1 == &a2 的判断出现了不同的结果

总结

个人感觉 c++ 的设计不是很好,首先空类也占用一个字节的空间就是一件让人困惑的事情,所以不得不花很多篇幅去解释这个问题,还要去解决由这个问题引发的空类组合和继承的问题;另外对于空类是不是一定要有不同的地址来去区分不同的对象,这个场景在实际的生产中基本没有碰到过,就算是真的有这样的场景,也可以让用户自己增加成员变量去实现;最重要的,这样的设计会带来不必要的内存和性能的开销,而这种开销还不可避免

链接

转载请注明出处
本文链接:https://tech.hatlonely.com/article/58

<think>好的,我现在需要准备关于C++类的大小计算的面试问题。用户提到空类大小为1,含有虚函数的空类大小为4或8,这可能在面试中被问到。我得仔细回想相关知识,确保回答准确。 首先,空类大小为什么是1?记得C++中每个对象需要有唯一的地址,所以即使没有成员,编译器会插入一个字节的占位符。这样当创建多个空类对象时,它们地址不同。这点没错,但得确认是否有例外情况。 接下来,含有虚函数的类大小变为4或8。这是因为虚函数表指针(vptr)的存在,指针的大小取决于系统架构,32位系统是4字节,64位是8字节。所以类中如果有虚函数,编译器会添加vptr,导致大小增加。但需要注意,是否所有虚函数的情况都是这样?比如继承的情况,或者多重继承,会影响大小吗? 然后,面试回答的结构需要清晰,分点说明。用户可能希望听到以下几点:空类大小,虚函数的影响,继承情况下的类大小,内存对齐的影响,以及可能的优化情况。 比如,空类大小是1字节,但如果作为基类,是否会被优化?比如空基类优化(EBO),当基类为空时,派生类可能不会为其分配空间,从而节省内存。这点需要提到,但可能属于进阶内容,面试中是否要深入? 关于虚函数,除了添加vptr,还要考虑虚继承的情况。虚继承可能会引入额外的指针,增加类的大小。但用户的问题可能集中在普通虚函数的情况,所以先重点讲vptr的影响。 内存对齐的问题也需要考虑。例如,类中有多个成员变量时,编译器会根据对齐规则调整大小。比如一个char和一个int,在64位系统上,char占1字节,int占4字节,但由于对齐,总大小可能是8字节。这部分是否属于类大小计算的常见问题?需要简要提及,但用户的问题主要针对空类和虚函数的情况。 此外,结构体或类的成员顺序是否会影响大小?比如调整成员顺序减少填充字节,优化空间,但可能面试中不深入,除非特别问到。 总结一下,面试回答的结构应该是: 1. 空类大小为1字节的原因:保证对象有唯一地址。 2. 含有虚函数的类大小增加的原因:虚表指针的存在,指针大小取决于系统。 3. 继承对类大小的影响:基类的成员和派生类成员的叠加,加上可能的虚表指针。 4. 内存对齐的影响:编译器填充字节以满足对齐要求。 5. 空基类优化的例外情况:当基类为空时可能不占空间。 还需要注意举例说明,比如代码示例,比如: class Empty {}; sizeof(Empty) == 1; class WithVirtual { virtual void foo() {} }; sizeof(WithVirtual) == 4(32位)或8(64位) 另外,多重继承时可能会有多个虚表指针,比如两个基类都有虚函数,派生类可能包含两个vptr,导致更大的大小。但用户问题可能不涉及,但作为扩展可以提到。 最后,总结回答时要强调关键点:空类占位,虚函数引入指针,内存对齐的影响。确保回答结构清晰,重点突出,符合用户提到的“结构清晰,帮助用户逐步解决问题”。</think>在C++中,类的大小计算涉及编译器的内存分配规则,以下是面试中需要掌握的关键点: --- ### 一、空类大小 ```cpp class Empty {}; ``` - **大小为1字节** 即使类为空,编译器会插入1字节的占位符,确保每个对象有唯一地址。 **用途**:区分不同空类对象(如`Empty e1, e2; &e1 != &e2`)。 --- ### 二、含虚函数的类大小 ```cpp class Base { public: virtual ~Base() {} // 虚函数 }; ``` - **大小取决于系统架构** - 32位系统:`sizeof(Base) = 4`(虚表指针`vptr`占4字节) - 64位系统:`sizeof(Base) = 8`(`vptr`占8字节) - **本质原因** 虚函数通过虚表(`vtable`)实现多态,编译器自动为类添加一个隐藏的虚表指针。 --- ### 三、继承关系中的类大小 #### 1. 单继承(无虚函数) ```cpp class A { int x; }; // sizeof(A) = 4 class B : public A {}; // sizeof(B) = 4(继承A的成员) ``` - **大小 = 基类成员 + 派生类成员** 若派生类无新增成员,大小与基类相同。 #### 2. 含虚函数的继承 ```cpp class Base { virtual void foo() {} }; // sizeof(Base) = 8(64位) class Derived : public Base {}; // sizeof(Derived) = 8(共享Base的vptr) ``` - **派生类共享基类的虚表指针** 不因继承而额外增加`vptr`(除非派生类新增虚函数且基类无虚函数)。 --- ### 四、内存对齐的影响 ```cpp class Example { char c; // 1字节 int i; // 4字节 double d; // 8字节 }; ``` - **对齐规则** 成员按最大对齐值的倍数排列(`double`对齐值通常为8)。 - **计算结果** `sizeof(Example) = 1(char) + 3(填充) + 4(int) + 8(double) = 16`。 --- ### 五、空基类优化(EBO) ```cpp class Empty {}; class Derived : public Empty { int x; }; ``` - **优化前** `sizeof(Derived) = 1(Empty) + 4(int) = 5`(实际可能对齐到8)。 - **优化后** 编译器允许将空基类占用的1字节与派生类成员合并,`sizeof(Derived) = 4`。 --- ### 六、面试回答模板 1. **空类**:固定1字节,保证对象地址唯一性。 2. **虚函数**:引入虚表指针,大小随系统架构(32位4字节,64位8字节)。 3. **继承**: - 单继承无虚函数:基类成员+派生类成员。 - 虚函数继承:共享基类虚表指针,不额外增加大小。 4. **内存对齐**:按最大成员对齐,填充字节避免性能损失。 5. **特例**:空基类优化可减少空间占用。 --- ### 示例对比 | **类定义** | **32位系统大小** | **64位系统大小** | |----------------------------|------------------|------------------| | `class Empty {};` | 1 | 1 | | `class WithVirtual { virtual ~WithVirtual(); }` | 4 | 8 | | `class A { int a; char b; };` | 8(对齐到4+4) | 8(对齐到4+4) | --- **总结**:类大小由成员变量、虚表指针、内存对齐共同决定,需结合具体场景分析。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值