1. sizeof
运算符简介#
sizeof
是C++中的一个关键字和一元运算符,其作用是取得数据类型或者表达式的大小(以Byte为单位),求得结果的类型是std::size_t
。它是一个编译时运算符,在绝大多数情况下是在编译时进行运算(在运行时运算的特例后面会提及)。sizeof
运算符的语法有两种形式:
- 对类型使用:必须使用括号
例如:
cppsizeof(type)
sizeof(int)
、sizeof(double)
- 对表达式使用:括号是可选的
例如:
cppsizeof expression // 或者 sizeof(expression)
sizeof(x)
、sizeof(x+1)
2. sizeof
运算符的常见用法#
-
sizeof
运算符可以求得基本数据类型的大小,例如:
cppsizeof(char);//结果为1
-
对变量使用
sizeof
,可以求得该变量类型的大小,例如:
cppint a; sizeof a;//结果为4
-
对数组使用
sizeof
,可以求得整个数组的长度,一个常见的用法是计算数组中元素的数量,例如:
cppint arr[] = {1, 2, 3, 4, 5, 6, 7}; int numElements = sizeof(arr) / sizeof(arr[0]); std::cout << "Number of elements: " << numElements << std::endl; // 输出: 7
-
对指针变量使用
sizeof
,可以求得指针本身的大小。对引用使用sizeof
,可以求得被引用对象的大小,而不是引用本身的大小,例如:
cppint* p = nullptr; sizeof p;//结果为8(64位系统)或4(32位系统) int x = 10; int& ref = x; sizeof ref;//结果为4
-
对类或者结构体使用
sizeof
,可以求得该类或者结构体的一个对象占用的总字节数,通常包括:- 非静态成员变量的大小
- 编译器为了内存对齐而额外添加的字节
- 如果存在虚函数,会包含一个指向虚函数表的虚指针大小
不包括:
- 静态成员变量的大小,因为静态成员变量不属于任何一个对象,而是由所有对象共享
- 成员函数的大小,因为成员函数存储在代码段,不占用对象的内存空间
cppclass MyClass { public: int a; // 4 bytes char b; // 1 byte // 内存对齐机制会填充3 bytes double c; // 8 bytes static int s; // 不计入 sizeof virtual void func() {} // 会增加一个 vptr 的大小 }; // 在一个典型的64位系统上,sizeof(MyClass) 可能是 24 字节 // (vptr: 8) + (int a: 4) + (char b: 1) + (padding: 3) + (double c: 8) = 24
在C++中,由于任何对象都必须有一个唯一地址,因此空类和结构体的大小至少为1字节,例如:
cppclass Empty {}; std::cout << "Size of Empty class: " << sizeof(Empty) << std::endl; // 输出: 1
3. sizeof
运算符的一些特性#
sizeof
是运算符而不是函数,这是sizeof
最基本的特性。因此我们通常不会采用参数和返回值的说法。sizeof
不能求得void
类型的长度,因为void
类型是没有大小的,我们也无法声明void
类型的变量。sizeof
能够求得void
类型的指针的长度,因为void
类型的指针是有大小的。
cppsizeof(void);//非法 sizeof(void*)//合法
- 对任何类型的指针变量使用
sizeof
,得到的永远是指针本身的大小,而不是它所指向的对象或内存块的大小。例如:
cppint arr[10]; int *p = arr; std::cout << sizeof(arr) << std::endl; // 输出:10 * sizeof(int) = 40 std::cout << sizeof(p) << std::endl; // 输出:指针大小,通常是 4 或 8 字节
- 在函数参数中,数组会退化成指针,因为在函数参数传递时,数组被退化成指针了,例如:
cppvoid func(int arr[10]) { std::cout << sizeof(arr) << std::endl; // 实际是 sizeof(int*),而不是数组大小 } int main() { int arr[10]; func(arr); }
- 表达式不会被求值,但是会返回表达式计算结果的类型大小。例如:
cppint x = 5; double y = 5.0; std::cout << sizeof(x++) << std::endl; // 结果为4,x 仍然等于 5 std::cout << sizeof(x+y) << std::endl; //结果为8,求得的是double的大小
- 动态内存只能求得指针大小,无法获取内存块的大小,例如:
cppint *p = new int[10]; std::cout << sizeof(p) << std::endl; // 结果是 4 或 8,无法获取数组总大小
sizeof
用在函数调用上时,实际上不会调用函数,只会求得函数的返回类型所占用的字节数,例如:这是因为
cppint foo() { std::cout << "foo() 被调用" << std::endl; return 42; } int main() { std::cout << sizeof(foo()) << std::endl;//输出结果为4 return 0; }
sizeof
的运算是在编译器完成的,不会产生任何的运行时行为。注意sizeof
不能对void
函数使用,因为返回值void
是没有大小的,编译会报错。sizeof
不能用来获取结构体中单个位域成员的大小,sizeof
只能针对类型或者整个结构体,不能直接用于单个位域成员,例如:
cppstruct Test { unsigned int a : 3; // 3 bits unsigned int b : 5; // 5 bits }; int main() { Test t; std::cout << sizeof(t.a) << std::endl; // 输出 4(因为 a 是 unsigned int 类型) std::cout << sizeof(t) << std::endl; // 输出结构体的总大小,通常会考虑对齐 return 0; }
sizeof
不能对不完整类型的数组求长度,会导致编译错误,例如:
cppint arr[]; // 只声明,不定义(大小未知) int main() { std::cout << sizeof(arr) << std::endl; // 编译错误,类型不完整 }
- 对字面量使用
sizeof
会包含\0
,例如:
cppstd::cout << sizeof("hello") << std::endl; // 输出 6,包括 '\0'
4. sizeof
的其他常见问题#
sizeof
是有可能是在运行时计算的,这只发生在一种非常特殊的情况下,即对变长数组使用sizeof
时。在标准的C++中,sizeof
的结果始终都是一个编译时常量,编译器在编译代码时就已经确定了所有类型的大小,并将sizeof
的结果替换成为一个具体数值。
变成数组是C99语言的一个特性,它允许使用一个变量来指定数组的长度:
// 这是 C 语言代码,不是标准 C++
void foo(int n) {
int array[n]; // n 的值在运行时才知道,所以 array 是一个变长数组
}
cpp但是我们会发现,我们在C++这样写不会报错,因为一些主流的C++编译器(特别是GCC和Clang)为了向后兼容C语言,把VLA作为一种非标准的语言扩展也引入到了C++中。所以在这种情况下,对变长数组使用sizeof
是运行时进行计算的。