【C语言】结构体与offsetof实现

这篇具有很好参考价值的文章主要介绍了【C语言】结构体与offsetof实现。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

远看山有色,近听水无声。春去花还在,人来鸟不惊。 — 唐代·王维《画》

【C语言】结构体与offsetof实现,C语言,c语言,笔记,经验分享


    这篇博客我们会详细介绍结构体相关知识,干货满满。
    【C语言】结构体与offsetof实现,C语言,c语言,笔记,经验分享

    结构体的声明🍀

    一般来说结构体应该有成员列表和变量列表这两个基础的模式。
    【C语言】结构体与offsetof实现,C语言,c语言,笔记,经验分享
    例如描述一个学生:

    struct strudent
    {
    
    	char name[20];//名字
    	int age;//年龄
    	char sex[5];//性别
    	char id[20];//学号
    }; //分号不能丢
    
    

    当然也不是只有这一种声明。

    特殊的声明🐽

    在声明结构的时候,可以不完全的声明。
    比如:

    //匿名结构体类型
    struct
    {
    	int a;
    	char b;
    	float c;
    }x;
    struct
    {
    	int a;
    	char b;
    	float c;
    }a[20], * p;
    

    这就是匿名结构体
    但是如果是这样那么这两个结构体是一样的吗?

    //在上面代码的基础上,下面的代码合法吗?
    p = &x;
    

    其实是不行的;
    警告:
    编译器会把上面的两个声明当成完全不同的两个类型。
    所以是非法的。

    结构体的自引用✳️

    在结构中包含一个类型为该结构本身的成员是否可以呢?

    
    struct Node
    {
     int data;
     struct Node next;
    };
    //可行否?
    //如果可以,那sizeof(struct Node)是多少?
    

    当然是不可以,struct Node next永远都还有下一个struct Node next,这样怎么可以呢?
    正确的自引用方式:
    //代码2

    struct Node
    {
     int data;
     struct Node* next;
    };
    

    注意:

    typedef struct
    {
     int data;
     Node* next;
    }Node;
    //这样写代码,可行否?
    
    

    不行 首先这个匿名结构体,重定义为Node,但是重定义之前,就定义了Node*next,
    这个不被允许的!
    解决方案:

    typedef struct Node
    {
     int data;
     struct Node* next;
    }Node;
    

    还有数据结构中常见的写法

    typedef struct Node
    {
    	int data;//数据
    	struct Node* next;//指针
    } Node, * pNode;
    
    //pNode --> struct Node*
    

    结构体变量的定义和初始化 🦑

    定义和初始化:

    
    struct P
    {
    	int a;
    	int b;
    }p;
    struct P p1;//定义结构体变量p2
    struct P p2 = { 1,2 };//初始化:定义变量的同时赋初值。
    struct Stu        //类型声明
    {
    	char name[15];//名字
    	int age;      //年龄
    };
    struct Stu s = { "zhangsan", 20 };//初始化
    
    struct P
    {
    	int a;
    	int b;
    }p;//声明类型的同时定义变量p
    

    还有结构体嵌套的情况。

    struct book
    {
    	char name[20];
    	int num;
    	char id[20];
    }b1 = {"C语言",20,"1234"};
    struct Node
    {
    	struct book b;
    	struct Node* next; //结构体嵌套
    };
    int main()
    {
    	printf("%s %d %s\n", b1.name, b1.num, b1.id);
    	struct Node n = { {"Java", 20, "321"}, NULL };//结构体嵌套初始化
    	return  0;
    }
    

    结构体内存对齐

    我们已经掌握了结构体的基本使用了。 现在我们深入讨论一个问题:计算结构体的大小。
    这也是一个特别热门的考点: 结构体内存对齐
    我们来看这两个结构体

    struct S1
    {
    	char c1;
    	int i;
    	char c2;
    };
    struct S2
    {
    	char c1;
    	char c2;
    	int i;
    };
    
    int main()
    {
    	printf("%d\n", sizeof(struct S1));//12
    	printf("%d\n", sizeof(struct S2));//8
    
    	return 0;
    
    

    为什么大小不一样呢?
    【C语言】结构体与offsetof实现,C语言,c语言,笔记,经验分享
    这就要讲到结构体内存对齐
    首先得掌握结构体的对齐规则

    1. 第一个成员在与结构体变量偏移量为0的地址处。
    2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
      对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。

      VS中默认的值为8
    3. 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
    4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍

    我们来看
    【C语言】结构体与offsetof实现,C语言,c语言,笔记,经验分享
    这样一来S2也就很好解释了,

    struct S2
    {
    	char c1;
    	char c2;
    	int i;
    };
    

    c1 0
    c2 1
    i 4-7
    总大小刚好是8。

    一般的我们已经知道了,现在来看看嵌套的
    struct S3
    {
     double d;
     char c;
     int i;
    };
    printf("%d\n", sizeof(struct S3));
    struct S4
    {
    	char c1;
    	struct S3 s3;
    	double d;
    };
    printf("%d\n", sizeof(struct S4));
    

    struct S3的大小是16【C语言】结构体与offsetof实现,C语言,c语言,笔记,经验分享

    【C语言】结构体与offsetof实现,C语言,c语言,笔记,经验分享
    那么S4的大小是多少呢?【C语言】结构体与offsetof实现,C语言,c语言,笔记,经验分享
    首先看到最后一个原则,也就是S3在S4内对齐时大小为8,是最大对齐数。
    【C语言】结构体与offsetof实现,C语言,c语言,笔记,经验分享
    一共就是32。【C语言】结构体与offsetof实现,C语言,c语言,笔记,经验分享
    补充一句VS和Linus的

    【C语言】结构体与offsetof实现,C语言,c语言,笔记,经验分享为什么存在内存对齐?

    大部分的参考资料都这样说的:

    1.平台原因(移植原因):
    不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特 定类型的数据,否则抛出硬件异常 。(比如只能访问4的倍数的地址上的数据)

    抛出硬件异常是指在计算机系统中,发生了与硬件相关的错误或异常情况。这些异常可能由于硬件故障、硬件错误、硬件不兼容性或硬件操作不当等原因引起。

    2. 性能原因:
    数据结构(尤其是栈)应该尽可能地在自然边界上对齐。
    原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。(32位下一次访问4个byte)

    不考虑对齐要读取两次才能读完i的4个字节的内容(32位).
    【C语言】结构体与offsetof实现,C语言,c语言,笔记,经验分享
    而考虑的话,i只需要读取一次就能读完,
    这里实际上就提高了效率,牺牲空间来提升效率。
    【C语言】结构体与offsetof实现,C语言,c语言,笔记,经验分享
    要读取一次就能读完,
    这里实际上就提高了效率,牺牲空间来提升效率。
    总体来说: 结构体的内存对齐是拿空间来换取时间的做法。

    那在设计结构体的时候,我们既要满足对齐,又要节省空间,如何做到:

    其实很简单:

    //例如:
    struct S1
    {
     char c1;
     int i;
     char c2;
    };
    struct S2
    {
     char c1;
     char c2;
     int i;
    };
    

    都是同样的结构体成员,但是把小的放在一起就节省空间 (这是上面讲过的)。

    修改默认对齐数🐣

    struct S
    {
    	char c;//1
    	double d;//8
    };
    
    int main()
    {
    	struct S s;
    	printf("%d\n", sizeof(s));
    	return 0;
    }
    

    还是这样这个代码
    大小为16。
    【C语言】结构体与offsetof实现,C语言,c语言,笔记,经验分享
    但是呢,我们可以用用#pragma pack() 来修改默认对齐数

    #pragma pack(4)
    struct S
    {
    	char c;//1
    	double d;//8
    };
    #pragma pack()//取消设置的默认对齐数,还原为默认
    
    int main()
    {
    	struct S s;
    	printf("%d\n", sizeof(s));
    	return 0;
    }
    
    

    我们将默认对齐数修改到4时,大小就已经变化了,【C语言】结构体与offsetof实现,C语言,c语言,笔记,经验分享
    为什么呢?char c; 0
    double d; 4:8 = 4, 4-11。 一共就是12。

    也可以设置不内存对齐,紧挨着排#pragma pack(1)

    #pragma pack(1)
    struct S
    {
    	char c;//1
    	double d;//8
    };
    #pragma pack()//取消设置的默认对齐数,还原为默认
    int main()
    {
    	struct S s;
    	printf("%d\n", sizeof(s));
    	return 0;
    }
    

    【C语言】结构体与offsetof实现,C语言,c语言,笔记,经验分享

    结论: 结构在对齐方式不合适的时候,我么可以自己更改默认对齐数。
    虽然我们能够随便改变,但是我们要改最好还是让默认对齐数是2的幂,也是为了让我们的硬件有一个好的发挥

    offsetof及其实现💥

    其实offsetof是用宏来实现的,与一般的函数不同。

    【C语言】结构体与offsetof实现,C语言,c语言,笔记,经验分享
    【C语言】结构体与offsetof实现,C语言,c语言,笔记,经验分享

    这个宏是用来查看结构体成员的偏移量的,并返回偏移量值。
    我们知道结构体的首元素需要放在0偏移处。之后的成员要放在正确的偏移处。

    struct S3
    {
    	double d;
    	char c;
    	int i;
    };
    
    int main()
    {
    	printf("%d\n", sizeof(struct S3));
    	printf("%u\n", offsetof(struct S3, d));
    	printf("%u\n", offsetof(struct S3, c));
    	printf("%u\n", offsetof(struct S3, i));
    
    	return 0;
    
    }
    

    来看看这个【C语言】结构体与offsetof实现,C语言,c语言,笔记,经验分享
    我们可以算一算确实是这样,
    double d; 0 - 7
    char c; 8
    int i; 12-15
    一共16。
    每个变量最开始的地方就是这个变量相对于0的偏移量。
    那么既然这是一个宏那该如何去实现呢?
    【C语言】结构体与offsetof实现,C语言,c语言,笔记,经验分享
    我们需要借助这幅图好好理解一下,到底偏移量意味着什么。
    我们看到实际上偏移量就是该变量的地址的值减去首地址。
    假设0偏移处地址是0x0012ff40。
    0x0012ff48-0x0012ff40=8。
    0x12ff4c-0x0012ff40 = 12。
    这样我们对偏移量就有了不一样的理解。

    那么我们如何来实现呢?

    #include<stddef.h>
    #define OFFSETOF(struct_type,member)  (int)&(((struct_type *)0)->member)
    
    struct S3
    {
    	double d;
    	char c;
    	int i;
    };
    
    int main()
    {	printf("%u\n", offsetof(struct S3, d));
    	printf("%u\n", offsetof(struct S3, c));
    	printf("%u\n", offsetof(struct S3, i));
    
    	printf("%u\n", OFFSETOF(struct S3, d));
    	printf("%u\n", OFFSETOF(struct S3, c));
    	printf("%u\n", OFFSETOF(struct S3, i));
    
    	return 0;
    
    }
    

    我们就是用这一行代码来实现的。
    (int)&(((struct_type *)0)->member)

    1. (struct_type
      *)0:将0强制转换为指向struct_type类型的指针。这里假设结构体的实例位于0地址处,实际上并不是真的将结构体放在0地址处,而是为了方便计算偏移量而做的假设。
    2. ((struct_type*)0)->member:通过上述转换得到的指针,访问结构体中的成员member。这里并不会真的访问到实际的结构体,而是为了计算成员的偏移量而进行的操作。
      3. (int)&(((struct_type*)0)->member):通过取地址操作&,将上述成员的假设地址转换为实际的地址,并将其强制转换为int类型。这样就得到了成员的偏移量。

    总结😈

    这篇博客是用来梳理结构体知识的,并不算太难,算的上是对知识的检查和回顾,结构体对数据结构的学习十分重要希望大家都能学会 完(๑′ᴗ‵๑)
    【C语言】结构体与offsetof实现,C语言,c语言,笔记,经验分享【C语言】结构体与offsetof实现,C语言,c语言,笔记,经验分享

    【C语言】结构体与offsetof实现,C语言,c语言,笔记,经验分享文章来源地址https://www.toymoban.com/news/detail-601273.html

    到了这里,关于【C语言】结构体与offsetof实现的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

    本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

    领支付宝红包 赞助服务器费用

    相关文章

    • 结构体与模板

      在结构体中我们可以定义自己需要的成员及成员类型。成员类型可以是 C++ 的标准类型,也可以是另一个结构体。例如: 特别要需要注意的是这个类型不可以是自己,否则就无法通过编译。 例如下面这个程序: 结构体与其他类型相同,可以作为函数的参数进行传递。 将结构

      2024年02月13日
      浏览(33)
    • [经验] 做完腺样体手术打呼噜很严重怎么办 #媒体#笔记#经验分享

      做完腺样体手术打呼噜很严重怎么办 1、打呼噜很严重怎么办 打呼噜是一种常见的睡眠障碍,不仅让睡眠质量变得很糟糕,也会影响室友或家人的睡眠质量。幸运的是,有许多方法可以减少打呼噜的发生率,从而让睡眠变得更好。 保持良好的睡眠姿势非常重要。睡觉时,枕头

      2024年02月20日
      浏览(62)
    • 顶会论文投稿经验分享-笔记【CVPR 2023预讲会】

      视频链接:Panel: 顶会论文投稿经验分享与大模型时代下的科研_哔哩哔哩_bilibili 嘉宾: 王琦,上海交通大学计算机系博士生 任星宇,上海交通大学博士三年级研究生 李逸轩,上海交通大学2022级硕士研究生 官同坤,上海交通大学2023级博士生 李逸轩:不管是对比实验、主图、

      2023年04月23日
      浏览(55)
    • 【经验分享】自然语言处理技术有哪些局限性和挑战?

      个人认为,主要是两个难点: 1.语料,通常的语料很好解决,用爬虫从互联网上就可以采集和标注训练。但是我们接触很多项目和客户需求都是专业性很强的,例如:航天材料、电气设备、地理信息、化学试剂 等等。往往很多素材和语料都是很宝贵的,而且都是这些企业的内

      2024年02月21日
      浏览(47)
    • [word] word如何设置每行字符数 #笔记#经验分享#媒体

      word如何设置每行字符数 如何设置每行字符数? 设置WORD设定每行中的字符数和每页中的行数的具体步骤如下: 我们需要准备的材料分别是:电脑、word文档。 1、首先我们打开需要编辑的word文档,点击打开“页面布局”。 2、然后我们在弹出来的子菜单栏中点击打开页面设置

      2024年02月22日
      浏览(42)
    • 实现自动化获取1688商品详情数据接口经验分享

      获取电商平台商品详情数据,主要用过的是爬虫技术,过程比较曲折,最终结果是好的。我将代码都封装在1688.item_get接口中,直接调用此接口可以一步抓取。 1688商品详情页展示 传入商品ID调用item_get获取数据 响应示例  

      2024年02月07日
      浏览(48)
    • 利用敏捷开发工具实现敏捷项目管理的实践经验分享

      Scrum中非常强调公开、透明、直接有效的沟通,这也是“可视化的管理工具”在敏捷开发中如此重要的原因之一 。通过“可视化的管理工具”让所有人直观的看到需求,故事,任务之间的流转状态,可以使团队成员更加快速适应敏捷开发流程。 所以,有敏捷工具的支撑是非常

      2024年02月11日
      浏览(46)
    • [经验分享]gpt-3.5-Turbo|unity中实现http接口调用gpt新接口以及信息处理的实现案例分享

      最近openAI发布了目前chatGPT所使用的模型gpt-3.5-Turbo,之前使用了text-davinci-003模型做了一个galgame的AI女友对话的demo。这次趁着新接口的发布,对这个demo也同步更新了模型调用的代码。本篇文章将分享一下,如何在unity里使用UnityWebRequest实现与openAI的接口调用以及信息处理的示例

      2024年01月16日
      浏览(44)
    • C语言学习笔记——C语言结构

      C语言是一种面向过程的结构化的语言,同时具有高级语言和汇编语言的优点 所有的C语言程序经过编译和连接之后才能被计算机执行 头文件是一种特殊的文件,记录了很多可以直接引用头文件然后使用的函数,也可通过#define声明函数、变量、宏等的定义 头文件通常包含在源

      2024年02月11日
      浏览(45)
    • 阿赵的MaxScript学习笔记分享十四《Struct结构体的使用和面向对象的思考》

      MaxScript学习笔记目录 大家好,我是阿赵 之前写了一些MaxScript的学习笔记,里面实现的功能不算很复杂,所以都是使用了偏向于面向过程的方式去编写的。 我本人其实是比较习惯用面向对象的方式去编写代码。关于面向过程和面向对象之间的优缺点对比,各位如果不是很熟悉

      2023年04月21日
      浏览(35)

    觉得文章有用就打赏一下文章作者

    支付宝扫一扫打赏

    博客赞助

    微信扫一扫打赏

    请作者喝杯咖啡吧~博客赞助

    支付宝扫一扫领取红包,优惠每天领

    二维码1

    领取红包

    二维码2

    领红包