63.【C语言】再议结构体(上)
1.复习
20.【C语言】初识结构体(重要)
48.【C语言】结构体补充
2.结构体的特殊声明
01.匿名结构体
*定义
不完全声明,即结构体没有自己的名字(没有结构体标签)
*注意事项
1.匿名结构体只能使用一次
2.下列代码合法吗
struct
{
int a;
char b;
float c;
}x;
struct
{
int a;
char b;
float c;
}*p;
int main()
{
p = &x;
return 0;
}
虽然编译没有问题 ,但生成解决方案会报警告
warning C4133: “=”: 从“*”到“*”的类型不兼容
编译器认为:x;和*p;是两个不同的结构体
02.自引用
前置知识
*数据结构:数据在内存中的存储结构
*常见的数据结构:数组,记录,链表
*一个典型的数据结构----链表:一种物理存储单元上非连续(因为用了指针)、非顺序(因为用了指针)的存储结构,用于线性方式存储数据(对比数组:元素的顺序集合)
下面这张图(摘自《计算机科学导论》)体现非顺序65-->66-->72-->96-->85-->74
*链表中的每一个元素称为结点,结点包含两个部分:数据和链(链是下一个节点的地址(指针),指向下一个节点的数据),链最后一个元素包含一个空指针
*链表名是头指针的名字,下面这张图(摘自《计算机科学导论》)
《计算机科学导论》对链表的解释
单向链表图例
代码实现链表的一个结点
struct Node
{
int data = 0;//数据
struct Node* next;//链(地址)(指针)
};
注:Node n.节点,结点
运行上方代码,x86环境下在内存中查看结构体
typedef重命名结构体
禁止使用typedef重命名匿名结构体
typedef struct Node
{
int data;
struct Node* next;
}Node;
3. 结构体内存对齐(考频高)
如果看过李忠老师的《x86汇编语言:从实模式到保护模式 第二版》将会比较好理解
8086CPU的内存对齐方式
书中提到:8086CPU在存储数据段时采用16字节对齐的方式,如果定义的数据没有到16字节的整数倍,将以00填充满
例如:
db "Hello"
db "World!"
在内存中查看数据的排布:
设该字符串有x个字符(即x bytes)
其在内存中的存储占用的字节数S的公式:
① x ≦ 16,S=16
②x = 16k (k为正整数),S=16k
③ 16k<x ≦16(k+1) (k为正整数),S=16(k+1)
对于结构体来说,内存对齐有4条规则
1. 结构体的第一个成员对齐到和结构体变量起始位置偏移量为0的地址处
2. 其他成员变量(即从第二个成员变量开始)要对齐到某个数字(对齐数)的整数倍的地址处
对齐数 = 编译器默认的一个对齐数与该成员变量大小的较小值
对齐数要看偏移量
-->VS中默认的值为8 bytes
-->Linux中gcc没有默认对齐数,对齐数就是成员自身的大小
3. 结构体总大小(注意这里说的不是偏移量)为最大对齐数(结构体中每个成员变量(含第一个成员变量)都有一个对齐数,所有对齐数中最大的)的整数倍,这样可能会浪费一些空间
4.如果嵌套了结构体的情况,嵌套的结构体成员对齐到自己的成员中最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体中成员的对齐数)的整数倍。
例题:非嵌套结构体
求下列代码的执行结果(在VS2022中测试)
#include <stdio.h>
struct s1
{
char a;
char b;
int c;
};
struct s2
{
char a;
int c;
char b;
};
int main()
{
printf("%zd,%zd", sizeof(struct s1), sizeof(struct s2));
return 0;
}
提示:结构体s1和结构体 s2唯一不同的地方在成员变量的排列顺序
s1:①char a ②char b ③int c
s2:①char a ②int c ③char b
答案速查
分析
在3.【C语言】内置数据类型文中讲过char占1个字节,int占4个字节
由内存对齐有4条规则可以画图(设CPU为结构体从偏移量为0处开辟空间):
对于结构体s1
1 < 8,a的对齐数为1
4 < 8,c的对齐数为4
从偏移量0到偏移量7,一共占8个字节,为最大对齐数4的整数倍(对齐数1 < 对齐数4)
因此sizeof(struct s1) == 8
对于结构体s2
c存储的位置必须从偏移量为4的最近位置开始
存储完a,c,b后,8-0+1=9 bytes,不是最大对齐数4的整数倍,因此要至偏移量为11的位置
因此打印结果为8,12
练习
求下列代码的执行结果(在VS2022中测试)
#include <stdio.h>
struct s3
{
double d;
char c;
int i;
};
int main()
{
printf("%zd", sizeof(struct s3));
return 0;
}
答案速查
分析
例题:嵌套结构体
求下列代码的执行结果(在VS2022中测试)
#include <stdio.h>
struct s3
{
double d;
char c;
int i;
};
struct s4
{
char c1;
struct s3 s3;
double d;
};
int main()
{
struct s4 s4;
printf("%zd", sizeof(struct s4));
return 0;
}
答案速查
分析
结构体的整体大小就是所有最大对齐数的整数倍(s3的最大对齐数和s4的最大对齐数都是8)
4.内存对齐的原因
1. 平台原因(移植原因)
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常
2. 性能原因
数据结构(尤其是栈)应该尽可能地在自然边界上对齐.原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问.假设一个处理器总是从内存中取8个字节,则地址必须是8的倍数.如果我们能保证将所有的double类型的数据的地址都对齐成8的倍数,那么就可以用一个内存操作来读或者写值了.否则,我们可能需要执行两次内存访问,因为对象可能被分放在两个8字节内存块中
总体来说:结构体的内存对齐是拿空间来换取时间的做法
5.内存对齐节省空间的方法
让占用空间小的成员尽量集中在一起
例如:
struct S1
{
char c1;
int i;
char c2;
};
struct S2
{
char c1;
char c2;
int i;
};