宁波网站推广方式怎么样,上海交通大学文科建设处网站,中国智慧城市建设门户网站,html代码在线萌新的学习笔记#xff0c;写错了恳请斧正。 目录
在定义结构体时起别名
匿名结构体
结构体的自引用
结构体的内存对齐
offsetof
内存对齐练习
为什么要内存对齐
平台原因
性能原因
书写规范
修改默认对齐数
结构体传参
位段#xff08;位域#xff09;
位段的…萌新的学习笔记写错了恳请斧正。 目录
在定义结构体时起别名
匿名结构体
结构体的自引用
结构体的内存对齐
offsetof
内存对齐练习
为什么要内存对齐
平台原因
性能原因
书写规范
修改默认对齐数
结构体传参
位段位域
位段的概念
位段的声明
位段的内存分配
位段的特殊声明
位段的跨平台性
位段注意事项 关于结构体的基本内容包括结构体的声明、创建、初始化、结构成员访问已经在笔记#15中讲述不再赘述。
在定义结构体时起别名
在定义结构体时前面直接加typedef进行起别名的操作不会影响结构体的创建。
typedef struct a
{int a;float b;char c;
} sta;
这就是定义了一个结构体类型struct a然后给它起别名为类型sta
但是这样就不能在定义结构体类型的时候直接创建结构体变量了
注意相关内容看下面
匿名结构体可以起别名这样就能正常使用了自引用同时起别名不能用别名自引用创建的优先顺序高于起别名
匿名结构体
结构体在声明时其实可以省略结构体标签名称。如下
struct
{int a;float b;char c;
} a;
这就是匿名的创建了一个结构体并声明了一个该类型的结构体变量。
注意在不起别名的情况下
匿名创建结构体如果没有直接声明几个对应的结构体变量之后就再也不能申请了。匿名创建结构体之后也再也无法找到这个结构体类型了。两个成员相同的匿名结构体不会被认为是同一种结构体比方说
struct
{int a;float b;char c;
} a;struct
{int a;float b;char c;
} b, *p;
在上面这种情况下如果令p等于a就是非法的因为两个匿名结构体类型不一样。
但是我们可以在创建匿名结构体的时候起别名这样就能通过别名正常使用结构体了
typedef struct
{int a;float b;char c;
} st;
比方说上面这段代码就能继续通过st这个类型名继续进行创建变量等操作
结构体的自引用
结构体可以自引用常用于链表以后讲
当然这不是说结构体的成员可以是该结构体本身
如果这样就无限套娃了大小无穷大
结构体的自引用指的是结构体的成员变量可以是该结构体的指针类型
比方说
struct chain
{int data;struct chain* next;
};
如果起别名和自引用同时进行的话自引用的地方不能用别名
比方说这样是不行的
typedef struct chain
{int data;st* next;
} st;
应该写成这样这边顺便把指针起别名了
typedef struct chain
{int data;struct chain* next;
} st;typedef st* pst;
结构体的内存对齐
我们现在研究一下结构体的内存大小
结构体类型占内存的大小是不是等于所有成员变量占内存的和呢
我们写一段程序验证一下
#include stdio.hstruct S1
{char c1;int i;char c2;
};int main()
{printf(%d\n, sizeof(struct S1));return 0;
}
这段代码在Win11 VS2022 x64 Debug的环境下输出12
而我们知道如果单纯的成员变量大小相加答案应该为6
所以结构体内存究竟是如何排布的呢
其实结构体在内存中的排布遵循内存对齐规则 1. 结构体的第一个成员对齐到和结构体变量起始位置偏移量为0的地址处 2. 其他成员变量要对齐到某个数字对齐数的整数倍的地址处 对齐数 编译器默认的对齐数 与 该成员变量大小 的 较小值 VS2022 的默认对齐数为8Linux gcc没有默认对齐数 3. 结构体总大小为结构体成员中最大的对齐数的整数倍 4. 如果结构体嵌套了结构体嵌套的结构体成员对齐到自己成员中最大对齐数的整数倍处并且占据单独计算自身大小那么大的空间因为内存对齐浪费的空间不会被下一个成员利用。结构体总大小就是所有对齐数包括嵌套结构体中的成员中最大对齐数的整数倍 offsetof
//定义于头文件 stddef.h
#define offsetof(type, member) /*implementation-defined*/ 在stddef.h头文件中有一个宏offsetof可以返回一个成员在结构体中的偏移量
其第一个参数是结构体类型名第二个参数是成员变量名
其返回值可以用%zd、%zu接收
内存对齐练习
#include stdio.hstruct S1
{char c1;int i;char c2;
};struct S2
{char c1;char c2;int i;
};struct S3
{double d;char c;char i;
};struct S4
{char c1;struct S3 s3;char d;
};int main()
{printf(%d\n, sizeof(struct S1));printf(%d\n, sizeof(struct S2));printf(%d\n, sizeof(struct S3));printf(%d\n, sizeof(struct S4));return 0;
}
在Win11 VS2022 x64 Debug的环境下4段输出为12、8、16、32
为什么要内存对齐
其实这是一种空间换时间的做法
平台原因
不是所有的硬件平台都能访问任意地址上的任意数据某些平台只能在某些地址处对齐的位置取对应类型的数据否则硬件异常。
性能原因
内存对齐的情况下访问速度一般会更快
访问未对齐内存的数据处理器可能需要作两次内存访问内存是一段一段访问的数据不对齐可能存放在两个内存的访问段内而对齐的内存访问仅需要一次访问
书写规范
所以为了节省空间我们创建结构体应该尽量使较小的成员变量在前面较大的放在后面
修改默认对齐数
我们可以使用预处理指令#pragma来修改编译器默认的对齐数
#include stdio.h#pragma pack(1) //设置默认对⻬数为1struct S
{char c1;int i;char c2;
};#pragma pack() //取消设置的对⻬数还原为默认int main()
{printf(%d\n, sizeof(struct S));return 0;
}
上面这段代码的输出结果就为6
结构体传参
与其他数据类型类似结构体传参也分为直接传参与传地址两种
#include stdio.hstruct S
{int data[3];int num;
} s { {1,2,3}, 1000 };void print1(struct S s)
{printf(%d\n, s.num);
}void print2(struct S* ps)
{printf(%d\n, ps-num);
}int main()
{print1(s); //传结构体print2(s); //传地址return 0;
}
两种方式作用相同但是我们优先使用传地址的方式
因为函数传参时需要拷贝实参作为形参压栈如果传递结构体本身会占用较多的内存
位段位域
位段的概念
位段是一种特殊的结构体类型其成员的内存宽度可以被我们规定
位段成员必须是int、signed、unsigned之间的一种C99以前
C99标准开始位段成员也可以使用布尔类型
位段的声明
位段的声明与结构体类似但是成员名可以省略代表直接浪费一段空间后有一个冒号和数字
#include stdio.hstruct A
{int _a : 2;signed _b : 5;unsigned _c : 10;int _d : 30;
};int main()
{printf(%d\n, sizeof(struct A));return 0;
}
这段程序在Win11 VS2022 x64 Debug的环境下的输出结果为8
为什么呢这与位段的内存分配有关
位段的内存分配
位段声明中冒号后面的数字就代表了其被规定占据多少个比特位
而整个位段总大小是按4个字节int类或者1个字节_Bool逐步分配的
上述代码中位段A内存申请了一次4个字节32位。这32位填充了_a、_b、_c后只剩下15位了发现不够继续填充_d就再次申请了4个字节用来填充数据_d所以总共占据了8个字节。
位段的特殊声明
相邻的几段如果类型占据的空间大小一致可以打包起来写在一起通常可以比方说
struct B
{int _a : 2, _b : 5, _c : 10;int _d : 30;
}; //宽8
规定空间可以省略代表占据一整个类型的空间比方说
struct B
{int _a : 2, _b : 5, _c : 10;int _d;
}; //宽8
这里_d就占据了整个4字节32位的空间
成员名可以省略用来占据一定不被使用的空间
struct C
{unsigned _a : 2;signed _b : 5;int _c : 30, _d : 1, _e : 3;int _f : 3, :2, _g : 4;
}; //宽12
这里_f和_g直接有两个比特是被占位的
如果宽度规定为0即零位域必须未命名代表直接开始下一个分配单元这边剩下来的丢掉
struct D
{unsigned _a : 2, :0;signed _b : 5, :0;int _c : 30, _d : 1, _e : 3;int _f : 3, : 2, _g : 4;
}; //宽16
位段的跨平台性
位段跨平台性很差原因如下
int位段被当做有符号还是无符号是不确定的机器的位数不一样导致类型宽度不一样位段在每个分配单元中数据从左往右填还是从右往左填不确定还有很多其他原因
所以虽然位段很省空间没事还是不要用位段
位段注意事项
位段不能取地址不能有指针变量会报错
因为位段的成员的起始位置可以不在整字节处没有地址