container_of(ptr, type, member)是内核中的经典函数之一。该函数的作用是:根据结构体中一个成员的地址,找到结构体的地址。这个函数是内核实现面向对象的基础设施,且最近在学习中经常见到这个函数,于是笔者在内核中查看了该函数的实现,故在此记录。本文原本是为了展示container_of的实现,但写着写着,发现有些内建函数与GNU C拓展的使用,所以就顺便查了资料,也一并记录于此,写得比较乱,请大家谅解。
结构体在内存中的分布,是按照成员的顺序分配内存,同时保持内存对齐的要求
该函数在5.17.5中的实现在include/linux/container_of.h中
5.16之前,这个宏都被放在
include/linux/kernel.h中
源码如下:
/**
* 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) ({ \
void *__mptr = (void *)(ptr); \
static_assert(__same_type(*(ptr), ((type *)0)->member) || \
__same_type(*(ptr), void), \
"pointer type mismatch in container_of()"); \
((type *)(__mptr - offsetof(type, member))); })
将传入的成员变量的地址,转换为void *类型,并赋给另一个值。这个操作笔者没有理解,所以找了以前版本的源码来进行分析,在2.6.23里,他的实现是这样的:
#define container_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})
这个版本中,第一行的作用其实相当于赋值+检查,考虑如果传进来的指针类型和member不一致,编译器会报warning。
在使用查看了相关的log之后,发现这个宏是在提交c7acec713d14c被改变的,改变的原因是:如果结构体内引入了一个非const数组成员,那么这个指针就会产生变量赋值给常量的问题,这会在gcc-4.9中产生一个warning: initialization from incompatible pointer type。这一笔改动抽离出了类型检查,但__mptr仍留在原处,笔者实在不清楚这个操作的深意,又或许只是历史遗留问题?(已更新,请查看尾部的“更新”章节)
static_assert(__same_type(*(ptr), ((type *)0)->member) || \
__same_type(*(ptr), void), \
"pointer type mismatch in container_of()"); \
这个地方在5.16后修改成static_assert,之前使用的是
BUILD_BUG_ON()这个宏,他和static_assert被定义在同一个文件里,感兴趣的朋友们可以去看一看相关实现,根据commit message显示,使用static_assert可以给出更加直接的错误提示,并且在理论上可以提升一点点的build速度(commit message里写了a tiny bit faster)
一个断言,用于检查ptr和member的类型一致性。这个断言函数static_assert()我们先放在一边,来分析一下这个断言的第一个参数:内部使用了__same_type()这个宏,来看看这个宏的实现:
/* Are two types/vars the same type (ignoring qualifiers)? */
#define __same_type(a, b) __builtin_types_compatible_p(typeof(a), typeof(b))
这个宏使用了两个函数:__builtin_types_compatible_p()和typeof()。
typeof()想必大家都比较熟悉了,它是一个GNU C的拓展,作用是获取变量的类型。文档地址:https://gcc.gnu.org/onlinedocs/gcc/Typeof.html
__builtin_types_compatible_p(type1, type2)是一个GNU C的内建函数,用于比较两个类型是否相等,若相等则返回1,不等则返回0。需要注意的是,这个函数的参数并不是表达式,而是变量类型,所以需要使用typeof()先取得变量类型后再传入。文档地址:https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html
最后我们再来看一看static_assert(),这个函数的实现位于include/linux/build_log.h,源码如下:
#define static_assert(expr, ...) __static_assert(expr, ##__VA_ARGS__, #expr)
#define __static_assert(expr, msg, ...) _Static_assert(expr, msg)
关于C宏定义中#符号的用法,可以总结为以下两点:
##,转换为合法标识符#define to_symbol(x) T_##x
// 下面这句等效于 int T_1 = 10;
int to_symbol(1) = 10;
#,转换为字符串#define to_string(x) #x
// 下面这句等效于 "a+b+c"
to_string(a+b+c);
那##__VA_ARGS__又是什么呢?它的功能有两个:
接着再来看一看_Static_assert(expr, msg, ...),这是一个C11特性,用来在编译时测试expr的正确性,如果正确则什么都不会发生,如果错误,则打印指定信息msg。文档地址:https://www.gnu.org/software/gnulib/manual/html_node/assert_002eh.html
综上所述,第二行的作用就是:判断传入的ptr和member(或者void)是否为同一类型,若否,则打印"pointer type mismatch in container_of()"
这一行真正用于获取结构体的地址。
((type *)(__mptr - offsetof(type, member)));
看上去很简单!就是用传进来的成员变量地址值减去它在结构体里的偏移值嘛!
逻辑上来讲确实很简单,但是如何实现呢?如何在不同的对齐下让这个函数均能成功运行呢?让我们带着这个疑问走进offsetof()这个宏:
// At include/linux/stddef.h
#define offsetof(TYPE, MEMBER) ((size_t)&((TYPE *)0)->MEMBER)
也很简单对吧?把0地址转换成结构体类型指针,然后利用这个特殊的结构体指针获取member,然后再对member取地址,得到的这个值就是member相对于0地址的偏移值,这个偏移值不就是member相对于结构体首地址的偏移值嘛!
看到这里,如果你和笔者一样是内核初学者,你可能会和笔者一样惊讶:0地址还能这么用?!!笔者也是在发出了这样的感叹之后,才决定记录下这篇随笔。
offsetof这个宏还有另一个实现,即调用GNU C的内建函数__builtin_offsetof,本质上和上面的定义是一致的。
这个宏包括了三步:赋值、检查、寻址。笔者分析了2.6.23中的赋值操作目的与最新的5.17.5中的检查和寻址操作。
在最后希望询问看到这篇文章的朋友们一个问题:为什么最新的版本还需要赋值给__mptr,能否在第三行中直接使用(void *)ptr代替__mptr?
关于文章结尾的问题,可以参阅这里的回答:https://stackoverflow.com/questions/72074089/what-is-the-purpose-of-mptr-in-latest-container-of-macro ,在这里感谢Chen Li的耐心解答。
大概的意思是,__mptr现在并不用于类型检查,而是为了消除scripts/gcc-plugins/randomize_layout_plugin.c中对于结构体强制转换的检查。
该检查会检查对结构体的起始地址和结构体指针的类型转换,假设这个宏被修改成这个样子:
#define container_of(ptr, type, member) ({ \
static_assert(__same_type(*(ptr), ((type *)0)->member) || \
__same_type(*(ptr), void), \
"pointer type mismatch in container_of()"); \
((type *)((void *)(ptr) - offsetof(type, member))); })
如果offsetof()返回0,并且member是一个结构体指针,那(type *)(void *)(ptr)就会将member的指针类型强制转换为type,这个强制转换会触发检查,并触发一个warning。使用一个中间变量可以避免这个问题,该问题被修复于补丁:https://www.mail-archive.com/linux-kernel@vger.kernel.org/msg1426986.html
原创文章,如有错漏,敬请补充指正,如对于文章风格有建议,请在评论区直接提出,感谢。
当我使用has_one时,它工作得很好,但在has_many上却不行。在这里您可以看到object_id不同,因为它运行了另一个SQL来再次获取它。ruby-1.9.2-p290:001>e=Employee.create(name:'rafael',active:false)ruby-1.9.2-p290:002>b=Badge.create(number:1,employee:e)ruby-1.9.2-p290:003>a=Address.create(street:"123MarketSt",city:"SanDiego",employee:e)ruby-1.9.2-p290
一、引擎主循环UE版本:4.27一、引擎主循环的位置:Launch.cpp:GuardedMain函数二、、GuardedMain函数执行逻辑:1、EnginePreInit:加载大多数模块int32ErrorLevel=EnginePreInit(CmdLine);PreInit模块加载顺序:模块加载过程:(1)注册模块中定义的UObject,同时为每个类构造一个类默认对象(CDO,记录类的默认状态,作为模板用于子类实例创建)(2)调用模块的StartUpModule方法2、FEngineLoop::Init()1、检查Engine的配置文件找出使用了哪一个GameEngine类(UGame
我正在尝试将以下SQL查询转换为ActiveRecord,它正在融化我的大脑。deletefromtablewhereid有什么想法吗?我想做的是限制表中的行数。所以,我想删除少于最近10个条目的所有内容。编辑:通过结合以下几个答案找到了解决方案。Temperature.where('id这给我留下了最新的10个条目。 最佳答案 从您的SQL来看,您似乎想要从表中删除前10条记录。我相信到目前为止的大多数答案都会如此。这里有两个额外的选择:基于MurifoX的版本:Table.where(:id=>Table.order(:id).
我遇到了一些Ruby代码,我试图理解为什么变量在initialize方法声明中的名称末尾有冒号。冒号有什么原因吗?attr_reader:var1,:var2definitialize(var1:,var2:)@var1=var1@var2=var2end 最佳答案 那些是关键字参数。您可以按名称而非位置使用它们。例如ThatClass.new(var1:42,var2:"foo")或ThatClass.new(var2:"foo",var1:42)Anarticleaboutkeywordargumentsbythoughtbot
我正在安装gitlabhq,并且在Gemfile中有对某些资源的“git://...”的引用。但是,我在公司防火墙后面,所以我必须使用http://。我可以手动编辑Gemfile,但我想知道是否有另一种方法告诉bundler使用http://作为git存储库? 最佳答案 您可以通过运行gitconfig--globalurl."https://".insteadOfgit://或通过将以下内容添加到~/.gitconfig:[url"https://"]insteadOf=git://
我创建了一个文件,这样我就可以在lib/foo/bar_woo.rb中的许多模型之间共享一个方法。在bar_woo.rb中,我定义了以下内容:moduleBarWoodefhelloputs"hello"endend然后在我的模型中我正在做类似的事情:defMyModel解释器提示它期望bar_woo.rb定义Foo::BarWoo。《使用Rails进行敏捷Web开发》一书指出,如果文件包含类或模块,并且文件使用类或模块名称的小写形式命名,那么Rails将自动加载文件。因此我不需要它。定义代码的正确方法是什么,在我的模型中调用代码的正确方法是什么? 最佳答案
我是Rails的新手,我遇到了一个错误,但我似乎找不到问题所在。这是日志:[32651:ERROR]2012-10-0913:46:52::comparisonofFloatwithFloatfailed[32651:ERROR]2012-10-0913:46:52::/home/sunny/backend/lib/analytics/lifetime.rb:45:in`each'/home/sunny/backend/lib/analytics/lifetime.rb:45:in`max'/home/sunny/backend/lib/analytics/lifetime.rb:45
@scores_raw.eachdo|score_raw|#belowiscodeiftimewasbeingsentinmillisecondshh=((score_raw.score.to_i)/100)/3600mm=(hh-hh.to_i)*60ss=(mm-mm.to_i)*60crumbs=[hh,mm,ss]sum=crumbs.first.to_i*3600+crumbs[1].to_i*60+crumbs.last.to_i@scoressum,:hms=>hh.round.to_s+":"+mm.round.to_s+":"+ss.round.to_s}@score
我一直在努力学习如何处理由数组组成的数组。假设我有这个数组:my_array=[['ORANGE',1],['APPLE',2],['PEACH',3]我将如何找到包含'apple'的my_array索引并删除该索引(删除子数组['APPLE',2]因为'apple'包含在该索引的数组中)?谢谢-我非常感谢这里的帮助。 最佳答案 您可以使用Array.select过滤掉项目:>>a=[['ORANGE',1],['APPLE',2],['PEACH',3]]=>[["ORANGE",1],["APPLE",2],["PEACH",3
目录0专栏介绍1平面2R机器人概述2运动学建模2.1正运动学模型2.2逆运动学模型2.3机器人运动学仿真3动力学建模3.1计算动能3.2势能计算与动力学方程3.3动力学仿真0专栏介绍?附C++/Python/Matlab全套代码?课程设计、毕业设计、创新竞赛必备!详细介绍全局规划(图搜索、采样法、智能算法等);局部规划(DWA、APF等);曲线优化(贝塞尔曲线、B样条曲线等)。?详情:图解自动驾驶中的运动规划(MotionPlanning),附几十种规划算法1平面2R机器人概述如图1所示为本文的研究本体——平面2R机器人。对参数进行如下定义:机器人广义坐标