簡體   English   中英

試圖理解C指針,塊和轉到語句的問題

[英]trying to understand an issue with C pointers, blocks and go to statements

我正在努力處理C和Cyclone,因為我通過Jim Trevor的“ Cyclone:C語言的安全方言 ”來完成PL課程。 Trevor給出了一個不安全的首要聲明的例子:

int z;
{ int x = 0xBAD; goto L; }
{ int *y = &z;
L: *y = 3; // Possible segfault
}

Trevor在上面的代碼中解釋了安全問題如下:

許多編譯器堆棧在輸入時分配塊的局部變量,並在塊退出時釋放(彈出)存儲(盡管這不是C標准規定的)。 如果以這種方式編譯示例,則當程序進入第一個塊時,x的空間將在堆棧上分配,並使用值0xBAD進行初始化。 goto跳轉到第二個塊的中間,直接指向指針y的內容。 由於y是第二個塊中聲明的第一個(唯一)變量,因此賦值期望y位於堆棧的頂部。 不幸的是,這正是x分配的位置,因此程序嘗試寫入位置0xBAD,可能會觸發分段錯誤。

我不明白為什么go to聲明是一個問題。 似乎問題是未初始化的指針Z的不可預測的行為。在第二個塊的開始, int * y用Z的地址填充.Z未初始化,因此它將填充int* y並且位模式為on由Z引用的內存區域中的堆棧。我不明白為什么Trevor的論文暗示Z和X都以某種方式引用0xBAD。 C不會為第一個塊創建一個新的堆棧幀(正如Trevor所描述的那樣):因此將0xBAD寫入內存中的新幀(而不是Z中引用的內存中的位置)?

我不明白為什么去聲明是一個問題。

goto L繞過y的初始化( y不會被設置為&z ),因此分配給who-knows-where-it-pointing *y

似乎問題是未初始化的指針Z的不可預測的行為

不。指針&z實際上是有效的。 intz未初始化,但這並不重要,因為您從未嘗試過讀取它; 你實際上是想覆蓋它。

在第二個塊的開頭,int * y用Z的地址填充。

這就是重點。 goto L繞過那個。

我不明白為什么Trevor的論文暗示Z和X都以某種方式引用0xBAD

我認為Trevor在這里暗示了第二個潛在的問題,盡管我不確定有多少編譯器(如果有的話)會實際展示它。 當使用goto離開塊時,理論上堆棧指針(例如x86上的ESP )可能不會遞減。 通過跳過y的初始化,堆棧指針也可以不遞增。 因此,如果編譯器使用堆棧指針(而不是幀指針,例如x86上的EBP )引用本地,這樣的編譯器理論上可能會將x誤認為y ,就好像int* y = 0xBAD已經發生一樣。

如果刪除塊並分離出值的初始化和聲明,則更容易理解該問題。

int z;
int *y;
goto L;
y = &z;
L: 
*y = 42;

這基本上是原始樣本中發生的事情,但更清楚一點。 這里y = &z線永遠不會被執行,因此y指向一個未定義的位置,因此它的設置是不安全的。

就語言而言,程序的行為是未定義的。 goto跳過了y的初始化; 指針對象存在,但它不指向任何已定義的位置。 取消引用y具有未定義的行為。

但是更詳細地查看代碼,並對其行為進行一些(無根據的)假設:

int z;
{ 
    int x = 0xBAD; goto L; 
}   
{ 
    int *y = &z;
    L: *y = 3; // Possible segfault
}

局部變量(通常)在堆棧上分配。 當控制到達包含其定義的塊的末尾時,每個局部變量都不再存在。

我認為,這個想法是第一個塊創建一個int對象x ,並為其賦值0xBAD goto將控制轉移出該塊時, x不再存在 - 但0xBAD值可能仍然存在於堆棧頂部的上方。

goto將控制轉移到第二個塊。 它會跳過y的初始化,但不會跳過它的分配; 指針對象y仍然在堆棧上分配,無論控件是直接進入塊還是通過goto語句進入塊。 如果0xBAD值保留在堆棧頂部之上,那么y可以很容易地在同一位置分配; 由於跳過初始化, 0xBAD值可以保留在y (或者更確切地說,構成0xBADint表示的0xBAD保留在y並被解釋為指針值)。

所以賦值*y = 3; 嘗試將值3存儲在內存位置0xBAD

這可能是定義,初始化和使用變量x :在y占用的內存中留下特定垃圾值的基本原理。

但事實上沒有我這里描述的(第一款)之后的行為是由C標准要求。 並行塊中的對象(如示例中的對象)可能存儲也可能不存儲在同一存儲器位置。 x的初始化,甚至它在堆棧上的分配,都可以通過優化編譯器輕松消除。 並且局部變量甚至不需要在“堆棧”上分配(在由堆棧指針管理的連續內存區域的意義上); C標准甚至沒有使用“堆棧”這個詞。 連續堆棧是實現局部變量所需語義的最自然方式,但並不是必需的。 當然, intint*的大小不一樣。

底線:當*y = 3; 執行時, y的值是未初始化的垃圾(我故意避免使用“隨機”一詞),因此取消引用y的行為是未定義的。 鑒於某些假設,垃圾可能看起來像0xBAD ,但它並不重要。

正如您所說,問題是變量y可能在初始化之前被訪問。 您提供的代碼段只是一種可能證明問題的方法。

當我使用-Wall選項使用GCC編譯它時,它會警告warning 'y' is used uninitialized in this function 如果我使用g ++將其編譯為C ++代碼,那實際上是一個錯誤:

test.cc:8:3: error: jump to label ‘L’
test.cc:6:25: error:   from here
test.cc:7:10: error:   crosses initialization of ‘int* y’

雖然在這種情況下y是POD類型,但如果它是具有構造函數的類,則goto將跳過構造函數。 C ++語言規范說這在所有情況下都是非法的。

暫無
暫無

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

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