内存对齐相关代码:
位段与练习:
枚举和联合:
结构体
结构是一些值的集合,这些值称为成员变量。结构的每个成员变量可以是不同类型。
结构体的声明与定义
struct tag
{
int x;
char y;
double z;
}s1,s2,s3;//这里的s1,s2都是创建的结构体变量
//匿名结构体类型
struct
{
char name[10];
int num;
}s1;//匿名结构体类型只能用一次
//定义
struct tag s;
即便两个匿名结构体的成员变量完全相同,编译器会把这两个声明当成完全不同的两个类型。
结构体的自引用
数据结构中最常用的。如:链表实现中节点的定义。
数据结构:数据在内存中的存储结构,包括线形、顺序表、链表、树形、二叉树。
struct Node
{
int data;//4Byte
struct Node* next;//x86:4Byte;x:8Byte
};
结构体的成员变量不能是同类型的结构体,只能是同类型的结构体指针。(以下为错误写法:)
struct Node
{
int data;//4Byte
struct Node next;
};
//这样的结构体发生嵌套,编译器会报错
结构体的重命名
typedef struct Node
{
int data;//4Byte
struct Node* next;//x86:4Byte;x:8Byte
}Node,*LinkList;
//等价于
typedef struct Node Node;
typedef struct Node* LinkList;
结构体变量的初始化
1、成员变量里没有结构体类型。
struct Point
{
int x;
int y;
};
int main()
{
//初始化:在创建的同时赋值
struct Point p1 = { 2,3 };
return 0;
}
2、成员变量里面有结构体类型。
struct stu
{
char name[10];
int age;
};
struct score
{
int n;
char ch;
struct stu;
};
int main()
{
struct score sc1= { 1,'c',{"zhangsan",18} };
return 0;
}
结构体传参
void print1(struct S ss); //传值
void print2(const struct S* ps);//传地址
结构体内存对齐
!!!非常重要!!!
相同成员变量的结构体,所占内存空间的大小并不完全相同。
结构体对齐规则
对齐数 = min( 编译器默认的对齐数 , 该成员的字节大小 )
- vs默认的对齐数为8。
- 其他编译器没有默认对齐数,数据的对齐数即本身大小。
宏:offsetof
头文件<stddef.h>
size_t offsetof( structName, memberName );
//structName:结构体名
//memberName:成员变量名
//返回值:该成员变量相对于起始地址的偏移量
int main()
{
printf("%d\n", offsetof(struct S1, c1));//0
printf("%d\n", offsetof(struct S1, i));//4
printf("%d\n", offsetof(struct S1, c2));//8
return 0;
}
结构体对齐的练习
1、相同成员变量,不同的对齐方式所占空间大小不同。
2、结构体本事的最大对齐数为:8(为下面的嵌套铺垫)
3、嵌套结构体的内存对齐
内存对齐的意义
1、平台原因
2、性能原因
数据结构应尽可能地在自然边界上对齐。
- 访问未对齐的内存,处理器需要两次内存访问。
- 访问对齐的内存,处理器只需要一次内存访问。
3、总结
结构体的内存对齐就是拿空间换时间的做法。
4、设计结构体的方法
尽可能让占用空间小的成员集中在一起。
默认对齐数的修改
//#pragma once
//头文件中使用,功能:防止头文件被多次引用
#pragma pack(4)//修改默认对齐数
struct S
{
int i;
double d;
};
//默认对齐数为8:占16字节
//默认对齐数为4:占12字节
#pragma pack()//取消修改
//pragma pack每次使用最好设置范围
位段
位段通过结构体来实现,位段是用来节省空间的。
位段的声明和结构体类似,但有两个不同:
1、位段的成员名必须是整型家族的类型(char、int、unsigned int等)。
2、位段的成员名后面有一个冒号和一个数字。
struct A
{
//冒号后面的bit位
int _a : 2;
int _b : 5;
int _c : 10;
int _d : 30;
};
//2+5+10+30=47
//6 Byte = 48 bit
// 实际大小
//8 Byte = bit
位段的内存分配
- 位段的成员可以是char、int、unsigned int,必须整型家族类型的。
- 位段的空间是按照需要4个字节(int)或者1个字节(char)的方式来开辟的。
- 位段涉及很多不稳定因素,是不可跨平台的。
位段的跨平台问题
- int位段被当做有符号数还是无符号数是不确定的。
- 位段中最大位的数目不确定。
- 位段的成员在内存中从左向右分配,还是从右向左分配,标准未定义。
- 当一个结构体中包含两个位段,第二个位段成员较大,无法容纳第一个位段的剩余位时,是舍弃剩余的位还是利用这是不确定的。
不同的编译器、机器等对于位段的实现没有统一标准,但这并不意味着位段没有任何意义。位段本身并不跨平台,但是我们可以用不同的代码来实现位段在平台应用。
枚举
枚举:把实际存在的数据抽象出来
枚举类型的定义
enum Day
{
Mon=1,//枚举常量
Tues,
Wed,
Thur,
Fri,
Sat,
Sun
};
//枚举常量从第一个开始一次递增1
//默认第一个枚举常量为0
枚举的优点
为什么用枚举而不用#define?
- 增加了代码的可读性和维护性。
- 比起#define定义的标识符,枚举有类型检查,更加严谨。
- 防止命名污染。
- 便于调试。
- 使用方便,一次可以定义多个变量。
#define语句在预处理阶段就会被替换。在调试时的代码已经不是看到的代码了。
联合(共用体)
- 联合也是一种特殊的自定义类型,
- 这中类型定义的变量也包含一系列的成员,特征是这些成员共用同一块空间。
联合的定义
union Un
{
int a;
char c;
};
//占4个字节
//联合的任意两个成员不能同时使用
联合体的特点
- 联合体的成员共用同一块内存空间。
- 一个联合体的大小,至少是最大成员的大小。
- 任意两个成员不能同时被使用。
联合体的应用
1、判断大小端字节序
int check_sys_u()
{
union Un
{
char c;
int i;
}u;
u.i = 1;
//返回1是小端,返回0是大端
return u.c;
}
联合体大小的计算
union U
{
//char arr[5];//对齐数是 char=1
short arr[7];//对齐数是 short=2
int i;
//对齐
};//16个字节