重点来了,知道了这些规则后,具体要怎么算一个结构体的大小呢?
不是所有的硬件平台都能访问任意地址上的任意数据;某些硬件平台只能在某些特定地址处取某些特定的数据,否则就会抛出硬件异常。也就是说在计算机在内存读取数据时,只能在规定的地址处读数据,而不是内存中任意地址都是可以读取的。
正是由于只能在特定的地址处读取数据,所以在访问一些数据时,对于访问未对齐的内存,处理器需要进行两次访问;而对于对齐的内存,只需要访问一次就可以。 其实这是一种以空间换时间的做法,但这种做法是值得的。
1.第一个成员在结构体变量偏移量为0的地址处,也就是第一个成员必须从头开始。
2.其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。对齐数为编译器默认的一个
对齐数与该成员大小中的较小值。在64位编译器中默认值是8 ,可以在代码前通过#pragmapack (N)修改,使用#pragma pack(show)可以查看对齐值,但修改时N的取值只能设置成(1,2,4,8,16)这几个值
3.结构体总大小为最大对齐数的整数倍。(每个成员变量都有自己的对齐数)
4.如果嵌套结构体,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是
所有最大对齐数(包含嵌套结构体的对齐数)的整数倍。
不同位的编译器不同类型所占字节不一样,如下图所示。注:以下数据类型对应表供参考,绝大部分是对应的,但具体大小以所用编译器为准!
但都会满足如下条件:
sizeof(char) == 1 |
sizeof(char) <= sizeof(short) <= sizeof(int) <= sizeof(long) <= sizeof(long long) |
来了,从小白开始,教你们怎么算,这个面试可能会考,特别是c语言岗位的
以下面的这个结构体为例:
- typedef struct test
- {
- char a;
- int b;
- int d;
- long c;
- int *e;
-
- }test_t;
我们根据对齐规则的第二点,要知道对齐数,具体怎么做呢,在代码上方加入这个
#pragma pack(show)
如下所示,我在编译时就会警告,告诉我们对齐数是多少
知道对齐数了,还要知道每个类型的大小,代码如下
- #include
- #pragma pack(show)
-
- typedef struct test
- {
- char a;
- int b;
- int c;
- long d;
- int *e;
- }test_t;
-
- int main()
- {
- printf("short int的大小:%zd\n", sizeof(short int));
- printf("int 的大小:%zd\n", sizeof(int));
- printf("long 的大小:%zd\n", sizeof(long));
- printf("long long的大小:%zd\n", sizeof(long long));
- printf("char 的大小:%zd\n", sizeof(char));
- printf("char* 的大小:%zd\n", sizeof(char*));
- printf("float 的大小:%zd\n", sizeof(float));
- printf("double 的大小:%zd\n", sizeof(double));
- printf("long double的大小:%zd\n", sizeof(long double));
- //printf("int的大小:%d", sizeof(int));
-
- //printf("结构体test的大小:%zd\n", sizeof(test_t));
- return 0;
- }
会输出如下:和上面表格差不多,我使用的是64位编译器
- short int的大小:2
- int 的大小:4
- long 的大小:8
- long long的大小:8
- char 的大小:1
- char* 的大小:8
- float 的大小:4
- double 的大小:8
- long double的大小:16
接下来该算结构体大小了,我知道了对齐数为8,要从结构体中定义的变量,从上往下算,我们要明确规则第三点中的每个成员变量都有自己的对齐数。
我们设结构体的内存存储从1开始,实际是从0开始的,为了易理解:
计算结构体中第1个变量,a为char型,占1个字节。
计算结构体中第2个变量,b为int型,占4个字节,根据规则对齐规则2(其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处),如果从2地址开始存储,是错误的,起始地址为2,无法整除下一个变量的第一个整型变量大小,2/4 != 0,如下图所示
所以要从4地址开始存储,4/4 = 1,能整除,存储后,结构体现在大小为1+'3'+4=8;存储结构如下图所示,才是正确的
计算结构体中第3个变量,c为int型,8/4=2,能整除,从8开始存储,占4个字节,结构体现在大小为8+4=12
接下来计算结构体中第4个变量,d为long型,占8个字节,从12往后找能被8整除最近的数,就是16啦,所以从16开始存储,结构体现在大小为12+'4'+8=24
接下来计算结构体中第5个变量,e为指针型,占8个字节,现在总大小为24+8=32,图就不画了,和上面的类似,所以这个结构体test,总共占用字节数为32
要是结构体中还是有第6个变量呢?如下所示
有人说了,这不简单吗,+1不就得了么,总共33个字节。不是的!结构体对齐规则第三条中写着:结构体总大小为最大对齐数的整数倍,33显然不是,里33最近的就是8*5=40,那此时结构体大小就为40。
我打印下结构体中的每个变量地址分配,就知道了,如下图所示,表示的是每个变量存储的地址起点。
这和我上述分析是一样的,只不过我用的是10进制分析的,编译器是16进制,你们可以算一下。
有人说了,这不是浪费空间吗,结构体中就多了一个字节的变量,结构导致整个结构体大小多了一个对齐数=8位。确实会这样,这个结构体需要优化,后面会讲,先说一下结构体中的特殊情况。
就是结构体中嵌套了结构体,也就是套娃,这时候要怎么算呢?我这里用一个简单的例子讲一下,结构体里面套一个结构体。如下所示
这时候要怎么算呢?
我们刚刚算到c这里时,这时已经占了12个字节了,接下来到结构体这个对象s了,先算这个嵌套结构体的大小,这个结构体比较简单,第一个是名字的cha*型指针,我的是64位编译器,所以所占字节位8位,第二个位int型变量,所占字节数位4,现在总大小为12byte,再根据结构体对齐规则第三条,结构体总大小为最大对齐数的整数倍,里12最近的就是8*2=16,所以总字节数为16.
嵌套结构体的大小为16,在结构体test怎么存储呢?此时16已经超过最大对齐数,存储的话能够被最大字节数整除的下一个最近位置开始存储即可。刚刚算到c时,已经占到12这个位置了。所以要从16开始存储,一共占16位,所以总字节数为12+'4'+16=32;
类型这种一个变量或者对象的大小超过最大对齐数的很多,如string类型的变量,就有固定的大小32位。存储的话,像上面那样套路存储计算,即可;
接下来计算结构体中第5个变量,本身占8个字节,按上面的套路计算即可,加上去后此时总字节数40;
计算第六个变量,d为long型,占8个字节,从40开始存储,现在总字节数为48;
计算第七个变量,d为char型,占1个字节,从41开始存储,现在总字节数为49 ,根据结构体对齐规则3(结构体总大小为最大对齐数的整数倍 ),所以最终结构体总字节数为56.
地址分布如下图所示:
C语言中还有一种情况:结构体中有一种如char name[16],这种c语言字符串类型的,怎么算呢,这种很简单,他就相当于char 型的字符,有16个,连续排在一起。如下图所示,这种char型一个字符的,基本就是有地方就插,毕竟1能被任何数整除。
结构体如何计算的已经搞定了,像上面说的,很多时间,结构体中的变量存储会有很多空隙,导致浪费存储空间,怎么办呢?
可能很多人都想到了,把占用字节数小的变量放前面,大的放后面。
如果没有考虑,对结构体对齐不了解的话,可能变量乱放,如下图所示,这个结构体test的大小为72字节
如果有考虑,对结构体对齐了解的话,变量不乱放,对结构体的成员位置进行优化,如下图所示,此时这个结构体test的大小为56字节,比不优化的少了16个字节
可能有人会说:不优化结构体,平时写代码时运行时也没什么感觉
其实,在嵌入式领域,结构体是经常用到的,如单片机,stm32等,为什么要优化结构体呢?
1.嵌入式领域的芯片,他的容量是很小的,不能我们电脑的磁盘那么大,可以节省空间。
2.芯片中的数据读取,是有特定区域的,如果不优化结构体,结构体的数据读取的时候有些要拼接,这就浪费了时间,而芯片这种东西,效率是要求很高的。可以提高读取效率
对于软件开发人员,不像嵌入式的芯片,很多软件并不在意这点运行所用的空间和时间,这是个细节吧。得要有这个意识,可能面试时问到,扣你细节时,回答对了,offer就是你的!