对空结构体求 sizeof
C++ 声称完全兼容了 C, 这一点在某些细节上不尽然. 比如对空结构体 --- 没有成员, 不含虚函数, 虽然 C 还生活在没有虚函数的三叠纪 --- 求 sizeof
的结果. 具体地说就是下面这个表达式
struct empty {};
sizeof(emtpy);
在 C 和 C++ 中会得到不同的值: C 中其值为 0 (在主流编译器中如此), 而 C++ 中其值为 1. 这个微妙的不同步源于 C 中的一个指针相减问题, 如下代码
#include <stdio.h>
struct empty {};
int main()
{
struct empty x;
struct empty y;
printf("%ld", &x - &y);
return 0;
}
以 C 语言编译并运行, 程序会直接崩溃掉, 因为在 C 中计算表达式 &x - &y
的值等同于 ((char*)&x) - ((char*)&y) / sizeof(struct empty)
. 这个整数除法非常糟糕, 毫无疑问 C 编译器应该了解到危险所在了: 在编译期, 它应该发现该除法算式的常数分母是整数 0, 但是它还是义无反顾地生成了代码, 甚至连警告也不给, 将程序推入运行时再来崩溃大奖的深渊.
本来这种事情应该偷偷改掉拉倒, 可是 C 标准对这个事情讳而不谈, 丢出一张王牌 "对空结构体或联合求 sizeof
将会是未定义行为". 对此 C++ 只好吐了个槽, 说任何对象至少要占用 1 字节空间. 所以其实 C++ 标准也没有明确说出 "对空结构体或联合求 sizeof
将会是 1" 这样的话, 但是根据前面这个规定, 由编译器厂商演绎出来的结果就是这样的, sizeof
纷纷得到结果 1, 包括下面这样的情况
struct empty_base_a {};
struct empty_base_b {};
struct empty_inherit : empty_base_a, empty_base_b {};
sizeof(empty_inherit); /* 1 */
即对从空类上 (多重) 继承的空子类求 sizeof
也将得到 1.
这一招看起来很挫, 但还真的管用了, 用 C++ 编译器编译并运行上述程序, 零也不除了, 程序也不会崩了, 还能给出正确地结果.
虽然把两个什么空的东西用继承的方式捏在一起不会产生体积变大, 但是一个数组的什么空的东西则会导致体积累加, 如
struct empty {};
int main()
{
struct empty x[4];
printf("%ld", sizeof(x)); /* 4 */
return 0;
}
这段 C++ 代码的运行结果将是 4, 也就是 x 占用了 4 个 1 字节. 这又扯到 C++ 另一个核心编程思维 --- 面向迭代器. 例如下面一坨代码
struct empty {};
void echo(empty)
{
std::cout << "echo" << std::endl;
}
int main()
{
struct empty x[4];
std::for_each(x, x + 4, echo);
return 0;
}
如果认为整个数组是一个对象, 打个包求 sizeof
才能得到 1, 而 x[0]
与 x[4]
等等有相同的地址, 那么 std::for_each
中的循环将一次也不被执行. 类似的, 让多个空类对象聚合在一个空类对象中时, 它们占用的空间大小是会累加的, 如
struct empty {};
struct twin {
empty a;
empty b;
};
sizeof(twin); /* 2 */