簡體   English   中英

無法理解關於linux中函數調用的簡單c代碼的輸出

[英]can't understand the output of the simple c code about function call in linux

當我嘗試理解函數調用時,我編寫了一個簡單的代碼。 但我無法理解它的輸出。

#include <stdio.h>

int* foo(int n)
{
    int *p = &n;
    return p;
}

int f(int m)
{
    int n = 1;
    return 999;
}

int main(int argc, char *argv[])
{
    int num = 1;
    int *p = foo(num);
    int q = f(999);
    printf("[%d]\n[%d]\n", *p, q);
    /* printf("[%d]\n", *q); */
}

輸出:

[999]
[999]

為什么*p是999?

然后我修改了我的代碼,如下所示:

#include <stdio.h>

int* foo(int n)
{
    int *p = &n;
    return p;
}

int f()
{
    int n = 1;
    return 999;
}

int main(int argc, char *argv[])
{
    int num = 1;
    int *p = foo(num);
    int q = f();
    printf("[%d]\n[%d]\n", *p, q);
    /* printf("[%d]\n", *q); */
}

輸出:

[1]
[999]

為什么*p在這里是1? 我在Linux,使用gcc但是Clang得到了相同的輸出。

除了你的代碼提出了未定義的行為這一事實,因為你正在返回一個指向堆棧變量的指針,你要求通過改變f()的簽名來改變行為的原因。

之所以

原因在於編譯器為函數構建堆棧幀的方式。 假設編譯器正在為foo()構建堆棧幀,如下所示:

Address Contents  
0x199   local variable p
0x200   Saved register A that gets overwritten in this function
0x201   parameter n
0x202   return value
0x203   return address

對於f(int m),堆棧看起來很安靜:

Address Contents  
0x199   local variable n
0x200   Saved register A that gets overwritten in this function
0x201   parameter m
0x202   return value
0x203   return address

現在,如果你在foo中返回指向'n'的指針會發生什么? 結果指針將為0x201。 返回foo后,堆棧頂部位於0x204。 內存保持不變,您仍然可以讀取值“1”。 這有效直到調用另一個函數(在你的情況下為'f')。 調用f后,位置0x201將被參數m的值覆蓋。

如果您訪問此位置(並使用printf語句),則會顯示“999”。 如果在調用f()之前復制了此位置的值,則會找到值“1”。

堅持我們的例子,f()的堆棧框架看起來像這樣,因為沒有指定參數:

Address Contents  
0x200   local variable n
0x201   Saved register A that gets overwritten in this function
0x202   return value
0x203   return address

在使用“1”初始化局部變量時,可以在調用f()后在位置0x200處讀取“1”。 如果您現在從位置0x201讀取值,您將獲得已保存寄存器的內容。

進一步的陳述

  • 至關重要的是要理解上述解釋是為了向您展示為什么觀察您觀察到的內容的方法。
  • 真正的行為取決於您使用的工具鏈和所謂的呼叫對話。
  • 人們很容易想象,有時很難預測會發生什么。 釋放后訪問內存是一種安靜的類似情況。 這就是為什么它總是無法預測會發生什么。
  • 甚至可以通過更改優化級別來更改此行為。 例如,我可以想象如果你打開-O3例如,觀察將是不同的,因為未使用的變量n將不再出現在二進制中。
  • 理解了背后的機制之后,為什么對從foo檢索到的地址的寫訪問可能導致嚴重問題應該是可以理解的。

勇敢的試圖通過實驗來證明這種解釋

首先,重要的是要看到上面的解釋不依賴於真正的堆棧框架布局。 我剛剛介紹了布局,以便使插圖易於理解。

如果你想在你自己的機器上測試行為,我建議你帶上你最喜歡的調試器,看一下局部變量和參數的地址,看看到底發生了什么。 請記住:更改f的簽名會更改堆棧中的信息。 所以唯一真正的“便攜式”測試是改變f()的參數並觀察值p指向的輸出。

在調用f(void)的情況下,放在堆棧上的信息大量不同,並且在位置p處寫入的值指向不再必須依賴於參數或本地。 它還可以依賴於main函數的堆棧變量。

例如,在我的機器上,再現顯示您在第二個變體中讀取的'1'來自保存用於存儲'1'到“num”的寄存器,因為它似乎用於加載n。

我希望這會給你一些見解。 如果您有其他問題,請發表評論。 (我知道這有點奇怪)

您正在調用未定義的行為。 您不能返回局部變量的地址(在本例中為參數int n ),並期望它稍后有用。

一個局部變量,如代碼中的n

int* foo(int n)
{
    int *p = &n;
    return p;
}

foo函數完成后“消失”。

您無法使用它,因為訪問該變量可能會給您帶來不可預測的結果。 不過你可以寫這樣的東西:

int* foo(int* n)
{
    *n = 999;
    return p;
}

int main(int argc, char *argv[])
{
    int num = 1;
    int *p = foo(&num);
    printf("[%d]\n", *p);
}

因為您的變量num仍然存在於打印點。

在你的第一個樣本中,當你這樣做

int num = 1;
int *p = foo(num);

其中foo()

int* foo(int n)
{
    int *p = &n;
    return p;
}

當傳遞來自main()變量num ,它將通過值傳遞給foo 換句話說,在堆棧上創建變量num的副本,稱為n numn都具有相同的值,但它們是不同的變量,因此具有不同的地址。

當你從foo()返回pmain()獲取的地址值與main()num delared地址不同

相同的解釋適用於您修改的程序。

讓我們看另一個例子來澄清:

int i = 2;

int * foo()
{
return &i;
}

int main() {

i = 1;
int *p = foo();
return 0;

}

在這種情況下, i在堆上聲明,並且在main()foo()引用相同的i 相同的地址和相同的值。

我們來看第三個例子:

int i = 2;

int * foo(int i)
{
return &i;
}

int main() {

int i = 1;
int *p = foo(i);
return 0;

}

在這里,即使有一個全局i ,它也被main()的局部變量i隱藏,這就是傳遞給foo() 所以, &ifoo返回,即main()p的值將與main()聲明的變量i的地址不同。

希望這澄清了變量范圍和值傳遞,

沒有匯編程序輸出並不容易,但這是我的猜測:

本地和參數在堆棧中被放置。 因此,當調用foo ,它將返回堆棧上第一個參數的地址。

在第一個示例中,您將一個參數傳遞給第二個函數,該函數也將被推送到堆棧中,正好在p指向的位置。 因此它會覆蓋*p的值。

在第二個示例中,在第二個調用中未觸及堆棧。 舊值( num )仍然存在。

這種未定義的行為是由於堆棧的參與

int *p = foo(num);
int q = f(999);

在第一種情況下,當你說&num ,它實際上將地址存儲在存儲num的堆棧中。 然后foo(num)完成它的執行,f(999)開始執行參數999.由於使用相同的堆棧,堆棧中存儲num的相同位置現在具有參數999.並且我們知道堆棧是連續的。

這就是打印999的原因。 實際上兩者都試圖打印堆棧中相同位置的內容。

而在第二種情況下,num不會被覆蓋,因為沒有參數傳遞給f()所以,這會按預期打印。

暫無
暫無

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

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