提供网站建设工具的品牌有哪些,wordpress签到打卡插件,网络平台推广具体是干啥,网站免费制作教程文章目录 前言一、结构体类型的声明结构体回顾结构体的特殊声明结构体的自引用 二、结构体的内存对齐对齐规则为什么存在内存对齐#xff1f;修改默认对齐数 三、结构体传参四、结构体实现位段什么是位段位段的内存分配位段的跨平台问题位段的应用位段使用的注意事项 总结 前言… 文章目录 前言一、结构体类型的声明结构体回顾结构体的特殊声明结构体的自引用 二、结构体的内存对齐对齐规则为什么存在内存对齐修改默认对齐数 三、结构体传参四、结构体实现位段什么是位段位段的内存分配位段的跨平台问题位段的应用位段使用的注意事项 总结 前言 事实上我们早就有过关于结构体的学习 现在我们再来深入理解它一下 一、结构体类型的声明
结构体回顾 结构是一些值的集合这些值称为成员变量结构的每个成员可以是不同类型的变量
结构的声明 struct tag { member-list; }variable-list; 例如我们想要描述一位学生: struct Stu { char name[20]; // 名字 int age; // 年龄 char sex[5]; // 性别 char id[20]; // 学号 }; // 分号不能丢 结构体变量的创建和初始化 运用大括号 { } 即可在其里面进行初始化 可以按照默认顺序初始化也可以按照指定顺序初始化 struct Stu s1 {.age 30, .name “Lisi”, .sex “nv”, .id “2023020405”};
结构体的特殊声明
在声明结构的时候可以不完全的声明
// 匿名结构体类型
// 只能使用一次
struct
{int a;char b;float c;
}x;struct
{int a;char b;float c;
}a[20], *p;我们可以思考一下对于上面两个结构体 p x; 这样的语句对不对 答案是不对的虽然两个结构体成员相同但是因为是匿名的在编译器看来是两种不同的类型
大部分情况下我们不会用到匿名结构体的你在使用的时候也需要注意一下
结构体的自引用 在结构中包含一个类型为该结构本身的成员是否可以呢 可以我们之后学数据结构的链表就是如此请持续关注我的博客
在结构体自引用使用的过程中夹杂了 typedef 对匿名结构体类型重命名也容易引入问题
typedef struct
{int data;Node* next; // err
}Node;答案是不行的因为Node是对前面的匿名结构体类型的重命名产生的但是在匿名结构体内部提前使用Node类型来创建成员变量这是不行的一言以蔽之就是你提前使用了重命名类型 二、结构体的内存对齐 现在我们要来深入探讨一个问题结构体的大小计算 其实也就是知识点 - 内存对齐 先让你诧异一下哈哈就拿这两个输出作为引子吧 对齐规则
结构体的对齐规则
结构体的第一个成员对齐到和结构体变量起始位置偏移量为0的地址处其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处对齐数 编译器默认的一个对齐数 与 该变量成员大小的较小值 - VS中默认的值为8 - Linux中gcc没有默认对齐数对齐数就是成员自身的大小结构体总大小为最大对齐数(结构体中每个成员变量都有一个对齐数所有对齐数当中最大的)整数倍如果嵌套了结构体的情况嵌套的结构体成员对齐到自己的成员中最大对齐数的整数倍处结构体的整体大小就是所有最大对齐数(含嵌套结构体中成员的对齐数)的整数倍
struct S1
{char c1; // 1Bytechar c2; // 1Byteint i; // 4Bytes
}我们会发现起始c1放在偏移量为 0 的位置上c2放在偏移量为1的位置上而 int 占 4 个字节取较小值得到对齐数是 4 对齐到 4 的整数倍的位置 4 上并占 4 5 6 7 这四个位置并且算下来整个结构体占据了 8 个字节是最大对齐数 4 的整数倍所以结构体的总大小为 8 浪费了 2 个字节的空间图示如下
我们再来看S2
struct S2
{char c1; // 1Byteint i; // 4Byteschar c2; // 1Byte
}c1先放在偏移量为 0 的位置上而 int 占 4 个字节对齐到整数位置 4 上并占 4 5 6 7四个位置而c2占在偏移量为8的位置上这时候结构体的最大对齐数是4而结构体的大小为0 ~ 8为9不是4的整数倍因此还要再来 3 个字节也就是 0 ~ 11最终会发现结构体的大小为 12 这其中就浪费了 6 个字节的内存空间图示如下
接下来我再给出一个例子你用做练习自己分析吧
struct S3
{double d; // 0 ~ 7char c; // 8int i; // 12 ~ 15
};答案是16个字节
那再来个嵌套结构体的
struct S4
{char c1;struct S3 s3;double d;
};首先c1放在偏移量为0的位置上而s3是个结构体按照上述第五条规则应该对齐到结构体成员中最大对齐数8的整数倍位置上显然s3放在8上并占 8 ~ 23位置(s3大小为16前面求过)这时候double放到对齐数8的整数倍上刚好24满足并占24~31位置这样的话S4大小为32恰好也满足了所有成员中对齐数的整数倍这一必要性条件浪费7个字节图示如下 如果你要验证可以自己去写输出部分验证或者可以打开内存监视 亦或者有一个宏 offsetof 可以用来计算结构体成员相较于起始位置的偏移量 需要注意的是这个宏需要包含一个头文件 stddef.h 为什么存在内存对齐
平台原因移植原因 不是所有的硬件平台都能够访问任意地址上的任意数据某些硬件平台只能在某些地址处获取某些特定类型的数据否则则出现硬件异常性能原因 数据结构尤其是栈应该尽量地在自然边界上对齐。原因在于为了访问未对齐的内存处理器需要两次内存访问而对齐的内存访问只需要一次访问。假设一个处理器总是从内存中取8个字节则地址必须是8的倍数。如果我们能保证所有的double类型的数据都在对齐的地址中那么就可以用一个内存操作来读取者写值了。否则我们可能需要进行两次内存访问因为对象可能分散在两个8字节内存块中。 按图来说的话性能原因如下
总而言之结构体的内存对齐是拿空间来换时间的一种做法 所以在设计结构体的时候我们既要满足对齐又要节省空间的话可以考虑将占用内存小的成员尽量集中在一起 比如上述的 S1 和 S2 结构体S1 设计的就比 S2 好 修改默认对齐数
#pragma 这个预处理指令可以改变编译器的默认对齐数
#include stdio.h
#pragma pack(1) //设置默认对齐方式为1
struct 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[1000];int num;
};struct S s { {1,2,3,4}, 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;
}上面 printf1 和 printf2 函数哪个好些 printf2的方式更好一些也就是说传地址方式更好一些本质上还是因为形参是实参的一份拷贝太浪费内存空间了
四、结构体实现位段 你可能只听说过段位没事现在我来跟你讲一下位段 没听过很正常这个知识点比较细致但是对于了解底层以及未来网络的学习还是很有用的
什么是位段 位段的声明和结构是类似的有两个不同
位段的成员必须是int、unsigned int或 signed int在C99中位段成员的类型也可以选择其他类型位段的成员名后边有一个冒号和一个数字
struct A
{int _a:2;int _b:5;int _c:10;int _d:30;
}
// A就是一个位段类型那么位段A所占内存的大小是多少 为什么不是按照前面讲述的内存对齐是16反而是8 位段的内存分配
位段的出现就是为了节省空间
位段的成员可以是 int、unsigned int、signed int 或者是 char 等类型。位段的空间上是按照需要以 4 个字节int或者 1 个字节char的方式来开辟的。位段有很多不确定因素位段是不跨平台的注意可移植的程序应避免使用位段。(比如说一个字节从左向右还是从右向左使用)
假设从右向左来个例子如下 _a给两个比特位_b给5个比特位_c给10个比特位_d给30个比特位 _a、_b、_c没什么好说的但是这个_d就有两种存储方式了是直接弃掉第一个 int 剩下的15个比特位新开一个 int 用30个比特位存_d 还是继续使用完这15个比特位再新开个 int 用15个存_d其实C语言也没有规定完全取决于编译器哈哈这又是不确定性
VS下其实是从右向左存储如果剩余的空间不够下一个成员使用就新开空间并浪费 我们来通过下面这段代码来进行验证
#include stdio.hstruct S
{// char就好在是一个字节一个字节开辟char a : 3;char b : 4;char c : 5;char d : 4;
};int main()
{struct S s { 0 };s.a 10;s.b 12;s.c 3;s.d 4;printf(%zd, sizeof(struct S)); // 3return 0;
}首先a 和 b共占用了7个比特位剩下1个比特位不够 c 于是新开一个字节放 c同理再新开一个字节放 d 接下来我们给 a 赋值为 10即1010截断后三位放入010同理b放入1100 这时候第一个字节的8个比特位就是01100010 同理c放入00011 这时候第二个字节的8个比特位就是00000011 同理d放入0100 这时候第三个字节的8个比特位就是00000100 那么按照十六进制位这三个字节就是分别就是 0x62、0x03、0x04打开内存 果然如此 位段的跨平台问题
int 位段被当成有符号数还是无符号数是不确定的位段中最大位的数目不能确定。16位机器最大1632位机器最大32写成27在16位机器会出问题。位段中的成员在存储中有右分配还是从右向左分配标准尚未定义。当一个结构包含两个位段第二个位段成员比较大无法容纳于第一个位段剩余的位置是含有剩余的位置是利用这是不确定的。
位段的应用 同开头所说位段在于网络传输部分还是用处蛮大的 这个自行了解我也是现查的具体原理大家自辨
位段使用的注意事项 位段的几个成员共享同一个字节这样有些成员的起始位置并不是某个字节的起始位置那么这些位段处是没有地址的。内存中每个字节分配一个地址一个字节内部的bit位是没有地址的。 所以不能对位段的成员使用操作符这样就不能使用scanf直接给位段的成员输入值只能是先输入放在一个变量中然后赋值给位段的成员。
struct A
{ int _a : 2; int _b : 5; int _c : 10; int _d : 30;
}; int main()
{ struct A sa {0}; scanf(%d, sa._b); // error// right int b 0; scanf(%d, b); sa._b b; return 0;
}总结 我也没想到结构体深入了解竟然能讲出那么多东西来哈哈 本篇还是比较难的请你和我好好消化一下准备接下来的学习~