我实际上是想container_of()
弄清楚Linux内核中的宏,其中涉及如下内容:
#ifndef offsetof
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
#endif
#ifndef container_of
/**
* 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)); })
我发现这个表情很(type *)0)->member
讨厌。我不明白为什么这个表达式在这些宏中起作用。我阅读了这篇文章,然后尝试提出一个程序来进一步理解它:
#include <stdio.h>
typedef struct {
int first;
int second;
int third;
}group;
int main(){
group a;
printf("Address of second is %p, group is %p\n", &a.second, &a);
size_t offset = &(((group*)0)->second);
printf("Offset of second is %zd\n", offset );
printf("Address of group is %p\n", (char*)&a.second - offset);
int val = ((group*)0)->second;
}
我确实知道表达式(group *)0)->member
不是void*
指针,而是group
类型的指针,但最终它最终不是NULL
被引用的地址吗?这条线工作正常
size_t offset = &(((group*)0)->second);
而这导致 SIGSEGV
int val = ((group*)0)->second;
两种情况下的内存访问有何不同?
不同之处在于,编译器已经在编译时“知道”了偏移量,并且不需要计算偏移量,因此不需要内存访问,也不会发生段错误。这就是为什么offsetof
就不能一起工作的不透明结构。当您检查相应的x86_64
汇编代码时,这一点特别明显。当我运行gcc -S
以下C代码时:
#include <stdio.h>
typedef struct {
int first;
int second;
int third;
}group;
int main(){
group a;
size_t offset = (size_t) &(((group*)0)->second); # notice the cast to avoid a warning
return 0;
}
基本上只有两个与我的C程序的内容相对应的指令:
movq $4, -24(%rbp) # move literal value 4 to *(rbp-24)
movl $0, %eax # move literal value 0 to eax (this is just a part of "return 0;" statement)
如果我现在要将C中的最后两行更改为:
size_t offset = (size_t) &(((group*)0)->third);
return 1;
汇编代码仅在这两个指令中有所不同。然后,他们将阅读:
movq $8, -24(%rbp)
movl $1, %eax
之所以有4和8,是因为在我的机器int
上等于4个字节。更重要的是,知道您的结构的成员是什么(这就是为什么不透明结构不起作用的原因-该信息被隐藏了。)由于编译器(或汇编器)从一开始就具有此信息,因此它可以并且它只是“硬编码”它。它不需要任何取消引用,因为它不需要这样做。
如果我现在将有问题的行添加到我的C代码中:
#include <stdio.h>
typedef struct {
int first;
int second;
int third;
}group;
int main(){
group a;
size_t offset = (size_t) &(((group*)0)->third);
int val = ((group*)0)->second;
return 0;
}
并组装它,我得到以下附加说明:
movl $0, %eax # move literal value 0 to eax
movl 4(%rax), %eax # dereference the value at *(rax + 4) and save it in eax
movl %eax, -28(%rbp) # move the value saved at eax to the *(rbp - 28)
第一行仅将字面值0存储在rax
寄存器的下半部分(上半部分始终为零)。当在该位置取消对内存的引用以rax + 4 = 4
尝试将获取的值存储到eax
寄存器时,将在下一条指令中触发段错误。实际上,在这里您可以再次看到编译器只是通过简单地将字面值4偏移结构(保存在中)的位置而知道struct group
成员的偏移量。恰好这不是有效的内存,因此,操作系统通过发送程序来终止您的程序。second
rax
SIGSEGV
如评论中所述,在第一个示例中,您没有取消引用任何内容,而只是计算地址。在第二种情况下,您实际上要取消引用指向0的指针,这将导致段错误。您自己链接的文章中都有这些内容:
现在,结构偏移已被“规范化”,我们甚至不必关心绿色成员的大小或结构的大小,因为很容易绝对偏移与相对偏移相同。这正是&(((TYPE *)0)-> MEMBER的作用。此代码将结构取消引用到内存的零偏移量。
通常这不是一件聪明的事情,但是在这种情况下,不会执行或评估该代码。就像我上面用卷尺显示的一样,这只是一个技巧。该offsetof()宏将只返回相对于零偏移的成员。这只是一个数字,您无法访问此内存。因此,执行此技巧唯一需要了解的就是结构的类型。
(我的重点。)
本文收集自互联网,转载请注明来源。
如有侵权,请联系 [email protected] 删除。
我来说两句