- 结构体有名定义
- 无名定义
- 结构体嵌套定义
- 结构体内存对齐
- 结构体成员初始化
- 结构体变量引用
- 结构体的有名定义:直白点就是在struct 之后跟着结构体的名称,如下:
struct str_test{
int a;
char b;
};
- 结构体的无名定义:注意与有名定义的区别。这种方式不提倡,但是系统支持
struct {
int c;
char e;
}var1,var2; //结构体定义完毕直接定义变量。
- 结构体的嵌套定义:
struct str_test3{
int length;
int wide;
int high;
struct str_test4{
int id;
struct str_test3 var;
};
};//这种也可以,但是不提倡使用。
struct str_test3{
int length;
int wide;
int high;
};
struct str_test4{
int id;
struct str_test3 var;
};//是最常用方式,结构体互相独立;
- 结构体内存对齐
1)、平台原因:不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
2)、性能原因:数据结构应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
这些问题既和软件相关又和硬件相关。
- 所谓软件相关主要是指和具体的编程语言的编译器的特性相关,编译器为了优化CPU访问内存的效率,在生成结构体成员的起始地址时遵循着某种特定的规则,这就是所谓的 结构体成员“对齐”;
- 所谓硬件相关主要是指CPU的“字节序”问题,也就是大于一个字节类型的数据如int类型、short类型等,在内存中的存放顺序,即单个字节与高低地址的对应关系。
字节序分为两类:Big-Endian和Little-Endian,有的文章上称之为“大端”和“小端”,他们是这样定义的:
Little-Endian就是低位字节排放在内存的低地址端,高位字节排放在内存的高地址端;
Big-Endian就是高位字节排放在内存的低地址端,低位字节排放在内存的高地址端。
最本质原因是基本数据类型的长度有大有小,在使用的时候不可能长短一样,但CPU为了提高效率需要大批量一次访问多个内存区,可能存在同一个数据由于没有字节对齐而出现跨字节访问,这样效率会下降,甚至有些CPU会异常,比如一个大小固定的盒子如果要放进去不整齐的筷子,就会很麻烦要么折断要么换盒子,最好就是将筷子对齐同时放入盒子。
其中筷子也有大头和小头之分,具体怎么放那是一个约定,只要整体系统都采用统一的大头或小头就行。
我们在做盒子放筷子的试验的时候,很多时候是我们人工可识别的,能很智能完成复杂操作,那么计算机在处理数据的过程中的字节是怎么对齐的呢?
首先我们的程序在编译阶段,编译器会稍微做些智能化的工作,将内存的分配过程中按照一定的规则对齐,GCC 默认的对齐字节是 4字节, vc 默认的对齐字节是 8字节。
在编译过程中有个开关可以控制对齐的字节大小: #pragma pack(n) 这个宏就是来控制编译过程中的字节对齐用的,其中的 n 就是你可定制的对齐字节大小,如果不设置就按默认值处理。
我们可以编写程序来验证字节对齐的神奇:
struct A
{
char a;
int c;
};
问 如上结构体的大小是多少?为什么? 这是一个企业面试题,类似的面试题多不胜数。
将这个结构体编写程序跑起来看看就知道了,在运行之前我们按照以前学习的基本知识计算出 结果应该是:char =1 int =4 所以结果应该是5
但是非常抱歉,程序的运行结果却是:8 ,到底是我们计算错误还是计算机出故障了?其实我们都没错,计算机也没错 ,就是字节对齐的起作用。
编译器在编译代码的过程中,根据如下原则对数据对齐
- 数据成员对齐规则:结构(struct或联合union)的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员存储的起始位置要从该成员大小的整数倍开始(比如int在32位机为4字节,则要从4的整数倍地址开始存储)。
- 结构体作为成员:如果一个结构里有某些结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储。(struct a里存有struct b,b里有char,int,double等元素,那b应该从8的整数倍开始存储。)
原则- 收尾工作:结构体的总大小,也就是sizeof的结果,必须是其内部最大成员的整数倍,不足的要补齐。
- 结构体中的字节对齐,如果成员的大小大于等于4字节,则以4字节对齐,然后再排列下一个成员。如果两个成员的大小加起来大于四字节,则这两个相邻成员仍分别按照四字节对齐。仅当两个相邻成员的总大小在四字节内的时候,才会一起按照四字节对齐。
Demo:
typedef struct {
char a;
int b;
short c;
}Demo1;
Demo1 tmp;
sizeof(Demo1)=12
("%p", &tmp.a) = 0
("%p", &tmp.b) = 4
("%p", &tmp.c) = 8
Demo1 tmp.png
typedef struct {
char a;
short b;
int c;
}Demo2;
Demo2 tmp;
sizeof(Demo2)=8
("%p", &tmp.a) = 0
("%p", &tmp.b) = 2
("%p", &tmp.c) = 4
Demo2 tmp.png
typedef struct {
char a;
char b[4];
short c; //int c
}Demo3;
Demo3 tmp;
sizeof(Demo3)=8 // 12
("%p", &tmp.a) = 0
("%p", &tmp.b) = 1
("%p", &tmp.c) = 6
Demo3 short.png
Demo3 int.png
所以我们在使用结构体的过程中一定要注意它的这个特性,尤其是在需要用到结构体大小的地方,一定要考虑字节对齐产生的影响。
1)联合体是一个结构;
2)它的所有成员相对于基地址的偏移量都为0;
3)此结构空间要大到足够容纳最"宽"的成员;
4)其对齐方式要适合其中所有的成员;
- 结构体成员初始化
struct str_test
{
int a;
int b;
};
struct str_test var = {10,20}; ///结构体定义与变量定义分离,变量的初始化一次性完成
struct str_test var2 = {.a=30}; ///结构体变量的成员可以单独指定
等价
struct str_test var2 ;
var2.a =30;
struct str_test2
{
int aa;
int bb;
struct str_test cc;
};
struct str_test2 var3 = {10,20,{30,40}}; ///嵌套结构体初始化一次性完成
struct str_test2 var5 = {.cc.a =10}; ///嵌套结构体成员变量的逐层赋值
struct str_test3
{
int a;
int b;
}var6,var7; ///定义结构体的时候定义变量
struct str_test4
{
int a;
int b;
}var8 = {100,200}; ///定义结构体的时候定义变量并初始化
- 结构体变量的引用:
1、变量名.成员 ;
2、指针变量 ->成员;
struct str_test var;
var.a =10;
var.b =20;
struct str_test pvar = NULL; ///结构体指针与基本数据类型的指针一样,必须指向一个实际的内存地址。
pvar = (strcut str_test *) malloc(sizeof(struct str_test));
或者 定义变量同时定义指针:
struct str_test var, *pvar; pvar = &var;
网友评论