前言
sizeof
是C语言里面的一个关键字,用于计算占据多少字节,如sizeof(int) = 4
,int类型占据4个字节。
sizeof做笔试和面试的时候出现的频率也相当高,总结一下。
一 数据类型占据内存
二 测数组
// 32位机
#include<stdio.h>
int main()
{
int a[5]={1,2,3,4,5};
printf(“sizeof数组名=%d\n”,sizeof(a));
printf(“sizeof *数组名=%d\n”,sizeof(*a));
}
// 运行结果
// sizeof数组名=20
// sizeof *数组名=4
三 测指针
如果是指针,sizeof只会检测到是指针的类型,指针都是占用4个字节的空间(32位机)。(64位占8个字节)
//32位机
char *p = “sadasdasd”;
sizeof( p ): 4
sizeof( *p ): 1//指向一个char类型的
要是非要使用sizeof来得到指向内容的大小,就得使用数组名才行,像上面测数组的例子sizeof(a)
四 测struct
4.1 内存对齐
测结构体前,需要了解内存对齐的原理,至于为什么需要对齐呢:
1.平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
2.性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
对齐参考:https://www.cnblogs.com/clover-toeic/p/3853132.html
4.2 struct内存对齐三大原则
- 对于结构体的各个成员,第一个成员的偏移量是0,排列在后面的成员其当前偏移量必须是当前成员类型的整数倍;
- 结构体内所有数据成员各自内存对齐后,结构体本身还要进行一次内存对齐,保证整个结构体占用内存大小是结构体内最大数据成员的最小整数倍;
- 如程序中有#pragma pack(n)预编译指令,则所有成员对齐以n字节为准(即偏移量是n的整数倍),不再考虑当前类型以及最大结构体内类型。
// 64位
struct CAT_s
{
int ld; // 4
char Color; // 1
unsigned short Age; //2
char *Name; // 8
void(*Jump)(void); //8
}Garfield;
cout << sizeof(CAT_s) << endl; //24
按照上面的3大规则直接来进行分析:
1.使用32位编译,int占4, char 占1, unsigned short 占2,char* 占4,函数指针占4个,由于是32位编译是4字节对齐,所以该结构体占16个字节。(说明:按几字节对齐,是根据结构体的最长类型决定的,这里是int是最长的字节,所以按4字节对齐);
2.使用64位编译 ,int占4, char 占1, unsigned short 占2,char* 占8,函数指针占8个,由于是64位编译是8字节对齐,(说明:按几字节对齐,是根据结构体的最长类型决定的,这里是函数指针是最长的字节,所以按8字节对齐)所以该结构体占24个字节。
4.3 pragma pack(k)
#pragma pack(1)
struct fun{
int i;
double d;
char c;
};
cout << sizeof(fun) << endl;
// sizeof(fun) = 13
可能是一般的内存对齐做习惯了,如果本题采用内存对齐的话,结果就是24(int 4 double char 7)。但是#pragma pack(1)让编译器将结构体数据强制按1来对齐。
每个特定平台上的编译器都有自己的默认“对齐系数”(32位机一般为4,64位机一般为8)。我们可以通过预编译命令#pragma pack(k),k=1,2,4,8,16来改变这个系数,其中k就是需要指定的“对齐系数”。
只需牢记:
- 第一个数据成员放在offset为0的地方,对齐按照对齐系数和自身占用字节数中,二者比较小的那个进行对齐;
- 在数据成员完成各自对齐以后,struct或者union本身也要进行对齐,对齐将按照对齐系数和struct或者union中最大数据成员长度中比较小的那个进行;
参考文章:#pragma pack()的解读。
4.4 位域/位段
C中允许在一个结构体中以位为单位来制定其成员所占内存长度
struct A {
int a:2;
int b:5;
int c:10;
int d:30;
};
// sizeof(A) = 8
// int: 4个字节,32位
// 2+5+10+30 = 47 > 32,但47 < 64,所以占8字节
参考博客:
变量定义后接冒号解释参考链接
结构体的中含有位域参考链接
结构体、位段与联合体
4.5 attribute((packed))
在32位系统中有如下定义,则sizeof(data_t)的值是()
typedef struct_data{
char m:3; //1
char n:5;
short s; // 2
union{ // 4
int a;
char b;
};
int h; //4
}_attribute_((packed)) data_t;
sizeof(data_t) = 11;
sizeof(data_t) = 11;
attribute((packed))的作用就是告诉编译器取消结构在编译过程中的优化对齐,按照实际占用字节数对齐,union联合体里面的变量是共享一个地址空间的,以及结构体的位段操作知识点。
4.6 有趣
//64位
struct C
{
double t; //8 1111 1111
char b; //1 1
int a; //4 0001111
short c; //2 11000000
};
sizeof(C) = 24; //注意:1 4 2 不能拼在一起,4位于1 2之间就不能拼接在一起
struct C1
{
double t; //8 1111 1111
int a; //4 0001111
char b; //1 1
short c; //2 11000000
};
// sizeof(C1) = 16; //这样就能拼接在一起
struct C2
{
double t; //8 1111 1111
short c; //2 11000000
char b; //1 1
int a; //4 0001111
};
// sizeof(C1) = 16; //这样就能拼接在一起
上面的题目使之上可以抽象为下面两个结构体来解释:
struct A {
int a; //4
char b; //1
short c; //2
};
//sizeof(A)=8
struct B {
char b; //1
int a; //4
short c; //2
};
//sizeof(B)=12
有效对齐N表示“对齐在N上”,即该数据的“存放起始地址%N=0”
对于struct B
假设其从0x0000开始存放,默认指定对齐值为4。
对于成员b,自身对齐值为1,比默认指定对齐值4小,所以有效对齐值N为1,存放起始地址0x0000符合“0x0000%1=0”
此时成员b占据了0x0000这个地址
对于成员a,自身对齐值为4,有效对齐值N为4,起始地址为0x0004时,才能满足存放起始地址%N=0,即“0x0004%4=0”。
此时成员a占据了0x0004 - 0x0007
对于成员c,自身对齐值为2,有效对齐值N为2,起始地址0x0008,满足“0x0008%2=0”。
此时成员c占据了0x0008 - 0x0009
上述过程就是内对齐。
所以,0x0000 - 0x0009存放B的内容,但是由于外对齐原则,0x0000 - 0x0009共有10个字节,要12个字节才能满足(10+2)%4=0。因此,struct B
共占用12个字节。
快速的,struct A
各个成员占据地址为:
- 成员a: 0x0000 - 0x0003
- 成员b: 0x0004
- 成员c: 0x0006 -0x0007
外对齐下,(7+1)%4=0,所以struct A
占据8个字节
五 测联合体union
联合体union内存对齐的2大规则:
1.找到占用字节最多的成员;
2.union的字节数必须是占用字节最多的成员的字节的倍数,而且需要能够容纳其他的成员.
//x64
typedef union {
long i;
int k[5];
char c;
} D;
// sizeof(D) = 24 /在vscode下使用gcc编译,显示20啊,不是24/???
要计算union的大小,首先要找到占用字节最多的成员,本例中是long,占用8个字节,int k[5]中都是int类型,仍然是占用4个字节的,然后union的字节数必须是占用字节最多的成员的字节的倍数,而且需要能够容纳其他的成员,为了要容纳k(20个字节),就必须要保证是8的倍数的同时还要大于20个字节,所以是24个字节。
六 例题
程序按64位编译,运行下列程序代码,打印输出结果是多少
#define CALC(x,y) (x*y)
int main(void) {
int i=3;
int calc;
char **a[5][6];
calc = CALC(i++, sizeof(a)+5);
printf("i=%d, calc=%d\n", i, calc);
return 0;
}
输出结果为:i=4, calc=725
注意在宏定义中带参数时括号的用法,在本题中#define CALC(x, y) (x*y)的结果是725,但是如果这样写:#define CALC(x,y) (x)*(y) 的结果就是735
一般32位机器就是5*6*4 = 120,64位则是5*6*8=240 ,char *a是字符型指针,char **a是指针的指针,在64位和32位中指针的大小是不一样的
参考博客: