[C++]sizeof那些事儿

sizeof 是什么

首先明确一点,sizeof是运算符,不是函数。运算符是一个对于编译器来说的概念,是由编译器处理的,在程序编译好之后所有的sizeof都已经被替换为实际的值。类似的还有decltype。所以像

1
sizeof(++i);

这类的语句是不会改变i的值的。在C99之后,sizeof增加了一些运行时特性,可以算出可变数组的大小,像这样:

1
2
3
4
int num;
scanf("%d", &num); //input 4
int arr[num];
sizeof(arr); //output 16

sizeof 怎么用

sizeof 支持下面的语法:

1
2
sizeof(type) (1) 以字节数返回type类型对象表示的大小
sizeof expression (2) 以字节数返回expression的类型对象表示的大小

其中,sizeof(char)sizeof(signed char)以及sizeof(unsigned char)始终返回1。

sizeof不能用于函数类型、不完整类型(含void)或位域左值。在一些编译器里,sizeof(void)会返回0,但这是没有定义的。

普通的sizeof

几个上面用法的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int main(void)
{
// type argument:
printf("sizeof(float) = %u\n", sizeof(float)); //4
printf("sizeof(void(*)(void)) = %u\n", sizeof(void(*)(void))); //4
printf("sizeof(char[10]) = %u\n", sizeof(char[10])); //10
// expression argument:
printf("sizeof 'a' = %u\n", sizeof 'a'); // 'a'的类型是int, 4
// printf("sizeof main = %zu\n", sizeof main); // 错误:函数类型
printf("sizeof &main = %u\n", sizeof main()); //类型为返回值int, 4
printf("sizeof \"hello\" = %u\n", sizeof "hello"); // 类型为char[6], 6
printf("sizeof(\"12345\" + 1) = %u\n", sizeof("12345" + 1)); // 类型为指针, 4
}

需要注意的是,在函数传参等数组退化为指针的时候sizeof返回的当然是指针大小,切忌用它来计算数组大小。

sizeof 和字节对齐

在sizeof用于非基本类型时,编译器会有字节对齐这样一个行为。它可以有效地用空间换时间,快速找到数据的地址。

字节对齐指的是每个变量的首地址和自身对齐值对齐,所以和变量顺序也有一定的关系。

字节对齐满足三个准则:

1) 结构体变量的首地址:能够被其最宽基本类型成员的大小所整除;

2) 结构体每个成员相对于结构体首地址的偏移量:都是该成员大小的整数倍,如有需要编译器会在成员之间加上填充字节。

3) 结构体末尾填充:结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要编译器会在最末一个成员之后加上填充字节。

基本类型的宽度:char=1,short=2,int=4,double=8,指针=4

例如下面一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
typedef struct{
int id; //0-7 由于下个元素要和4对齐,补齐了2字节
double weight; //8-15
float height; //16-23 由于结构体大小和8对齐,补齐了4字节
}Body_Info;
typedef struct{
char name[2]; //0-3 由于下个元素要和4对齐,补齐了2字节
int id; //4-7 很齐
double score; //8-15 很齐  
short grade; //16-23 由于下个元素要和8(BB中最长类型)对齐,补齐了6字节   
Body_Info b; //24-47 很齐
}Student_Info;
// 删除掉Student_Info的id或者name,大小都是不变的
// 删除掉Student_Info的grade或者把grade移动到name后,大小会成为40字节

sizeof的实现

因为是个编译器行为,就没有函数代码了,也不在标准库里。在clang的编译器代码中可以看到一些对sizeof的处理,可以把玩一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
TypeInfo ASTContext::getTypeInfoImpl(const Type *T) const {
uint64_t Width = 0;
unsigned Align = 8;
bool AlignIsRequired = false;
switch (T->getTypeClass()){
case Type::Vector: {
const VectorType *VT = cast<VectorType>(T);
TypeInfo EltInfo = getTypeInfo(VT->getElementType());
Width = EltInfo.Width * VT->getNumElements();
....
case BuiltinType::UInt:
case BuiltinType::Int:
Width = Target->getIntWidth();
Align = Target->getIntAlign();
break;
case BuiltinType::ULong:
case BuiltinType::Long:
Width = Target->getLongWidth();
Align = Target->getLongAlign();
break;
...
case Type::Pointer:
unsigned AS = getTargetAddressSpace(cast<PointerType>(T)->getPointeeType());
Width = Target->getPointerWidth(AS);
Align = Target->getPointerAlign(AS);
break;
...
}

编译结束后,sizeof就会根据WidthAlign被替换为常量。