C/C++中 sizeof 的用法总结

sizeof 运算符

需要注意的是 sizeof() 是运算符,而不是一个函数,在编译时就计算好了,用于计算数据空间的字节数。因此,sizeof 不能用来返回动态分配的内存空间的大小。sizeof 常用于返回类型和静态分配的对象、结构或数组所占的空间,返回值跟对象、结构、数组所存储的内容没有关系。

本文将介绍使用 sizeof 来判定 共用体结构体嵌套结构体混合结构体 以及 类对象 所占空间的大小。


不同数据类型所占的内存大小

32 位 64 位
char 1 1
int 4 4
short 2 2
long 4 8
float 4 4
double 8 8
指针 4 8

long 类型与指针类型在 32 位机器上只占 4 字节,在 64 位机器上占 8 字节。其他类型在 32 位机器和 64 位机器都是占同样的大小空间。


共用体的大小

1
2
3
4
5
6
7
union A{
int a[5];
char b;
double c;
};

cout << sizeof(A) << endl;

上面求出共用体的大小为:24

union 中变量共用内存,应以最长的为准,A 中最长的成员是数组 a,其长度为 20。与结果不一样,这是因为在共用体内变量的默认对齐方式,必须以最长的 double(8Byte)对齐,所以得到 sizeof(A) = 24。所以将共用体内的 int a[5] 修改成 int a[6] 后,结果仍然不变;但如果将 int a[5] 修改成 int a[7],结果就变成 32。

对齐系数:每个平台上的编译器都有默认对齐系数 n,但是可以通过 #pragma pack(n) 来设定。

有效对齐系数:对于一个复杂类型变量,有效对齐系数 = min(对齐系数 n,复杂类型中最长数据类型的长度)。比如设定的对齐系数为 8,而结构体中最长的是 int,4个字节,那么有效对齐值为 4。

通过下面的例子说明有效对齐系数:

1
2
3
4
5
6
7
8
9
#pragma pack(4)

union A{
int a[5];
char b;
double c;
};

cout << sizeof(A) << endl;

输出为:20

这是因为通过 #pragma pack(n) 设置对齐系数为 4,所以实际的有效对齐系数为:min(4, sizeof(double)) = 4,所以最后共用体 A 的大小为 5*int(4) = 20。


结构体的大小

首先介绍一个概念和两条原则:

偏移量 :偏移量指的是 结构体变量中成员的地址结构体变量地址 的差。

存储变量时地址要求对齐,编译器在编译程序时会遵循两条原则

  1. 结构体变量中成员的偏移量必须是成员大小的整数倍。
  2. 结构体大小必须是所有成员大小的整数倍,也即所有成员大小的公倍数。

例子:

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

cout << sizeof(B) << endl;

输出为:24

这是因为 char a 的偏移量为 0,占用 1Byte;double b 指的下一个可用的地址的偏移量为 1,不是 sizeof(double)=8 的整数倍,需要补足 7Byte 才能是偏移量为 8;int c 指的下一个可用的地址的偏移量为 16,是 sizeof(int)=4 的整数倍,满足 int 的对齐方式。

故所有成员的变量都分配了空间,空间总的大小为 1+7+8+4 = 20,不是结构的节边界数(即结构中占用最大空间的类型所占用的字节数 sizeof(double)=8)的倍数,所以需要填充 4Byte,以满足结构的大小为 sizeof(double)=8 的倍数,即 24 。


嵌套结构体的大小

对于嵌套的结构体,需要将其展开。对嵌套结构体求 sizeof 时,上述原则变为:

  1. 展开后的结构体的第一个成员的偏移量应当是被展开的结构体中最大的成员的整数倍。
  2. 结构体大小必须是所有成员大小的整数倍,这里所有成员计算的是展开后的成员,而不是将嵌套的结构体当做一个整体。

例子:

1
2
3
4
5
6
7
8
9
10
struct C{
char a;
struct{
char b;
int c;
} ss;
short d;
};

cout << sizeof(C) << endl;

输出:16

char a 的偏移量为 0,占用 1Byte;但是对于展开后的结构体的第一个成员 char b,下一个可用的地址的偏移量为 1,不是被展开结构体中最大成员 int c 的整数倍,需补充 3Byte 才能是偏移量变为 4;char b 占用 1Byte,下一个可用地址的偏移量是 5,又不是 int c 大小的整数倍,又需补充 3Byte 变为 8;short d 的偏移量为 12,满足 short 的对齐方式。

故所有成员变量都分配了空间,空间总的大小为 1+3+1+3+4+2 = 14,结尾还得填充 2Byte,以满足为 4 的倍数,所以总的大小为 16。


混合结构体的大小

混合结构体指的是结构体中包含有共用体(或数组)等比较复杂的结构体。如下面的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream>
using namespace std;
typedef union{
long i;
int k[5];
char c;
} UDATE;
struct data{
int cat;
UDATE cow;
double dog;
}too;
UDATE temp;
int main(){
cout << sizeof(struct data) + sizeof(temp) << endl;
return 0;
}

摘自《后台开发:核心技术与应用实践》例1.16,书中后面的解释有部分错误。

输出:64

假设测试机器是 64 位。UDATE 是一个 union,作为变量公用空间。里面占用字节数最多的变量是数组 int k[5],有 20Byte,但是它要与 long 类型的 8Byte 对齐,所以占用 24Byte。data 是一个 struct,每个变量分开占用空间,依次为 int(4+4) + UDATE(24) + double(8) = 40,字节已对齐,故 sizeof(struct data) 是 40。所以最后的结果为 40+24 = 64。


类对象的大小

关于类占用的内存空间,有以下几点需要注意:

  1. 如果类中含有虚函数,则编译器需要为类构建虚函数表,类中需要存储一个指针指向这个虚函数表的首地址,注意不管有几个虚函数,都只建立一张表,所有的虚函数地址都存在这张表里,类中只需要一个指针指向虚函数表首地址即可。

  2. 类中的静态成员是被类所有实例所共享的,它不计入sizeof计算的空间。

  3. 类中的普通函数或静态普通函数都存储在栈中,不计入sizeof计算的空间。

  4. 类成员采用字节对齐的方式分配空间。

总的来说,类对象占用的内存空间为:非静态成员变量总和 加上 编译器为了 CPU 计算做出的数据对齐处理支持虚函数所产生的负担 的总和。

例子:

1
2
3
4
5
6
7
8
9
10
11
12
class B{
public:
virtual void funa();
virtual void funb();
void func();
static void fund();
static int si;

private:
char c;
int i;
};

以上类的大小:sizeof(B) = 12(32位)sizeof(B) = 16(64位)

根据以上的规则,多个虚函数只建立一张虚函数表,类中只存有一个指向虚函数表首地址的指针;普通函数 func() 不计入;静态成员 fund()si 也不计入;char c 占用 1Byte,再需补充 3Byte;int i 占用 4Byte。所以总的大小为:一个指针大小+1+3+4。32位系统指针大小为 4Byte,所以为 12Byte;64位系统指针大小为 8Byte,所以总大小为 16Byte。