[英]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讀取值,您將獲得已保存寄存器的內容。
進一步的陳述
勇敢的試圖通過實驗來證明這種解釋
首先,重要的是要看到上面的解釋不依賴於真正的堆棧框架布局。 我剛剛介紹了布局,以便使插圖易於理解。
如果你想在你自己的機器上測試行為,我建議你帶上你最喜歡的調試器,看一下局部變量和參數的地址,看看到底發生了什么。 請記住:更改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
。 num
和n
都具有相同的值,但它們是不同的變量,因此具有不同的地址。
當你從foo()
返回p
, main()
獲取的地址值與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()
。 所以, &i
從foo
返回,即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.