当我浏览Linux内核时,我找到了一个container_of定义如下的宏:
#define container_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})
Run Code Online (Sandbox Code Playgroud)
我理解container_of做了什么,但我不明白的是最后一句,也就是说
(type *)( (char *)__mptr - offsetof(type,member) );})
Run Code Online (Sandbox Code Playgroud)
如果我们使用宏如下:
container_of(dev, struct wifi_device, dev);
Run Code Online (Sandbox Code Playgroud)
最后一句的相应部分是:
(struct wifi_device *)( (char *)__mptr - offset(struct wifi_device, dev);
Run Code Online (Sandbox Code Playgroud)
看起来什么都不做.有人可以在这里填补空白吗?
代码示例:
struct name
{
int a, b;
};
int main()
{
&(((struct name *)NULL)->b);
}
Run Code Online (Sandbox Code Playgroud)
这是否会导致未定义的行为?我们可以辩论它是否"取消引用无效",但是C11没有定义术语"解除引用".
6.5.3.2/4明确指出*在空指针上使用会导致未定义的行为; 但它并没有说同样的->,也没有定义a -> b为(*a).b; 它为每个运营商分别定义.
->6.5.2.3/4中的语义说:
后缀表达式后跟 - >运算符和标识符指定结构或联合对象的成员.该值是第一个表达式指向的对象的指定成员的值,并且是左值.
但是,NULL并没有指向一个对象,所以第二句似乎没有说明.
相关的可能是6.5.3.2/1:
约束:
一元运算
&符的操作数应该是函数指示符,[]一元或一元运算*符的结果 ,或者是一个左值,它指定一个不是位字段的对象,并且不用寄存器存储类说明符声明.
但是我觉得粗体文本是有缺陷的并且应该读取可能指定对象的左值,按照6.3.2.1/1(左值的定义) - C99弄乱了左值的定义,所以C11必须重写它,也许这个部分错过了.
6.3.2.1/1确实说:
左值是一个表达式(对象类型不是void)可能指定一个对象; 如果左值在评估时未指定对象,则行为未定义
但&操作员确实评估了它的操作数.(它不访问存储的值,但这是不同的).
这种长期推理似乎表明代码会导致UB,但它相当脆弱,我不清楚标准的作者是什么意图.如果事实上他们打算做任何事情,而不是让我们讨论:)
我正在阅读Linux内核上的Beautiful Code中的章节,作者讨论了Linux内核如何在C语言中实现继承(以及其他主题).简而言之,定义了一个"基础"结构,为了从中继承,"子类"结构将基类的副本放在子类结构定义的末尾.然后,作者花了几页来解释一个聪明而复杂的宏,以确定要从对象的基本部分转换为对象的子类部分需要多少字节.
我的问题:在子类struct中,为什么不将struct struct声明为struct中的第一个东西,而不是最后一个?
首先放置基础结构的主要优点是从基类转换到子类时根本不需要移动指针 - 实际上,执行转换只是告诉编译器让代码使用'额外'子类struct在基类定义的东西之后放置的字段.
只是为了澄清我的问题,让我抛出一些代码:
struct device { // this is the 'base class' struct
int a;
int b;
//etc
}
struct usb_device { // this is the 'subclass' struct
int usb_a;
int usb_b;
struct device dev; // This is what confuses me -
// why put this here, rather than before usb_a?
}
Run Code Online (Sandbox Code Playgroud)
如果碰巧有一个指向usb_device对象内部的"dev"字段的指针,那么为了将其强制转换回该usb_device对象,需要从该指针中减去8.但是如果"dev"是usb_device中的第一个东西,那么指针根本就不需要移动指针.
任何有关这方面的帮助将不胜感激.甚至关于在何处找到答案的建议也会受到赞赏 - 我不太确定谷歌如何为这样的决定背后的架构原因.我在StackOverflow上找到的最接近的是: 为什么要使用这些奇怪的嵌套结构
而且,要明确一点 - 我知道很多聪明的人已经在Linux内核上工作了很长时间,所以很明显这样做是有充分理由的,我只是无法弄清楚它是什么.
我正在使用宏,我认为它工作正常 -
#define CStrNullLastNL(str) {char* nl=strrchr(str,'\n'); if(nl){*nl=0;}}
因此它可以将字符串中的最后一个换行清零,实际上它用于在fgets保持不变时切断换行符.
所以,我想知道我是否可以从宏中"返回"一个值,因此它可以被称为
func( CStrNullLastNL( cstr ) ) ;
或者我必须写一个函数
此代码是否违反严格别名?
struct {int x;} a;
*(int*)&a = 3
Run Code Online (Sandbox Code Playgroud)
更抽象的是,只要原始读/写操作类型正确,在不同类型之间进行转换是否合法?