首页 > 解决方案 > 指针算术通过先前的成员地址(在同一个结构中)导致指向另一个结构成员的指针

问题描述

C标准对指针算术结果的看法是什么?通过同一结构中的前一个成员地址指向另一个结构成员?


代码 1(无结构),mystery_1

int mystery_1(void)
{
    int one = 1, two = 2;
    int *p1 = &one + 1;
    int *p2 = &two;
    unsigned long i1 = (unsigned long) p1;
    unsigned long i2 = (unsigned long) p2;

    if (i1 == i2)
        return p1 == p2;
    return 2;
}

从代码1,我知道结果是不确定的,因为无法保证堆栈上的局部变量是如何放置的。

如果我使用这样的结构(代码 2)怎么办?


代码 2(带结构),mystery_2

int mystery_2(void)
{
    struct { int one, two; } my_var = {
        .one = 1, .two = 2
    };
    int *p1 = &my_var.one + 1;
    int *p2 = &my_var.two;
    unsigned long i1 = (unsigned long) p1;
    unsigned long i2 = (unsigned long) p2;
    
    if (i1 == i2)
        return p1 == p2;
    return 2;
}

编译器输出

神螺栓链接: https ://godbolt.org/z/jGoKfETn7

海合会 10.2

mystery_1:
        xorl    %eax, %eax # return 0, while clang returns 2 (fine as no guarantee)
        ret
mystery_2:
        movl    $1, %eax # return 1, as compiler must consider the memory order of struct members
        ret

铿锵声 11.0.1

mystery_1:                              # @mystery_1
        movl    $2, %eax # return 2, while gcc returns 0 (fine as no guarantee)
        retq
mystery_2:                              # @mystery_2
        movl    $1, %eax # return 1, as compiler must consider the memory order of struct members
        retq

我的理解

问题

问题

我与某人讨论了 struct case ( mystery_2),他们说:

p1指向(过去)一,并p2指向二。在 C 规范中,这些被视为不同的“对象”。然后规范继续定义指向不同对象的指针可能比较不同,即使两个指针具有完全相同的位模式

标签: cpointerslanguage-lawyer

解决方案


根据 C 2018 6.5.6 8,指针算术的两个基础是:

  • 可以调整指向数组元素的指针(通过整数的加减法)以指向数组的任何元素或指向末尾(最后一个元素之后的一个)。C 标准未定义的算术以外的算术。
  • 对于指针算术,单个对象的行为就像一个对象的数组。

因此int *p1 = &one + 1;具有定义的行为。

关于:

    unsigned long i1 = (unsigned long) p1;
    unsigned long i2 = (unsigned long) p2;

由于它不是这个问题的重点,我们假设实现定义的指针到 an 的转换unsigned long会产生一个唯一值,该值唯一地标识指针值。(也就是说,将任何地址转换为unsigned long仅会为该地址生成一个值,并且将该值转换回指针会重现该地址。C 标准不保证这一点。)

那么,如果i1 == i2,则意味着p1 == p2,反之亦然。根据 C 2018 6.5.9 6,p1并且p2只有当twop2指向)已在内存中布局超过onep1指向刚刚超出)时,才能比较相等。(一般来说,由于其他原因,指针可以比较相等,但这些情况涉及指向同一个对象、结构及其第一个成员、同一个函数等的指针,所有这些都被排除在这个特殊的p1and之外p2。)

因此,如果代码 1 中的代码two紧随其后在内存中布局,则返回 1,否则返回one2。

在代码 2 中也是如此。&my_var.one + 1定义了指针算术,并且结果p1比较等于p2当且仅当成员two紧跟one在内存中的成员之后。

不过,two也不必马上跟进one。这个说法是不正确的:

... struct 保证内存布局。

C 标准允许实现在结构成员之间放置填充。常见的 C 实现不会这样做,struct { int one, two; }因为它不需要对齐(一旦one对齐,紧随其后的地址也适合对齐int,因此不需要填充),但 C 标准不保证这一点。

笔记

uintptr_t,在 中声明<stdint.h>,是将指针转换为整数的更好选择。但是,标准只保证(uintptr_t) px == (uintptr_t) py暗示px == py,不保证px == py暗示(uintptr_t) px == (uintptr_t) py。换句话说,将两个指向同一个对象的指针转换为uintptr_t可能会产生两个不同的值,尽管将它们转换回指针会导致指针比较相等。


推荐阅读