指针取消引用的不同行为

佐佐

我实际上是想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成员的偏移量。恰好这不是有效的内存,因此,操作系统通过发送程序来终止您的程序secondraxSIGSEGV


如评论中所述,在第一个示例中,您没有取消引用任何内容,而只是计算地址。在第二种情况下,您实际上要取消引用指向0的指针,这将导致段错误。您自己链接的文章中都有这些内容

现在,结构偏移已被“规范化”,我们甚至不必关心绿色成员的大小或结构的大小,因为很容易绝对偏移与相对偏移相同。这正是&(((TYPE *)0)-> MEMBER的作用。此代码将结构取消引用到内存的零偏移量。

通常这不是一件聪明的事情,但是在这种情况下,不会执行或评估该代码。就像我上面用卷尺显示的一样,这只是一个技巧。offsetof()宏将只返回相对于零偏移的成员。这只是一个数字,您无法访问此内存因此,执行此技巧唯一需要了解的就是结构的类型。

(我的重点。)

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章