如果堆栈在数字较低的地址处增长,为啥指针比较会反转呢?

Posted

技术标签:

【中文标题】如果堆栈在数字较低的地址处增长,为啥指针比较会反转呢?【英文标题】:If the stack grows at numerically lower address why does pointer comparison reverses this?如果堆栈在数字较低的地址处增长,为什么指针比较会反转呢? 【发布时间】:2020-01-06 11:10:03 【问题描述】:

由于堆栈向下增长,即朝着数字较小的内存地址增长,为什么&i < &j 为真。如果我错了,请纠正我,但我想这是 C 创建者的设计决定(C++ 维护)。但我想知道为什么。

同样奇怪的是,堆分配的对象pin 位于比堆栈变量更高的内存地址,这也与堆位于比堆栈更小的内存地址(并且向上增加)这一事实相矛盾。

#include <iostream>

int main()

    int i = 5;                  // stack allocated
    int j = 2;                  // stack allocated
    int *pi = &i;               // stack allocated
    int *pj = &j;               // stack allocated

    std::cout << std::boolalpha << '\n';
    std::cout << (&i < &j) && (pi < pj) << '\n';            // true
    struct S
    
        int in;
    ;
    S *pin                      // stack allocated
        = new S10;            // heap allocated
    std::cout << '\n' << (&(pin->in) > &i) << '\n';         // true
    std::cout << ((void*)pin > (void*)pi) << '\n';          // true

到目前为止我是对的吗?如果是这样,为什么 C 设计人员会扭转这种情况,即数字较小的内存地址看起来更高(至少当您比较指针或通过 addressof 运算符 &amp; 时)。这样做只是为了“让事情顺利进行”吗?

【问题讨论】:

比较不属于同一数组的元素的地址是未指定的行为,除了(不)相等。结果取决于您的平台/实现。 尝试比较两个不同堆栈帧中变量的地址,例如将pj 传递给函数并将其与该函数中局部变量的地址进行比较 强制链接问答***.com/questions/31774683/… @FrançoisAndrieux 它真的没有定义,还是没有意义?我认为您可以比较指针就好了,但是由于变量在内存中的放置完全是编译器的工作,您的程序不应该关心大多数比较的结果是什么。 @Caleb 该标准明确表示未指定。每个实现都定义了自己的规则,但他们需要定义一些规则。你一定在编辑之间发现了我的评论。 【参考方案1】:

如果我错了,请纠正我,但我想这是 C 创造者的设计决定

它不是 C 语言设计的一部分,也不是 C++。事实上,这些标准并不认可“堆”或“栈”内存。

这是一个实现细节。每种语言的每种实现都可能以不同的方式执行此操作。


指向不相关对象(例如&amp;i &lt; &amp;j(void*)pin &gt; (void*)pi)的指针之间的有序比较具有未指定的结果。两者都不能保证小于或大于另一个。

不管怎样,你的示例程序在我的系统上输出三个“假”计数。

【讨论】:

那是什么系统? @Nikos Linux stack-crooked 4.4.0-57-generic #78-Ubuntu SMP Fri Dec 9 23:50:32 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux 与编译器 g++ (GCC) 9.2。 0 好的。我刚刚检查了一个带有 g++ 7.3.0 的 Kali 4.15 x64 系统,它输出truefalsefalse【参考方案2】:

编译器生成的代码不是按顺序为每个单独的变量分配空间,而是为这些局部变量分配一个,因此可以在该块中随意安排它们。 p>

【讨论】:

【参考方案3】:

通常,在函数进入期间,一个函数的所有局部变量都被分配为一个块。因此,只有将在外部函数中分配的局部变量的地址与在内部函数中分配的局部变量的地址进行比较,才能看到堆栈向下增长。

【讨论】:

【参考方案4】:

这真的很简单:这样的堆栈是一个实现细节。 C 和 C++ 语言规范甚至不需要引用它。符合标准的 C 或 C++ 实现不需要使用堆栈!如果它确实使用堆栈,语言规范仍然不能保证其上的地址是以任何特定模式分配的。

最后,变量可以存储在寄存器中,或者作为代码文本中的立即值,而不是存储在数据存储器中。然后:获取这样一个变量的地址是一个自我实现的预言:语言规范将值强制到一个内存位置,并提供给您的地址 - 这通常会破坏性能,所以不要获取事物的地址你不需要知道地址。

A simple cross-platform example (it does the right thing on both gcc and msvc).

#ifdef _WIN32
#define __attribute__(a)
#else
#define __stdcall
#endif

#ifdef __cplusplus
extern "C" 
#endif
__attribute__((stdcall)) void __stdcall other(int);

void test()
    int x = 7; 
    other(x);
    int z = 8;
    other(z);


#ifdef __cplusplus

#endif

任何合理的编译器都不会不必要地将xz 放入内存中。它们要么存储在寄存器中,要么作为立即值压入堆栈。

这是 gcc 9.2 的 x86-64 输出 - 请注意,不存在内存加载或存储,并且有尾调用优化!

gcc -m64 -Os

test:
        push    rax
        mov     edi, 7
        call    other
        mov     edi, 8
        pop     rdx
        jmp     other

在 x86 上,我们可以强制使用使用堆栈传递所有参数的 stdcall 调用约定:即使这样,值 78 也永远不会在变量的堆栈位置中。调用other时直接压栈,事先不存在栈中:

gcc -m32 -fomit-frame-pointer -Os

test:
        sub     esp, 24
        push    7
        call    other
        push    8
        call    other
        add     esp, 24
        ret

【讨论】:

以上是关于如果堆栈在数字较低的地址处增长,为啥指针比较会反转呢?的主要内容,如果未能解决你的问题,请参考以下文章

为啥堆栈通常会向下增长?

真正的内存结构是啥? [复制]

为啥在函数中用作局部变量时数组不会沿堆栈方向增长?

编译器如何在堆栈上安排局部变量?

linux是不是在较低的堆栈端下方提供了保证的不可访问的内存区域?

关闭堆栈中较低的 ViewController 的行为不符合预期