1.结构体和数组都是聚合数据类型,它们之间有以下的区别:
数组是同种类型元素的集合,而结构体是相同或者不同的数据元素的集合。
数组名在传参时会退化为一个指针,但是结构体在作为函数参数时不会发生退化。
数组可以通过下标来访问某个元素,而结构体是通过结构体的成员名来访问成员的。
struct A
{
char a;//1 对齐数:1
double b;// 8 对齐数:8
int c;//4 对齐数:4
};
这叫做声明了一个结构体类型,并没有开辟空间。
上面定义的结构体是定义了一种聚合类型,这是一种自定义类型。
而int . float . char.等属于内置类型。
结构体的基本概念:
一般情况下,结构体标签和结构体变量名必须至少有一个。
在定义一个结构体变量时,可以使用标签,例如struct tag a;这里的a即就是结构体变量。定义结构体变量时开辟空间。
访问结构体成员时可以有两种方法:
1.直接访问:使用(.)操作符,.操作符的优先级是从左到右。
2.间接访问:使用箭头操作符(->),应注意,这里的箭头操作符的左操作数必须是指针变量。
结构体的不完整声明:
如果有两个结构体需要互相引用对方,那么最好使用不完整声明,在引用对方时最好定义指向那个结构体的指针,而不是定义那个结构体的变量。
结构体的初始化类似与指针,可以使用{}来初始化结构体成员。
在c语言中,结构体成员至少要有一个,在vs2013下,不能定义一个空结构体,而在linux下,空结构体大小为0,在c++下,空结构体的大小为1.
结构体为什么会有内存对齐?
对齐原因:
1、平台原因(移植原因):
不是所有的硬件平台都能访问任意地址上的任意数据的;
某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。2、性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。
原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
其他:
指定对齐值:#pragma pack (value)时的指定对齐值value。
#pragma pack () /取消指定对齐,恢复缺省对齐/
结构体的内存对齐原则:
1.第一个成员在与结构体变量偏移量为0的地址处——也就是第一个变量没有对齐数
2.其他成员变量要对齐到它的(对齐数)的整数倍的地址处
//对齐数 = 编译器默认的一个对齐数与该成员大小的较小值。
// vs—-默认为8;
// linux—默认为43.结构体总大小为最大对齐数(每个成员变量除了第一个成员都有一个对齐数)的整数倍。
4.如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是
最大对齐数(含嵌套结构体的对齐数)的整数倍。
下面通过例子来分析:(在linux下默认的对齐数是4)
注意:括号内是变量名,地址在累加,而每次前面的地址必须是后面变量对齐数的整数倍。
所以结构体p的大小为 4(e)+4(x)+1(y)+3(补得空间,因为ss结构体的最大对齐数为4) = 12(4的倍数)
12+3*20(3个结构体的大小) = 72
72(是3的倍数)+3 = 75
75(不是4的倍数,所以加1) +1 = 76(是4的倍数)
76+4(指针变量p的大小) = 80(是结构体p的最大对齐数 4的倍数)
所以结构体p的大小为80
位段(一般在协议中使用):(可以访问一个数据中的bit位)
使用位域的主要目的是压缩存储
位段成员必须声明为int 或unsigned int 或signed int类型。
声明一个位段时:在成员名后是一个冒号和整数。例如:unsigned int a: 2.
其大致的规则为:
1) 如果相邻位域字段的类型相同,且其位宽之和小于类型的sizeof大小,则后面的字段将紧邻前一个字段存储,直到不能容纳为止;
2) 如果相邻位域字段的类型相同,但其位宽之和大于类型的sizeof大小,则后面的字段将从新的存储单元开始,其偏移量为其类型大小的整数倍;
3) 如果相邻的位域字段的类型不同,则各编译器的具体实现有差异,VC6采取不压缩方式,Dev-C++采取压缩方式;
4)如果位段之间穿插着非位域字段,则不进行压缩。
5)结构体总大小为最大对齐数的整数倍。
总体来看,位段的对齐方式和结构体对齐方式类似,声明位域时最好声明为同一种类型的。
测试位段在内存中的存储方式:
#define _CRT_SECURE_NO_WARNINGS 1
#include
#include
#include
struct A
{
unsigned int a : 4;
unsigned int b : 4;
unsigned int c : 6;
unsigned int d : 13;
unsigned int e : 6;
}obj;
int main()
{
printf("%d\n", sizeof(struct A));//8
obj.a = 0xf;
obj.b = 0xe;
obj.c = 0x21;
obj.d = 0x1def;
obj.e = 0x1f;
printf("%d, %d, %d\n", obj.a, obj.b, obj.c);
system("pause");
return 0;
}
位段的对齐方式类似与结构体:
注意:
当一个位段变量的大小可以和前面已定义的位段存放在一个整型大小的空间时,就不用另外开辟空间,但是如果不够存储,则另外再开辟一个整型变量的大小,从它的类型的对齐数的倍数的地址处开始存储。
联合体(union):(也有内存对齐,所有的成员都必须对齐)
当多个数据需要共享内存或者多个数据每次只取其一时,可以利用联合体(union)。
1)联合体是一个结构;
2)它的所有成员相对于基地址的偏移量都为0;
3)此结构空间要大到足够容纳最”宽”的成员;
4)其对齐方式要适合其中所有的成员;
下面解释这四条描述:
由于联合体中的所有成员是共享一段内存的,因此每个成员的首地址相对于于联合体变量的基地址的偏移量为0,即所有成员的首地址都是一样的。为了使得所有成员能够共享一段内存,因此该空间必须足够容纳这些成员中最宽的成员。对于这句“对齐方式要适合其中所有的成员”是指其必须符合所有成员的自身对齐方式。
#define _CRT_SECURE_NO_WARNINGS 1
#include
#include
#include
struct A{
int a;
char b;
double c;//16
};
union un{
double a;
char b;
char g[39];//39 注意:结构体总的大小也要考虑内存对齐,联合体也应该遵循它里面的每个成员都应该内存对齐。
char c[5];
struct A d;//16
}obj;
int main()
{
printf("union :%d\n",sizeof(obj));
system("pause");
return 0;
}
#define _CRT_SECURE_NO_WARNINGS 1
#include
#include
#include
#pragma pack(4)//将默认对齐数设置为了4
struct A{
int a;//4
union {
int b;
char c;
char d[5];
}obj;//8
struct {
int e;
double f;
char g[5];
};//20
double x;//8
};//最终结构体的大小为40个字节
int main()
{
printf("STRUCT : %d\n",sizeof(struct A));
system("pause");
return 0;
}
应特别注意的是:联合体也需要内存对齐,指的是它内部所有的成员都应该自身对齐。
2017.1.17.更新
求解结构体中任意一个变量的地址的偏移量?——–offsetof宏
1.#define offsetof(Type,member) (size_t)&(((Type*)0)->member)
宏的功能:返回结构体中成员变量的偏移量
分析表达式:由内到外逐层分析
①0
②(Type )0—–可以看出,此表达式的意义是:将整型0强制类型转换为(Type )类型的,(这里的Type指的是结构体类型),
那么分析得:将0强转为了结构体指针类型,那么0代表的就是一个sizeof(Type)大小的内存空间的首地址③(Type *)0->member—-可以这样理解:定义了一个结构体指针,即强制类型转换后的0,然后是访问结构体成员的两种方法中的第二种,使用指针,形式为:结构体指针->成员变量,所以这个表达式就是访问结构体变量。
④&(((Type*)0)->member)——这个很好理解,就是给成员变量取地址。
⑤(size_t)&(((Type*)0)->member)——-将成员变量的地址强制类型转换为size_t(unsigned int),地址肯定不会为负啦…
综之分析:因为结构体的首地址取的是0,那么成员变量的地址即就是结构体成员变量的偏移量啦。
下面举例来看: