container_of宏定义作用_宏内核

container_of宏定义作用_宏内核内核链表是怎么通过指针域来访问数据域的呢?

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE使用 1年只要46元 售后保障 童叟无欺

回顾

上一篇我们讲到内核链表普通链表的区别,就有小伙伴追问:内核链表是怎么通过指针域来访问数据域的呢?这篇文章我们就来解答这个问题。

问题具象化

上述问题可以具体描述为:
① 有一个结构体变量 led

struct led_dev { 
   
	char *name;
	int brightness;
	struct list_head link;
	int flags;
};

struct led_dev led;

② 变量 led 不在当前代码的作用域内,无法直接操作其成员(可以理解为 led 变量在别的 .c 文件中,当前 .c 无法拿到这个变量直接使用)。

③ 已知 led.link 的地址,求 led 的地址是多少?

上工具

这时候,就用到了 linux 内核中提供的两个宏了

#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) );})

简单介绍下:
offsetof 宏用来计算某个成员变量在结构体中的偏移量。
container_of 宏用来在给定一个变量的结构体类型,和这个变量的某个成员的地址的条件下,计算出这个变量的地址。

offsetof 实例分析

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>

#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)

struct list_head { 
   
	struct list_head *next, *prev;
};

struct led_dev { 
   
	char *name;
	int brightness;
	struct list_head link;
	int flags;
};

int main()
{ 
   
	size_t off_set = 0;
	off_set = offsetof(struct led_dev, name);
	printf("off_set of name = %ld\n", off_set);
	off_set = offsetof(struct led_dev, brightness);
	printf("off_set of brightness = %ld\n", off_set);
	off_set = offsetof(struct led_dev, link);
	printf("off_set of link = %ld\n", off_set);
	off_set = offsetof(struct led_dev, flags);
	printf("off_set of flags = %ld\n", off_set);

	return 0;
}

运行输出

off_set of name = 0
off_set of brightness = 8
off_set of link = 16
off_set of flags = 32
  • name 是结构体的第一个元素,所以它的偏移量是 0
  • brightness 的偏移量是前面元素的大小,即一个指针变量的大小,我使用的是 64 位的机器,所以一个指针大小为 8 字节,所以 brightness 的偏移量为 8
  • 同理可求得 link 的偏移量为 16,flags 的偏移量为 32

container_of 实例解析

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
/** * container_of - cast a member of a structure out to the containing structure * @ptr: the pointer to the member. * @type: the type of the container struct this is embedded in. * @member: the name of the member within the struct. * */
#define container_of(ptr, type, member) ({ 
 \ const typeof( ((type *)0)->member ) *__mptr = (ptr); \ (type *)( (char *)__mptr - offsetof(type,member) );})
struct list_head { 

struct list_head *next, *prev;
};
struct led_dev { 

char *name;
int brightness;
struct list_head link;
int flags;
};
struct led_dev led = { 

"green",
1,
{ 
NULL, NULL},
0xFF,
};
int main()
{ 

printf("led address : %p\n", &led);  // 打印 led 的地址,这里仅仅打印,用来和后面计算的结果进行正确性对比,因为场景是 led 地址我们是不知道的。
printf("led.link address : %p\n", &led.link);  // 假设通过链表拿到了下一个节点的指针域的地址:&led.link (隐含信息 &led.link = &led.link.next)
// 下面就想办法通过这个地址来推出节点的首地址,进而也就知道了所有成员的地址
struct led_dev *ptr = container_of(&(led.link), struct led_dev, link);
printf("ptr address : %p\n", ptr);	// 检查 ptr 的地址是否和 led 的地址相同,想同的话,就表示我们成功拿到了 led 的地址。后面就能使用 ptr 来访问结构体中的其它成员变量了
printf("ptr->name = %s\n", ptr->name);
printf("ptr->brightness = %d\n", ptr->brightness);
printf("ptr->flags = 0x%x\n", ptr->flags);
return 0;
}

运行结果

led address : 0x55745d380020
led.link address : 0x55745d380030
ptr address : 0x55745d380020
ptr->name = green
ptr->brightness = 1
ptr->flags = 0xff
  • 我们定义了一个 led 变量,不过这里我们假设 led 变量是在别处定义的,我们拿不到。main 函数第一句仅仅是打印其地址,用作和后面求得的 led 变量地址做正确性验证。
  • 我们已知 led 的一个成员变量的地址,即 led.link 的地址
  • 我们的目的是通过 led.link 的地址求 led 的地址

struct led_dev *ptr = container_of(&(led.link), struct led_dev, link);

可以看到,上述这句就是得到 led 地址的语句,入参是:led.link 的地址,led 的结构体类型,成员
返回结果:led 变量的地址。

从运行结果也可以看到,我们已知 led.link 的地址为 0x55745d380030,求得 led 的地址为 0x55745d380020,和代码一开始打印的 led 的地址相同,故结果正确。

offsetof 原理

#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)

对于这个宏,我们逐层去理解

1. 			 		  0
2. 	 		  (TYPE *)0
3. 			 ((TYPE *)0)->MEMBER
4. 			&((TYPE *)0)->MEMBER
5. (size_t) &((TYPE *)0)->MEMBER

1、内存地址开始于 0
2、将 0 转换成 TYPE 类型的结构体指针,换句话说就是让编译器认为这个结构体开始于程序段的起始位置
3、引用结构体中的 MEMBER 成员
4、取地址
5、将取到的地址强制转换为 size_t 类型

因为这个结构体的起始地址被指定为 0,所以取到的结构体成员的绝对地址(当转换为数字)就是这个成员在结构体中的偏移量。

这个代码之所以没有风险,是因为这里没有对任何内存进行写操作,甚至没有读操作。只是操作了指向这些位置的指针,而指针一般存储在机器寄存器或是通常的本地堆栈。

container_of 原理

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

同样进行逐层分析

1. 				    0
2. 		    (type *)0
3. 		   ((type *)0)->member
4. typeof( ((type *)0)->member )
5. typeof( ((type *)0)->member ) *__mptr
6. typeof( ((type *)0)->member ) *__mptr = (ptr);
7. (type *)( (char *)__mptr - offsetof(type,member) );

1、2、3、 同 offsetof
4、typeof 获取变量类型
5、使用获取到的类型定义一个临时指针变量 __mptr
6、将传入的成员变量地址赋值给 __mptr
7、用 __mptr 减去成员在结构体中的偏移量,就得到了结构体变量的地址

参考

stackoverflow

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/172082.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • Wireshark抓包详解[通俗易懂]

    Wireshark抓包详解[通俗易懂]简述wireshark是非常流行的网络封包分析工具,功能十分强大。可以截取各种网络封包,显示网络封包的详细信息。使用wireshark的人必须了解网络协议,否则就看不懂wireshark了。为了安全考虑,wireshark只能查看封包,而不能修改封包的内容,或者发送封包。 wireshark能获取HTTP,也能获取HTTPS,但是不能解密HTTPS,所以wireshar

  • 小程序页面跳转、带参数跳转以及navigator跳转[通俗易懂]

    一、单纯的页面跳转跳转到的页面分tabBar页面和非tabBar页面。url路径可以写相对和绝对路径。1、跳转到非导航页面,用wx.navigateTo方法wx.navigateTo({url:’../person/goldcoin/index’//或者url:’/page/person/goldcoin/index’})2、跳转到tabB…

  • 【javascript】使用happypack和thread-loader加速构建「建议收藏」

    【javascript】使用happypack和thread-loader加速构建「建议收藏」使用happypack/thread-loader加速构建标签:webpack为什么需要happypack/threadloader webpack需要处理的文件是非常多的,构建过程是一个涉及大量文件读写的过程。项目复杂起来了,文件数量变多之后,webpack构建就会特别满,而且运行在nodeJS上的webpack是单线程模型的,也就是说Webpack一个时刻只能处理一个任务,不能同时…

  • Linux系统中修改文件内容「建议收藏」

    Linux系统中修改文件内容「建议收藏」1、进入文件:vim文件名vimcommon.js2、查找待修改内容位置:(1)按住shift输入“:”,使文件变成可查询状态(2)输入“/”+要修改的内容,回车(例如:要修改服务器地址和端口号)3、修改内容定位到要修改的位置后按i键变成可编辑状态,对要修改的内容进行修改4、退出按ESC键,退出修改状态5、保存(不保存)修改:保存修改:(1)按住shift输入“:”,使文件变成可查询状态(2)输入…

  • html文件怎么转换成word文件_word转换成网页文件格式不对

    html文件怎么转换成word文件_word转换成网页文件格式不对1回答2021-05-06浏览:0分类:办公入门回答:点击菜单,选择另存为在弹出的窗口选择文档类型为:网页类型(htm*HTML)取好名字和路径,确认保存2回答2020-11-28浏览:5分类:其他问题回答:1、打开HTML文件,点击菜单栏文件→使用MicrosoftOfficeWord编辑,之后系统会自动打开Word并显示HTML文件的内容,这是保存即可。2、如果…

    2022年10月10日
  • Java代码是怎么运行的「建议收藏」

    Java代码是怎么运行的「建议收藏」Java代码有很多运行方式。在开发工具中运行双击jar文件运行在命令行中运行在网页中运行当然,上述运行方式都离不开JRE,&nbsp;也就是Java运行时环境。JRE仅包含Java程序的必须组件,包括Java虚拟机以及Java核心类库…

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号