sizeof 运算符是一个极为常用且强大的工具。它能够在编译时计算任意数据类型或对象在内存中占用的字节数。


一、什么是 sizeof 运算符?

sizeof 是一个内置于 C++ 的运算符,用于获取数据类型或对象在内存中所占的字节数。它在编译时计算结果,意味着无需等待运行时,就能确定某一类型或对象的大小。这一特性使得 sizeof 在系统编程、内存管理及平台无关的编程中扮演了重要角色。


二、sizeof 运算符的使用场景

2.1 基本数据类型

C++ 提供了多个基本数据类型(如 intcharfloatdouble 等),不同的平台可能为相同的数据类型分配不同大小的内存。通过 sizeof,我们可以在编译时确定数据类型在当前平台上的大小。

1
2
3
std::cout << "Size of int: " << sizeof(int) << " bytes" << std::endl;
std::cout << "Size of char: " << sizeof(char) << " bytes" << std::endl;
std::cout << "Size of double: " << sizeof(double) << " bytes" << std::endl;

不出幺蛾子的话,在 32 位系统上,int 通常占用 4 个字节,而 double 可能占用 8 个字节。不同编译器和平台的差异使得 sizeof 成为编写可移植代码的关键工具。

2.2 用户定义的类型(结构体和类)

sizeof 还可以用于计算用户定义类型(如 structclassunion)的大小。这在系统编程中尤为重要,帮助我们理解复杂数据结构在内存中的布局。需要注意的是,结构体的大小可能会因编译器的内存对齐(padding)策略而变化。

1
2
3
4
5
6
7
struct MyStruct {
    int a;
    char b;
    double c;
};

std::cout << "Size of MyStruct: " << sizeof(MyStruct) << " bytes" << std::endl;

内存对齐和填充:在某些系统上,为了提高内存访问效率,编译器可能会在结构体的成员之间插入填充字节(padding)。这使得结构体的实际大小通常大于其成员的字节和。了解这一机制对优化数据结构在系统内存中的布局尤为重要。

2.3 数组

使用 sizeof 可以获取数组的总大小(即元素大小乘以数组元素个数)。但必须注意,sizeof 只能返回静态数组的总大小。如果数组是动态分配的,sizeof 只会返回指针的大小,而不是数组的大小。

1
2
int arr[10];
std::cout << "Size of arr: " << sizeof(arr) << " bytes" << std::endl;

重要注意:如果数组是通过指针传递给函数的,sizeof 返回的将是指针的大小,而不是数组的大小。这一点对于处理动态内存分配尤为重要。

2.4 指针

sizeof 可以用于指针类型,返回指针本身的大小,而不是指针所指向的数据类型的大小。通常在 32 位系统上,指针大小为 4 字节;而在 64 位系统上,指针大小为 8 字节。

1
2
int* ptr;
std::cout << "Size of pointer: " << sizeof(ptr) << " bytes" << std::endl;

无论指针指向的是什么类型的数据,sizeof 返回的始终是指针本身的大小。这在动态内存分配中,尤其是多级指针的使用时,需要特别注意。

2.5 字符串字面量

C++ 支持使用 sizeof 获取字符串字面量的大小,这包括字符串本身的字符数以及末尾的空字符 \0

1
std::cout << "Size of string literal 'Hello': " << sizeof("Hello") << " bytes" << std::endl; // 输出 6

在这个例子中,sizeof("Hello") 返回的大小为 6,因为它包括了末尾的空字符 \0


三、sizeof 运算符的限制

尽管 sizeof 是一个非常强大的工具,但它并非在所有场景下都适用。在某些特殊情况下,使用 sizeof 会产生意料之外的结果。

3.1 动态分配的数组

对于动态分配的数组,sizeof 返回的仅仅是指针的大小,而不是数组的大小。这是因为 sizeof 无法在编译时确定堆上分配的数组的大小。

1
2
3
int* arr = new int[10];
std::cout << "Size of arr: " << sizeof(arr) << " bytes" << std::endl; // 返回指针的大小,而不是数组的大小
delete[] arr;

解决方案:如果需要获取动态数组的大小,可以通过额外的变量存储数组的长度,或使用 C++ 提供的标准容器(如 std::vector),它们可以在运行时管理数组的大小。

3.2 不完全类型

sizeof 不能用于不完全类型(Incomplete Type),如前向声明的类或结构体。这是因为编译器无法知道不完全类型的内存布局。

1
2
3
struct MyStruct; // 前向声明

// std::cout << sizeof(MyStruct) << std::endl; // 错误,MyStruct 是不完全类型

尝试对不完全类型使用 sizeof 会导致编译错误,因此必须确保类型在使用 sizeof 之前已经完全定义。

3.3 函数

sizeof 运算符不能用于函数类型。函数本质上是指向内存中的一段指令,sizeof 只能操作数据类型或对象,而不能直接获取函数的大小。

1
2
3
void myFunction() {}

// std::cout << sizeof(myFunction) << std::endl; // 错误,不能对函数使用 sizeof

四、特殊情况:虚函数表指针的大小

对于包含虚函数的类,编译器会在类的对象中添加一个指向虚函数表的指针(vptr)。这个指针占用了内存,且 sizeof 计算的对象大小包含了 vptr,开发者也无法直接访问或操作它的大小。

1
2
3
4
5
class Base {
    virtual void func() {}
};

std::cout << "Size of Base: " << sizeof(Base) << " bytes" << std::endl;

在这个例子中,sizeof(Base) 返回的是包含虚函数表指针的对象大小。


五、sizeof 的其他最佳实践

  • 在编译时确定大小sizeof 在编译时执行,因此其结果在运行时是不可变的。这意味着 sizeof 可以用于数组声明、静态断言和模板元编程中,提供了极大的灵活性和性能优化。

  • decltype 结合使用:在 C++11 及更高版本中,sizeof 可以与 decltype 结合使用,以确保类型的安全性。例如:

    1
    2
    
    int x = 5;
    std::cout << "Size of x: " << sizeof(decltype(x)) << " bytes" << std::endl;
    
  • 括号使用:对于类型名,必须使用括号,如 sizeof(int);而对于变量,括号是可选的,如 sizeof xsizeof(x) 都是合法的。