结构体
对齐的作用和原因:各个硬件平台对存储空间的处理上有 很大的不同。一些平台对某些特定类型的数据只能从某些特定地址开始存取。比如有些架构的CPU在访问一个没有进行对齐的变量的时候会发生错误,那么在这种 架构下编程必须保证字节对齐,其他平台可能没有这种情况,但是最常见的是如果不按照适合其平台要求对数据存放进行对齐,会在存取效率上带来损失。比如有些 平台每次读都是从偶地址开始,如果一个int型(假设为32位系统)如果存放在偶地址开始的地方,那么一个读周期就可以读出这32bit,而如果存放在奇 地址开始的地方,就需要2个读周期,并对两次读出的结果的高低字节进行拼凑才能得到该32bit数据,显然在读取效率上下降很多。
结构体对齐原则:
结构体中元素是按照定义顺序一个一个放到内存中去的,但并不是紧密排列的。从结构体存储的首地址开始,每一个元素放置到内存中时,它都会认为内存是以它自己的大小来划分的,因此元素放置的位置一定会在自己宽度的整数倍上开始(以结构体变量首地址为0计算),结构体的存储单元是否为结构体元素中最宽的元素的长度的整数倍,是,则结束;若不是,则补齐为它的整数倍
如果存在一个结构体:
struct A{ //struct是关键字, A是结构体标志
char a;
int b; //a,b是结构体成员列表
}s1; //s1是结构体变量
0 | char a |
1 | 填充 |
2 | 填充 |
3 | 填充 |
4 | int b |
5 | |
6 | |
7 |
2 :
struct X
{
char a;
double b;
int c;
}S2;
0 | char a |
8 | double b |
12 | |
15 | |
16 | int c |
23 |
================================================================================================
很有意思的一个题
struct X1 {char a; char b; char c; char d; char e;}A; struct X2 {short a; short b; short c;}B; struct X3 {short a; long b; char c;}C;
sizeof(A) =5, sizeof(B) = 6, sizeof(C) = 12 / //64位机器
结构体A中的元素都是char类型,每一个元素都是紧挨着排列的,所以5个char 占5个字节,再看结构体中最大类型还是 char,结构体最终的大小必须是结构体里面最大类型元素的整数倍,5个字节 是char所占内存的整数倍。
结构体C中 short a 占2个字节,long占4字节, short a放在结构体的首地址占两个
short a | 填充 | 填充 | b | c | 填充 | 填充 | 填充 |
0 2 3 4 8 9 12
struct X { char a; double b; int c; }S2;
struct X3 {short a; long b; char c; X S2;}C;
struct X4 {short a; long b; char c; char d; double e; int f;}D;
sizeof(C)= 40 sizeof(D)= 32 //64 位机
结构体C 0表示填充
a | 0 | 0 | b | c | 0 | 0 | 0 | 0 | 0 | 0 | 0 | s2.a | 0 | 0 | 0 | 0 | 0 | 0 | 0 | S2.b | s2.c | 0 | 0 | 0 | 0 |
2+2+4+1+7+(1+7+8+4+4) // 括号里面的是S2结构体的大小
结构体D
a | 0 | 0 | b | c | s2.a | 0 | 0 | 0 | 0 | 0 | 0 | 0 | S2.b | s2.c | 0 | 0 | 0 | 0 |
2+2+4+1+1+6+8+4+4 =28 结构体大小必须是结构体里(不管嵌套几个结构体)最大类型的整数倍 -》 32
================================================================================================
以上都是使用的默认的对齐系数,我们可以指定对齐系数
对齐数 = 对齐系数 与 该成员大小的较小值,
#pragma pack(n);
中的n就是对齐系数。
VS中默认的值为8;linux中的默认值为4
#pragma pack(2)
struct X { char a; double b; int c; }S2; // sizeof(S2)= 14
0 | char |
1 | |
2 | double |
3 | |
4 | |
5 | |
6 | |
7 | |
8 | |
9 | |
10 | int |
11 | |
12 | |
13 |
#pragma pack(n) 影响的是第二个以及后面元素存放位置的起始地址,不使用该参数,第二个以及后面的元素偏移到自己类型大小的最小倍数处开始存放,如果使用了该参数,那么第二个以及后面的元素存放的位置起始地址需要满足%n==0
结构体与位域
如果结构体中含有位域(bit-field),那么VC中准则是:
1) 如果相邻位域字段的类型相同,且其位宽之和小于类型的sizeof大小,则后面的字段将紧邻前一个字段存储,直到不能容纳为止;
2) 如果相邻位域字段的类型相同,但其位宽之和大于类型的sizeof大小,则后面的字段将从新的存储单元开始,其偏移量为其类型大小的整数倍;
3) 如果相邻的位域字段的类型不同,则各编译器的具体实现有差异,VC6采取不压缩方式(不同位域字段存放在不同的位域类型字节中),Dev-C++和GCC都采取压缩方式;
系统会先为结构体成员按照对齐方式分配空间和填塞(padding),然后对变量进行位域操作。
#include<stdio.h>
struct A
{
char a; // a 占用1字节 后面需要填充3字节
int b; // b 4个字节
char index; // index可以紧接着b排列,占用 1个字节 填充3个字节
};
struct test
{
char a:7; //占用7个比特位
int b:11; // b,c,d 加起来25个比特位
int c:4;
int d:10; //a b c d 共占用32个比特位
char index; //占用 1个字节 填充3个字节
};
struct test2
{
char a : 7;
int b : 11;
int c : 4;
int d : 10; //a b c d 共占用32个比特位 后面填充4个字节
double index; //占用 8个字节
};
struct test3
{
char a : 7;
int b : 11;
int c : 4;
int d : 10; //a b c d 共占用32个比特位
};
struct test4
{
char a : 7;
int b : 11;
int c : 14; // a b c 共占32个比特位
int d : 15; //站4个字节
};
struct test5
{
char a : 7;
int b : 11; //因为c不是位域,a b共同占用4个字节(有填充)
float c; //4字节
int d : 15; //4字节
};
struct test6
{
char a[10]; //地址 0x7ffd284a3420
int b : 11; //2字节
float c; //地址 0x7ffd284a342c
};
int main(int argc, char *argv[])
{
struct test t1;
struct test2 t2;
struct test3 t3;
struct test3 t4;
struct test5 t5;
struct test6 t6;
struct A a;
printf("size:%d\n",sizeof(a));
printf("size:%d\n",sizeof(t1));
printf("size:%d\n",sizeof(t2));
printf("size:%d\n",sizeof(t3));
printf("size:%d\n",sizeof(t4));
printf("size:%d\n",sizeof(t5));
printf("size:%d\n",sizeof(t6));
}
联合体:
利用union可以用相同的存储空间存储不同型别的数据类型,从而节省内存空间。在C Programming Language 一书中对于联合体是这么描述的:
1)联合体是一个结构;
2)它的所有成员相对于基地址的偏移量都为0;
3)此结构空间要大到足够容纳最"宽"的成员;
4)其对齐方式要适合其中所有的成员;
union { unsigned short int i; struct { char first; // 75 A char second; // 76 B }half; } number; number.i = 0x4241; cout << number.half.first <<endl; cout << number.half.second <<endl; number.half.first = 0x31; number.half.second = 0x32; cout << number.i << endl;
================================================================================================
number.i = 0x4241 ,我们知道这是一个联合体,共用一块内存,struct half 地址里保存的值是0x4241,first 和 second各占一个字符,那么他们的值是多少呢? 这里我们需要知道另一个知识点-------大小端
大小端
大端模式: 数据的高字节存在低地址 数据的低字节存在高地址
小端模式: 数据的高字节存在高地址 数据的低字节存在低地址
我们常用的x86结构都是小端模式,而大部分DSP,ARM也是小端模式,不过有些ARM是可以选择大小端模式
低地址 | 高地址 |
half.first | half.second |
0x41 | 0x42 |
number.half.first = 0x31;
number.half.second = 0x32; number.i = 0x3231