2015-02-11 93 views
0

我想看看你是否可以通過堆棧傳遞結構,並設法從另一個void函數中的void函數中獲取局部變量。通過堆棧傳遞數據

你們認爲這有什麼用處嗎?有沒有可能在兩次函數調用之間得到損壞的數據?

這裏的(我知道它的髒)

#include <stdio.h> 

typedef struct pouet 
{ 
    int a,b,c; 
    char d; 
    char * e; 
}Pouet; 

void test1() 
{ 
    Pouet p1; 
    p1.a = 1; 
    p1.b = 2; 
    p1.c = 3; 
    p1.d = 'a'; 
    p1.e = "1234567890"; 
    printf("Declared struct    : %d %d %d %c \'%s\'\n", p1.a, p1.b, p1.c, p1.d, p1.e); 
} 

void test2() 
{ 
    Pouet p2; 
    printf("Element of struct undeclared : %d %d %d %c \'%s\'\n", p2.a, p2.b, p2.c, p2.d, p2.e); 
    p2.a++; 
} 

int main() 
{ 
    test1(); 
    test2(); 
    test2(); 
    return 0; 
} 

輸出是代碼在C:

申報的結構:1 2 3 A '1234567890'

結構未聲明的元素: 1 2 3 a'1234567890'

結構的元素未聲明:2 2 3 a'1234567890'

+0

我忘了說:因爲在C語言中,當你在堆棧中聲明一個新變量時,它不會將值初始化爲0或NULL,所以取值就是堆棧中的值:在這個情況下p1的值。 – 2015-02-11 09:53:54

+1

有人會稱這是一個可怕的想法。它過於依賴於C標準中沒有出現的實現細節。 – 2015-02-11 09:58:34

+0

嘗試添加類似'char dummy [16];'Pouet p2;'上面的內容'並查看它是否仍按預期工作。 – 2015-02-11 10:30:39

回答

1

與大多數人的意見相反,我認爲它可以在大多數情況下工作(不是你應該依靠它)。

讓我們來看看。首先你打電話test1,它得到一個新的堆棧幀堆棧指針這表示堆棧的頂部上升。在那個棧框架上,除了其他的東西外,你的結構體的內存(正是sizeof(struct pouet)的大小)被保留並初始化。當test1返回時會發生什麼?它的堆棧框架和你的記憶是否會被破壞?

恰恰相反。它留在堆棧上。但是,堆棧指針會降到它下面,回到調用函數中。你看,這是一個非常簡單的操作,只是改變堆棧指針的值。我懷疑有沒有任何技術可以在處理堆棧時清除堆棧框架。要做的事情太費錢了!

然後會發生什麼?那麼,你叫test2。所有它在堆棧上存儲的只是struct pouet的另一個實例,這意味着它的堆棧幀將爲,最可能的是test1的尺寸完全相同。這也意味着test2將保留之前包含您初始化的struct pouet的內存中的變量Pouet p2,因爲兩個變量都應該是最可能是相對於堆棧幀的開始位置具有相同的位置。這又意味着它將被初始化爲相同的值。

但是,這種設置不是可以依賴的。即使擔心非標準化行爲,它也必然會被打破,如調用test1test2test1test2的不同大小的堆棧幀之間的不同功能。

此外,您應該考慮編譯器優化,這也可能會破壞事情。但是,您的功能越相似,他們將獲得不同的優化處理的可能性越小。

+1

當你解釋到底發生了什麼,我驗證你的答案 – 2015-02-11 11:29:03

1

當然,你可能會得到損壞的數據;您正在使用未定義的行爲

1

你有什麼是未定義的行爲。

printf("Element of struct undeclared : %d %d %d %c \'%s\'\n", p2.a, p2.b, p2.c, p2.d, p2.e); 

變量P2的範圍是局部的作用test2(),一旦退出該函數的變量不再有效。

您正在訪問未初始化的變量,這將導致未定義的行爲。

您看到的輸出在任何時候和所有平臺上都無法保證。所以你需要擺脫代碼中未定義的行爲。

+0

我想知道這個未定義的行爲是否可以使用,我不會在任何項目中使用它,這太危險了。 – 2015-02-11 10:54:30

+1

@Jiloko當行爲未定義時,如果你問'我試圖知道這個未定義的行爲是否可以使用?',我會說不會有任何事情發生。'我說不,不。NO。請不要依賴這個 – Gopi 2015-02-11 10:57:15

+0

@Jiloko:那裏就C標準而言,嚴肅與「不太嚴肅」的UB沒有區別。您的程序可能會崩潰或提供不可預知的輸出。從理論上講,它甚至可能會擦掉硬盤,儘管它不太可能發生。有關更多詳細信息,請參見[未定義的行爲](http://en.wikipedia.org/wiki/Undefined_behavior)。 – 2015-02-11 11:13:49

0

這些數據可能或不會在test2中出現。這取決於程序是如何編譯的。它更像是在你的玩具例子中比在真正的程序中工作,如果關閉編譯器優化,它更有可能工作。

語言定義說本地變量在函數結束時不再存在。嘗試讀取您認爲存儲的地址可能會或可能會產生結果;它甚至可能會使程序崩潰,或者使其執行一些完全意想不到的代碼。這是undefined behavior

例如,編譯器可能會決定將一個變量放在寄存器中的一個函數中,而不是另一個函數中,從而打破堆棧上變量的對齊。它甚至可以用一個大的結構來實現,將它分成幾個寄存器和一些堆棧 - 只要你不把結構的地址作爲一個可尋址的內存塊存在。編譯器可能會在其中一個變量的頂部寫入一個堆棧金絲雀。這些只是我頭頂的可能性。

C讓你看到很多幕後。很多你在幕後看到的內容可以完全從 製作 編譯或運行到下一個。

瞭解這裏發生的事情作爲一種調試技巧是很有用的,可以理解您在調試器中看到的值可能來自哪裏。作爲一種編程技術,這是沒有用的,因爲你並沒有讓計算機完成任何特定的結果。

0

僅僅因爲這適用於一個編譯器並不意味着它適用於所有。如何處理未初始化的變量是未定義的,一臺計算機可以很好地初始化指向null等的指針,而不會違反任何規則。 所以不要這樣做或依靠它。我實際上看到了依賴於mysql中的功能的代碼,這是一個錯誤。當它在更高版本中被修復時,程序停止工作。我對這個系統的設計者的想法我會繼續保持。

總之,永遠不要依賴沒有定義的功能。如果您故意將其用於特定功能,並且您已準備好對編譯器等進行更新可能會破壞它,並且始終注意這一點,則可能是您可以解釋和解決的問題。但大多數情況下,這遠非一個好主意。