[英]Why is the printf statement in the code below printing a value rather than a garbage value?
int main(){
int array[] = [10,20,30,40,50] ;
printf("%d\n",-2[array -2]);
return 0 ;
}
任何人都可以解釋-2 [array-2]是如何工作的,為什么[]在這里使用? 這是我的任務中的一個問題,它給輸出“-10”,但我不明白為什么?
從技術上講,這會調用未定義的行為。 引用C11
,章節§6.5.6
如果指針操作數和結果都指向同一個數組對象的元素,或者指向數組對象的最后一個元素,則評估不應產生溢出; 否則,行為未定義。 [....]
所以, (array-2)
是未定義的行為。
但是,大多數編譯器都會讀取索引,並且它很可能能夠使+2
和-2
索引無效,[ 2[a]
與a[2]
相同,與*(a+2)
,因此, 2[a-2]
是*((2)+(a-2))
],並且僅考慮要評估的剩余表達式,即*(a)
或, a[0]
。
然后,檢查運算符優先級
-2[array -2]
實際上與-(array[0])
。 因此,結果是值array[0]
和-
ved。
這是教學的一個不幸的例子,因為它意味着做一些經常在實踐中工作的不正確的事情是可以的。
技術上正確的答案是程序具有未定義的行為,因此任何結果都是可能的,包括打印-10,打印不同的數字,打印不同的東西或根本不打印,無法運行,崩潰和/或做一些完全不相關的事情。
未定義的行為來自於評估子表達式array -2
。 array
從其數組類型衰減到指向第一個元素的指針。 array -2
將指向前面兩個位置的元素,但是沒有這樣的元素(並且它不是“一個接一個”的特殊規則),因此無論在什么上下文中,它都是一個問題。出現在。
(C11 6.5.6 / 8說)
當一個具有整數類型的表達式被添加到指針或從指針中減去時,....如果指針操作數和結果都指向同一個數組對象的元素,或者指向數組對象的最后一個元素,則評估不得產生溢出; 否則,行為未定義。
現在教師可能正在尋找的技術上不正確的答案是大多數實現中實際發生的事情:
即使array -2
在實際數組之外,它也會計算到某個地址,該地址是數組數據開始的地址之前的2*sizeof(int)
字節。 取消引用該地址是無效的,因為我們不知道那里確實存在任何int
,但我們不會這樣做。
綜觀更大的表達式-2[array -2]
在[]
操作者具有比一元更高的優先級-
操作者,所以這意味着-(2[array -2])
和不(-2)[array -2]
A[B]
定義為與*((A)+(B))
。 習慣上A
是一個指針值而B
是一個整數值,但是像我們在這里一樣使用它們也是合法的。 所以這些是等價的:
-2[array -2]
-(2[array -2])
-(*(2 + (array - 2)))
-(*(array))
最后一步的行為與我們期望的一樣:向array - 2
的地址值添加兩個array - 2
是該值之后的2*sizeof(int)
字節,這使我們返回到第一個數組元素的地址。 因此*(array)
解引用地址,給出10和-(*(array))
否定該值,給出-10。 該程序打印-10。
你應該永遠不要指望這樣的事情,即使你觀察它在你的系統和編譯器上“有效”。 由於語言不保證會發生什么,如果你做了一些似乎不應該相關的細微更改,或者在不同的系統,不同的編譯器,同一編譯器的不同版本或使用同一系統和編譯器在不同的一天。
以下是-2[array-2]
的評估方式:
首先,請注意-2[array-2]
被解析為- (2[array-2])
。 下標運算符, [...]
具有比一元更高的優先級-
運營商。 我們經常認為像-2
這樣的常數是單個數字,但它實際上是一個-
運算符應用於2
。
在array-2
, array
自動轉換為指向其第一個元素的指針,因此它指向array[0]
。
然后, array-2
嘗試在數組的第一個元素之前計算指向兩個元素的指針。 結果行為不是由C標准定義的,因為C 2018 6.5.6 8表示只定義了指向數組成員和數組末尾的算術。
僅用於說明,假設我們使用擴展C標准的C實現,方法是定義指針以使用平面地址空間並允許任意指針算術。 然后array-2
指向數組之前的兩個元素。
然后2[array-2]
使用C標准將E1[E2]
定義為*((E1)+(E2))
的事實。 也就是說,通過添加兩個東西並應用*
來實現下標運算符。 因此,哪個表達是E1
,哪個是E2
無關緊要。 E1+E2
與E2+E1
相同。 所以2[array-2]
是*(2 + (array-2))
。 添加2將指針從數組之前的兩個元素移回數組的開頭。 然后應用*
在該位置生成元素,即10。
最后,申請-
給-10。 (回想一下,只有使用C實現支持平面地址空間的假設才能得出這個結論。你不能在一般的C代碼中使用它。)
此代碼調用未定義的行為並可以打印任何內容,包括-10
。
C17 6.5.2.1數組下標狀態:
下標operator []的定義是
E1[E2]
與(*((E1)+(E2)))
含義array[n]
等價於*((array) + (n))
,這就是編譯器如何評估下標。 這允許我們像n[array]
一樣寫出愚蠢的混淆,與array[n]
相當100%。 因為*((n) + (array))
等價於*((array) + (n))
。 如下所述:
對於數組,為什么a [5] == 5 [a]?
具體看表達式-2[array -2]
:
[array -2]
和[array - 2]
自然是等價的。 在這種情況下,前者只是為了混淆代碼而故意使用的草率樣式。 []
。 -*( (2) + (array - 2) )
-
不是整數常量2
。 C不支持負整數常量1) , -
實際上是一元減運算符。 []
,所以2 in -2[
“綁定”到[
。 根據C17 6.5.6 / 8,單獨計算子表達式(array - 2)
並調用未定義的行為:
當一個具有整數類型的表達式被添加到指針或從指針中減去時,結果具有指針操作數的類型。 / - /如果指針操作數和結果都指向同一個數組對象的元素,或者指向數組對象的最后一個元素,則評估不應產生溢出; 否則,行為未定義。
推測性地,未定義行為的一種潛在形式可能是編譯器決定用array
替換整個表達式(2) + (array - 2)
,在這種情況下,整個表達式最終將作為-*array
並打印-10
。
沒有保證,因此代碼很糟糕。 如果你被賦予了解釋為什么代碼打印-10
,你的老師是無能的。 作為C研究的一部分,研究混淆不僅無意義/有害,依賴未定義的行為或期望它給出某種結果是有害的。
1) C支持負整數常量表達式 。 -2
是整數常量表達式,其中2
是int
類型的整數常量。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.