1.1 结构体的概念
结构体(struct):是一种复合数据类型,结构类型。
结构体,怎么理解?
你可以把它想象成一个桌面上的文件夹,这个文件夹里面可以有各种各样的文件,当然也还可以再有文件夹的存在,文件夹里面再放文件……。如果你要修改其中一个文件的内容,就是首先通过桌面上的那个文件夹作为入口,然后一个一个的进入文件夹去寻找你需要的文件,找到之后就可以随你修改了。
long、unsigned int 、short、char (相当于各种文件类型,比如 .txt、.c、.h)这些关键字是否很熟悉?这都是 C 语言定义好的数据类型,直接拿来用就行了。但是我想自定义一个别的类型的数据怎么办?
就靠 struct 了。结构体,顾名思义,就是将一个个数据类型构成一个数据类型以方便使用。
1.2 结构体的定义
struct
{
int a;
char b;
double c;
} s1; //没有标明其标签,声明了结构体变量s1
struct SIMPLE //标签被命名为SIMPLE
{
int a;
char b;
double c;
};
SIMPLE t1, t2[20], *t3; //用SIMPLE标签的结构体,另外声明了变量t1, t2[20], *t3
// 另外在许多大型软件的开发中,经常会定义结构体变量为宏变量以简化结构体的引用。简而言之,就是把typedef struct替换为Simple2。
typedef struct
{
int a;
char b;
double c;
} Simple2; //结构体的标签被命名为Simple2,
Simple2 u1, u2[20], *u3;//若去掉typedef则编译报错,error C2371: “Simple2”: 重定义;不同的基类型
1.3 结构体指针
//假设已有一个结构体名为Student
struct Student *pStruct
// 结构体类型 * 指针名;
一个指向变量的指针表示的是占内存中起始位置
一个指向结构体的变量的指针表示的是这个结构体变量占内存中的起始位置,同样它也可以指向结构体变量数组
# include <stdlib.h>
# include <string.h>
struct Student
{
char name[30];
int iNumber;
char cSex;
int iGrade;
}student;
int main ()
{
struct Student *pStruct;
pStruct = &student;
strcpy(pStruct->name,"瑶瑶");
pStruct->iNumber = 2016;
pStruct->cSex='W';
pStruct->iGrade=89;
printf("------------The student's information---------\n");
printf("Name:%s\n",(*pStruct).name);
printf("Number:%d\n",(*pStruct).iNumber);
printf("Sex:%c\n",(*pStruct).cSex);
printf("Grade:%d\n",(*pStruct).iGrade);
return 0;
}
-
student.iNumber
-
(*pStruct).iNumber
-
pStruct->iNumber
后两种是通过结构体变量指针来引用的结构体变量中的成员,第2种在* pStruct上加上小括号的原因是因为要提升 ”* pStruct“ 的运算优先级,因为在默认情况 . 运算符的优先级是比*运算符的优先级要高的。
个人比较喜欢用"->指向运算符"来引用结构体中的成员
2.1 结构体的大小与内存对齐
结构体内存对齐为两个原则:
原则 1. 前面的地址必须是后面的地址正数倍,不是就补齐。
原则 2. 整个Struct的地址必须是最大字节的整数倍。
struct MyStruct
{
char a; // 1 byte
int b; // 4 bytes
short c; // 2 bytes
long long d; // 8 bytes
char e; // 1 byte
};
我们可以看到,MyStruct中有5个成员,如果直接相加的话大小应该是16,但在32位MSVC里它的sizeof结果是32。
之所以结果出现偏差,为了保证这个结构体里的每个成员都应该在它对齐了的内存位置上,而在某些位置插入了Padding。
下面我们尝试考虑内存对齐,来计算一下这个结构体的大小。首先,我们可以假设MyStruct的整体偏移从0x00开始,这样就可以暂时忽略MyStruct本身的对齐。这时,结构体的整体内存分布如下图所示:
20140722214025291.png
我们可以看到,char和int之间;short和long long之间,为了保证成员各自的对齐属性,分别插入了一些Padding。
因此整个结构体会被填充得看起来像这样:
struct MyStruct
{
char a; // 1 byte
char pad_0[3]; // Padding 3
int b; // 4 bytes
short c; // 2 bytes
char pad_1[6]; // Padding 6
long long d; // 8 bytes
char e; // 1 byte
char pad_2[7]; // Padding 7
};
注意到上面加了Padding的示意结构体里,e的后面还跟了7个字节的填充。这是因为结构体的整体大小必须可被对齐值整除,所以“char e”的后面还会被继续填充7个字节好让结构体的整体大小是8的倍数32。
2.2 为什么我们需要内存对齐
- 并不是每一个硬件平台都能够随便访问任意位置的内存的。若读取的数据是未对齐的(比如一个4字节的int在一个奇数内存地址上),将拒绝访问,或抛出硬件异常。
- 考虑到CPU处理内存的方式(32位的x86 CPU,一个时钟周期可以读取4个连续的内存单元,即4字节),使用字节对齐将会提高系统的性能(也就是CPU读取内存数据的效率。比如你一个int放在奇数内存位置上,想把这4个字节读出来,32位CPU就需要两次。但对齐之后一次就可以了)。
参考:关于内存对齐的那些事
网友评论