[英]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
實際上是有效的。 int
值z
未初始化,但這並不重要,因為您從未嘗試過讀取它; 你實際上是想覆蓋它。
在第二個塊的開頭,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
(或者更確切地說,構成0xBAD
的int
表示的0xBAD
保留在y
並被解釋為指針值)。
所以賦值*y = 3;
嘗試將值3
存儲在內存位置0xBAD
。
這可能是定義,初始化和使用變量x
:在y
占用的內存中留下特定垃圾值的基本原理。
但事實上沒有我這里描述的(第一款)之后的行為是由C標准要求。 並行塊中的對象(如示例中的對象)可能存儲也可能不存儲在同一存儲器位置。 x
的初始化,甚至它在堆棧上的分配,都可以通過優化編譯器輕松消除。 並且局部變量甚至不需要在“堆棧”上分配(在由堆棧指針管理的連續內存區域的意義上); C標准甚至沒有使用“堆棧”這個詞。 連續堆棧是實現局部變量所需語義的最自然方式,但並不是必需的。 當然, int
和int*
的大小不一樣。
底線:當*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.