Contents

[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被替换为常量。