嵌入式C语言(三)

typeof()

使用typeof可以获取一个变量或表达式的类型。
typeof的参数有两种形式:表达式或类型。

int i;typeof(i) j = 20; --> int j = 20;typeof(int *) a; -->int *a;
int f();         -->typeof(f()) k;--? int k

我们可以看出通过typeof获取一个变量的类型int后,可以使用该类型再定义一个变量。

高级用法:

typeof (int *) y;-->int *y  y是一个指向int类型的指针typeof (int) *y;     执行int类型的指针变量ytypeof (*x) y;       y是一个指向x类型的指针  (这下是不是对前面两个的小小区别有所感悟)typeof (int) y[4];    y这个数组元素的类型是int  (换成x)就是x的类型   int y[4]   typeof (*x) y[4];   *x y[4]typeof (typeof (char *)[4]) y;//-->char *y[4]  字符指针数组  这个里面的数组元素都是 指针变量 typeof (char *)[4] --> char *[4]   type (char *)类型不就是括号里面的   然后 在加一层-->typeof(int x[4]) y; int y[4]对于以上其实观察的时候就一层层的剥开即可

typeof对于数组的操作还是挺花哨的,惊艳了。

container_of宏

Linux内核第一宏:container_of

听起来这么牛的宏,我们来一睹芳容一下

#define offsetof(TYPE, MEMBER) ((size_t) & ((TYPE *)0)->MEMBER)#define container_of(ptr,type,member)({ \const typeof( ((type *)0)->member) * __mptr = (ptr);\(type *)((char *)__mptr - offsetof(type,member));})

怎么样,内核里到处都是这。

宏中有宏。

三个参数:

  • type: 结构体的类型
  • member: 结构体成员
  • ptr: 结构体成员member地址

这个宏的作用:
通过结构体的某一成员的地址,来获取这个结构体的首地址。

这下再看是不是大概还是好点了。

我们来看个实用例子。

struct student
{int age;int num;int math;
};int main(void)
{struct student stu;struct student *p;p = container_of(&stu.num, struct student, num);return 0;
}

定义一个结构体类型student,
然后定义一个结构体变量stu,

知道了结构体成员变量stu.num的地址,

那么我们就可以通过container_of宏来获取结构体变量stu的首地址。

container_of这个玩意有什么用呢?

因为内核中有很多的结构体类型数据,为了抽象,会对结构体进行多次的封装,往往一个结构体里面又包含了多个结构体。无限套娃。不同的层次,不同的模块,使用的是对应的不同封装程度的结构体。

这个是不是有点让你联想到面向对象思想,没想到?没事我知道你认真专注于当下。

这样的优点我就不多说了。面向对象的优点。

在内核中,我们传递个函数的参数是某个结构体的成员,在这个函数中,还想用这个结构体的其他变量,这不是就需要container_of出场了。

找到了这个结构体的首地址–>

offsetof()函数主要用来计算member成员相对于结构体起始地址的偏移量。

现在我们来详细看看这个宏定义:

这里需要你先知道结构体怎么存储的。

结构体作为一个复合类型数据,它里面可以有多个成员。当我们定义一个结构体变量时,编译器要给这个变量在内存中分配存储空间根据每个成员的数据类型和字节对齐方式,编译器会按照结构体中各个成员的顺序,在内存中分配一片连续的空间来存储它们。

将数字0通过强制类型转换,转换为一个指向结构体类型为student的常量指针,然后分别打印这个常量指针指向的各成员地址。运行结果如下。

因为常量指针的值为0,即可以看作结构体首地址为0,所以结构体中每个成员变量的地址即该成员相对于结构体首地址的偏移。

知道了结构体的相对偏移地址,用结构体成员的地址减去相对偏移,就可以得到结构体的首地址。

从语法角度来看,container_of宏的实现由一个语句表达式构成。语句表达式的值即最后一个表达式的值。

取结构体某个成员member的地址,减去这个成员在结构体type中的偏移,运算结果就是结构体type的首地址。

因为语句表达式的值等于最后一个表达式的值,所以这个结果也是整个语句表达式的值,container_of最后会返回这个地址值给宏的调用者。

结构体的成员数据类型可以是任意数据类型,为了让这个宏兼容各种数据类型,我们定义了一个临时指针变量__mptr,该变量用来存储结构体成员MEMBER的地址,即存储宏中的参数ptr的值。如何获取ptr指针类型呢。


#define offsetof(TYPE, MEMBER) ((size_t) & ((TYPE *)0)->MEMBER)#define container_of(ptr,type,member)({ \const typeof( ((type *)0)->member) * __mptr = (ptr);\(type *)((char *)__mptr - offsetof(type,member));})三个参数:+ type: 结构体的类型+ member: 结构体成员+ ptr: 结构体成员member地址+ 指针变量__mptr:存储结构体成员MEMBER的地址-->ptr

我们知道,**宏的参数ptr代表的是一个结构体成员变量MEMBER的地址,**所以ptr的类型是一个指向MEMBER数据类型的指针,当我们使用临时指针变量__mptr来存储ptr的值时,必须确保__mptr的指针类型和ptr一样,是一个指向MEMBER类型的指针变量。

确保__mptr的指针类型和ptr一样

typeof(((type*)0)->member)表达式使用typeof关键字,用来获取结构体成员MEMBER的数据类型,然后使用该类型,通过typeof(((type*)0)->member)*__mptr这条程序语句,就可以定义一个指向该类型的指针变量了。

在语句表达式的最后,*因为返回的是结构体的首地址,所以整个地址还必须强制转换一下,转换为TYPE,即返回一个指向TYPE结构体类型的指针,*所以你会在最后一个表达式中看到一个强制类型转换(TYPE

这个文章也写的不错,讲的蛮清楚的。

在这里插入图片描述

一个parent代表结构体的类型,name代表结构体中的成员。

*((parent)0),**把数字0强制转换成parent 结构体指针类型。这样 ((parent )0) 这个整体就相当于一个指针指向了 0 这个地址,不管 0 这个地址是否合法,是否真的有这么一个结构体对象,它都会把以 0 地址为首的一片连续内存当成一个结构体对象操作。

((parent)0)->name,* *结构体指针((parent)0)取结构体对象中name成员。因为这只是对内存操作,**并没有写内存,虽然地址不合法也不会出现段错误。

*&((parent )0)->name对name成员取地址。

*offset = (uint32_t)&((parent )0)->name偏移量。 因为这个parent类型结构体对象是从0地址开始的,故而offset就是成员name的偏移量。

知道成员偏移量,就很容易求结构体对象本身的地址。成员的地址减去偏移量就是是结构体对象的首地址! – 本来的变量地址-便宜地址就是开始地方
((parent*)0)((uint32_t)node - (uint32_t)&((parent*)0)->name)结构体对象的首地址。

然后就可以开始访问变量了。->

到这里你应该懂了这个原理

[学习资料]:(https://www.freesion.com/article/57261214164/)
《嵌入式C语言自我修养:从芯片、编译器到操作系统》

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/264430.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

前后端分离Vue+node.js在线学习考试系统gqw7o

与其它应用程序相比,在线学习平台的设计主要面向于学校,旨在为管理员和学生、教师、院系提供一个在线学习平台。学生、教师、院系可以通过系统及时查看公告信息等。 在线学习平台是在Windows操作系统下的应用平台。为防止出现兼容性及稳定性问题&#xf…

使用 Verilog 做一个可编程数字延迟定时器 LS7211-7212

今天的项目是在 Verilog HDL 中实现可编程数字延迟定时器。完整呈现了延迟定时器的 Verilog 代码。 所实现的数字延迟定时器是 CMOS IC LS7212,用于生成可编程延迟。延迟定时器的规格可以在这里轻松找到。基本上,延迟定时器有 4 种操作模式:…

用c# 自己封装的Modbus工具类库源码

前言 Modbus通讯协议在工控行业的应用是很多的,并且也是上位机开发的基本技能之一。相关的类库也很多也很好用。以前只负责用,对其并没有深入学习和了解。前段时间有点空就在这块挖了挖。想做到知其然还要知其所以然。所以就有了自己封装的Modbus工具类库…

Leetcode3014. 输入单词需要的最少按键次数 I

题目&#xff1a; 代码(首刷看解析 2024年2月21日&#xff09;&#xff1a; class Solution { public:int minimumPushes(string word) {int n word.size();if (n < 8) return n;int mo n % 8;int x n / 8;int res 0;for (int i 1; i < x 1; i) {res i * 8;}res …

leet hot 100-6 三数之和

三数之和 原题链接思路代码 原题链接 leet hot 100-5 15. 三数之和 思路 从前往后定义第一个数字 first 开始遍历整个数组 然后要求 frist和上一个数字不重复否则就是重复组合 从frist往后遍历第二个数字 同样要求第二个数字不能重复 再定义第三个数字从后往前面数 三个数字…

iOS面试:4.多线程GCD

一、多线程基础知识 1.1 什么是进程&#xff1f; 进程是指在系统中正在运行的一个应用程序。对于电脑而已&#xff0c;你打开一个软件&#xff0c;就相当于开启了一个进程。对于手机而已&#xff0c;你打开了一个APP&#xff0c;就相当于开启了一个进程。 1.2 什么是线程&am…

http协议基础与Apache的简单介绍

一、相关介绍&#xff1a; 互联网&#xff1a;是网络的网络&#xff0c;是所有类型网络的母集因特网&#xff1a;世界上最大的互联网网络。即因特网概念从属于互联网概念。习惯上&#xff0c;大家把连接在因特网上的计算机都成为主机。万维网&#xff1a;WWW&#xff08;world…

二叉树和堆

二叉树和堆 一、树的概念和结构二、二叉树的概念三、堆四、堆的创建one、堆的插入(需要与向上或者向下调整算法配合(取决于你建大堆还是小堆)two、剔除堆中的根节点 五、堆的简单排序 一、树的概念和结构 树是一种非线性的的数据结构&#xff0c;逻辑结构就是一颗倒挂的树&…

Linux使用Docker部署Nacos容器并结合内网穿透实现公网访问本地服务

文章目录 推荐1. Docker 运行Nacos2. 本地访问Nacos3. Linux安装Cpolar4. 配置Nacos UI界面公网地址5. 远程访问 Nacos UI界面6. 固定Nacos UI界面公网地址7. 固定地址访问Plik 推荐 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff…

精美的WordPress外贸独立站模板

WordPress外贸独立站主题 简洁实用的WordPress外贸独立站主题&#xff0c;适合时尚服装行业搭建wordpress企业官网使用。 https://www.jianzhanpress.com/?p4999 简洁wordpress独立站模板 绿色精美、简洁大气的wordpress外贸独立网站模板 https://www.jianzhanpress.com/?…

本地配置多个git账户及ll设置

本地配置多个git账户 清除全局配置将命令行&#xff0c;切换到ssh目录生成GitLab和Gitee的公钥、私钥去对应的代码仓库添加 SSH Keys添加私钥ll设置 管理密钥验证仓库配置关于gitgitee.com: Permission denied (publickey) 清除全局配置 此步骤可以不做&#xff0c;经测试不影…

微信小程序本地开发

微信小程序本地开发时不需要在小程序后台配置服务器域名直接在小程序项目中填写后端在本机的IP地址和端口号 如图&#xff08;第一步&#xff09; 填写地址后发现报错&#xff0c;url不是合法域名&#xff0c;则在详情设置不校验合法域名 如图&#xff08;第二歩&#xff09;…

AI:134-基于深度学习的社交媒体图像内容分析

🚀点击这里跳转到本专栏,可查阅专栏顶置最新的指南宝典~ 🎉🎊🎉 你的技术旅程将在这里启航! 从基础到实践,深入学习。无论你是初学者还是经验丰富的老手,对于本专栏案例和项目实践都有参考学习意义。 ✨✨✨ 每一个案例都附带有在本地跑过的关键代码,详细讲解供…

猫头虎分享已解决Bug || AttributeError: ‘Sequential‘ object has no attribute ‘session‘

博主猫头虎的技术世界 &#x1f31f; 欢迎来到猫头虎的博客 — 探索技术的无限可能&#xff01; 专栏链接&#xff1a; &#x1f517; 精选专栏&#xff1a; 《面试题大全》 — 面试准备的宝典&#xff01;《IDEA开发秘籍》 — 提升你的IDEA技能&#xff01;《100天精通鸿蒙》 …

Remainder Problem(根号分治)

Educational Codeforces Round 71 (Rated for Div. 2) F. Remainder Problem 题目链接 F. Remainder Problem 题意&#xff1a; 给你一个由 500000 500000 500000 个整数&#xff08;编号从 1 1 1 到 500000 500000 500000 &#xff09;组成的数组 a a a 。最初 a a a…

SpringBoot -【SmartInitializingSingleton】基础使用及应用场景

SmartInitializingSingleton 在继续深入探讨 SmartInitializingSingleton接口之前&#xff0c;让我们先了解一下 Spring Framework 的基本概念和背景。Spring Framework 是一个开源的 JavaEE&#xff08;Java Enterprise Edition&#xff09;全栈&#xff08;full-stack&#x…

PureFlash v1.9.1特性介绍

PureFlashv1.9.1版本特性主要有3个&#xff1a; 1. 支持RDMA网络 使用RDMA协议可以大大减少对CPU的消耗&#xff0c;性能提升30%以上。 PureFlash的网络配置分为存储节点间网络&#xff08;存储后端网&#xff09;和客户端网络&#xff08;前端网&#xff09;。都支持使用RD…

Java的编程之旅19——使用idea对面相对象编程项目的创建

在介绍面向对象编程之前先说一下我们在idea中如何创建项目文件 使用快捷键CtrlshiftaltS新建一个模块&#xff0c;点击“”&#xff0c;再点New Module 点击Next 我这里给Module起名叫OOP,就是面向对象编程的英文缩写&#xff0c;再点击下面的Finish 点Apply或OK均可 右键src…

网络设备和网络软件

文章目录 网络设备和网络软件网卡交换机交换机的三个主要功能交换机的工作原理第二层交换和第三层交换交换机的堆叠和级联 路由器路由器工作原理 网关网关的分类 无线接入点(AP)调制解调器网络软件 网络设备和网络软件 网卡 网络接口卡又称网络适配器&#xff0c;简称网卡。网…

MySQL数据库基础(十五):PyMySQL使用介绍

文章目录 PyMySQL使用介绍 一、为什么要学习PyMySQL 二、安装PyMySQL模块 三、PyMySQL的使用 1、导入 pymysql 包 2、创建连接对象 3、获取游标对象 4、pymysql完成数据的查询操作 5、pymysql完成对数据的增删改 PyMySQL使用介绍 提前安装MySQL数据库&#xff08;可以…