簡體   English   中英

如果堆棧在數字較低的地址處增長,為什么指針比較會反轉這一點?

[英]If the stack grows at numerically lower address why does pointer comparison reverses this?

由於堆棧向下增長,即朝向數字較小的內存地址,為什么&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 S{10};            // heap allocated
    std::cout << '\n' << (&(pin->in) > &i) << '\n';         // true
    std::cout << ((void*)pin > (void*)pi) << '\n';          // true
}

到目前為止,我是否正確?如果是這樣,為什么 C 設計人員扭轉了這種情況,即數字較小的內存地址顯得更高(至少當您比較指針或通過 addressof 運算符& )。 這樣做只是為了“讓事情發揮作用”嗎?

如果我錯了,請糾正我,但我認為這是 C 創建者的設計決定

它不是 C 語言設計的一部分,也不是 C++。 事實上,這些標准並不認可“堆”或“堆棧”內存之類的東西。

這是一個實現細節。 每種語言的每個實現可能會以不同的方式執行此操作。


指向不相關對象(例如&i < &j(void*)pin > (void*)pi指針之間的有序比較具有未指定的結果。 兩者都不能保證小於或大於另一個。

值得一提的是,您的示例程序在我的系統上輸出了三個“假”計數。

編譯器生成的代碼不是按順序為每個單獨的變量分配空間,而是為這些局部變量分配一個,因此可以在該塊中隨意排列它們。

通常,在函數入口期間,一個函數的所有局部變量被分配為一個塊。 因此,如果將外部函數中分配的局部變量的地址與內部函數中分配的局部變量的地址進行比較,您只會看到堆棧向下增長。

這真的很容易:這樣的堆棧是一個實現細節。 C 和 C++ 語言規范甚至不需要引用它。 符合標准的 C 或 C++ 實現不需要使用堆棧! 如果它確實使用了堆棧,語言規范仍然不能保證其上的地址以任何特定模式分配。

最后,變量可以存儲在寄存器中,或者作為代碼文本中的立即數,而不是數據存儲器中。 然后:取這樣一個變量的地址是一個自我實現的預言:語言規范強制將值放到一個內存位置,然后提供給你的地址——這通常會破壞性能,所以不要取東西的地址你不需要知道地址。

一個簡單的跨平台示例(它在 gcc 和 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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM