Re: [問題] 關於Undefined Behavior的一些疑問
好像是被我推坑的 xD
※ 引述《angleevil (human)》之銘言:
: 1.
: 最近在看http://blog.regehr.org/archives/213
: 裡面有討論一些Undefined Behavior,但是有些不是很懂,
: 因此來詢問大家,
: 為什麼a變數要檢查INT32_MIN呢?
: b為-1會有什麼問題?
:
: int32_t safe_div_int32_t (int32_t a, int32_t b)
: {
: if ((b == 0) || ((a == INT32_MIN) && (b == -1)))
: {
: report_integer_math_error();
: return 0;
: }
: else
: {
: return a / b;
: }
: }
:
: → akasan:INT32_MIN/-1 = INT32_MAX'+1' 07/06 10:10
: → akasan:在一般二補數系統會發生 07/06 10:10
補充:C99 強制堆定 INTN_MIN 剛好 -2^(N-1) 然後 INTN_MAX 剛好
2^(N-1)-1. 即使在某些少見的實作中 int 不用二補數,但 int32_t 一定
是佔了剛好 32 位元,用二補數表示的有號整數。標準的意思是說沒支援
不提供沒關係,想支援就按照規矩來。這就是作者有信心這份程式碼絕對
安全的原因。(在詭異的實作上頂多編譯失敗,執行期間是有保證的。)
: 2.
: 還有所謂的side effect是指a=b++ + ++b的問題嘛?
: 因為我好像一值以來,把它和bound問題混在一起想,
: 作者用asm當例子,我就更難理解了!
不完全是。副作用(side effect)指的是除了計算出結果以外,其他
「看得到」的效果。例如我今天請你計算 1 + 1, 結果你先跑去引爆核彈
再回頭告訴我答案是 2,然後我也看到地球毀了,那引爆核彈就是副作用。
這有兩個重點,一個是「計算以外」,一個是「看得到」(observable)。
C99 和 C++03 定義了好幾樣東西都是副作用(C99 5.1.2.3 C++03 1.9),
其中一樣講白話就是修改變數中的內容。
blog 文章第三篇我個人感覺重點有兩個。第一個是,通常如果你這樣寫:
| 敘述1;
| 敘述2;
由於分號的關係,標準規定所有敘述1的副作用(例如引爆核彈)一定
要在分號前完成(要等地球徹底毀滅),然後敘述2的副作用(例如引爆氫
彈)才能開始(再毀滅一次)。許多運算子等等也有規定副作用的順序,
(篇幅關係全部省掉 xD)標準認為修改變數中的內容算副作用,可是很多
實際上在用的編譯器才不鳥這規定。也就是說,
| int a = 0;
| int b = 0;
| a++;
| b++;
(假設實作用現在常見的記憶體充當虛擬機器的記憶體)
理論上要等 a 的新值儲存完才能開始對 b 動刀,但實際上很多編譯器
都不按標準實作,因為它們覺得修改 a 和 b 的內容不算「副作用」。(請
原諒我一直避開艱澀難懂的術語。)第一個重點和文章後面的附註,就是要
強調「編譯器其實沒按照標準做」這件事情。這其實是情由可原的,因為大
部分的情況下的確分辨不出來 a 和 b 到底誰先改,然後如果編譯器不想辦
法把最佳化的手段推到極限,還會被別人抱怨。(「穩定但比較慢」的實作
永遠不及「快速而表面上還行」的實作受歡迎... 直到哪天出了大問題才會
檢討吧,我猜。)
註:個人不同意直接說這兩個不算副作用,見文章最後面。
第二個重點是,未定義行為不算在副作用中。如果召喚外星人是未定義
行為,然後你寫:
| 引爆核彈;
| 召喚外星人;
有可能核彈都還沒引爆,外星人就出現,然後把地球瞬間變成黑洞。撇
開第一個問題(沒按照標準實作)不談,在第二個問題上標準和編譯器可謂
有志一同,一致認為召喚出外星人是程式設計師的責任。即使表面上看起來,
地球要先被炸一炸才變成黑洞,但這不是真實世界的樣子。口說無憑,網路
上那篇文章就舉了一個例子惡整 clang. 就我測試的結果,把 printf 換成
fprintf (stderr, ... 也都可以重現它的結果。文章主要的意思是常常有人
用 printf 來偵測外星人在哪裡:
| /* 好多程式碼 */
|
| 發出 SOS 訊號;
|
| /* 又好多程式碼 */
|
| 召喚外星人;
表面上看起來,如果我們在螢幕上沒有發現 SOS 訊號,就可以篤定外星
人在那行程式碼之前出現。這種推論是錯的。即使表面上外星人是後面才會
出現,它可以在你發出訊號前把地球變成黑洞。
註:個人覺得不必像文章討論算不算副作用,因為未定義行為會直接讓
整個程式都沒有定義,當然也不受上面什麼分號的約束了。技術上來說,編
譯器遇到有未定義行為的程式,隨便怎麼實作都無所謂。
希望看了我亂七八糟的譬喻後對你有幫助 xD
--
※ 發信站: 批踢踢實業坊(ptt.cc)
◆ From: 140.112.30.39
※ 編輯: Favonia 來自: 140.112.30.39 (07/06 12:46)
編輯:2^N 應為 2^(N-1), 不好意思打錯了。
※ 編輯: Favonia 來自: 140.112.30.39 (07/06 12:53)
→
07/06 13:17, , 1F
07/06 13:17, 1F
→
07/06 13:18, , 2F
07/06 13:18, 2F
→
07/06 13:19, , 3F
07/06 13:19, 3F
→
07/06 13:20, , 4F
07/06 13:20, 4F
稍微再補了一點 :Q
推
07/06 18:13, , 5F
07/06 18:13, 5F
※ 編輯: Favonia 來自: 140.112.30.39 (07/06 20:11)
→
07/06 21:13, , 6F
07/06 21:13, 6F
→
07/06 21:13, , 7F
07/06 21:13, 7F
嗯,應該就是你的意思。標準允許外星人在隨便一個地方出現。我改一
下文章 xDDD
※ 編輯: Favonia 來自: 140.112.30.39 (07/06 21:17)
→
07/06 21:20, , 8F
07/06 21:20, 8F
→
07/06 21:23, , 9F
07/06 21:23, 9F
→
07/06 21:24, , 10F
07/06 21:24, 10F
嗯改好了 xD 謝謝你的建議。
※ 編輯: Favonia 來自: 140.112.30.39 (07/06 21:26)
→
07/06 21:41, , 11F
07/06 21:41, 11F
→
07/06 21:42, , 12F
07/06 21:42, 12F
※ 編輯: Favonia 來自: 140.112.30.39 (07/07 04:25)
推
07/07 13:25, , 13F
07/07 13:25, 13F
不好意思下次改火星好了 xDD
編輯:重寫一些細節 xD
※ 編輯: Favonia 來自: 140.112.30.39 (07/07 19:56)
→
07/07 21:00, , 14F
07/07 21:00, 14F
→
07/07 21:05, , 15F
07/07 21:05, 15F
→
07/07 21:05, , 16F
07/07 21:05, 16F
→
07/07 21:13, , 17F
07/07 21:13, 17F
推
07/07 22:42, , 18F
07/07 22:42, 18F
編輯:補一下自己意見好了,因為這篇文章是按照網路上那篇文章打的,
我沒有完全同意 orz 我的意見標上黃色。不想被人當作我同意所有看法 xD
※ 編輯: Favonia 來自: 140.112.30.39 (07/08 06:16)
編輯:發現愚蠢的錯字 Orz
※ 編輯: Favonia 來自: 140.112.30.39 (07/08 06:42)
※ 編輯: Favonia 來自: 140.112.30.39 (07/08 06:42)
→
07/08 08:57, , 19F
07/08 08:57, 19F
→
07/11 16:21, , 20F
07/11 16:21, 20F
※ 編輯: Favonia 來自: 140.112.30.39 (07/23 22:22)
我想了好久天才想到要怎麼補一個很討厭的東西,修正我之前講的話:
標準雖然有一大堆規定,但是實際上那是針對虛擬機器定的。如果編譯器
不把真實記憶體直接當作虛擬機器的記憶體,則上述的最佳化可能都沒有關系。
引用這段標準:
| An implementation might define a one-to-one correspondence
| between abstract and actual semantics: at every sequence point,
| the values of the actual objects would agree with those
| specified by the abstract semantics. The keyword volatile would
| then be redundant.
這個 blog 比較像是立基於上面這種實作來批評。它假設實體的記憶體和
虛擬機器的記憶體之間有某種簡單的對應。但其實標準接著說...
| Alternatively, an implementation might perform various optimi-
| zations within each translation unit, such that the actual
| semantics would agree with the abstract semantics only when
| making function calls across translation unit boundaries. In
| such an implementation, at the time of each function entry and
| function return where the calling function and the called
| function are in different translation units, the values of all
| externally linked objects and of all objects accessible via
| pointers therein would agree with the abstract semantics.
| Furthermore, at the time of each such function entry the values
| of the parameters of the called function and of all objects
| accessible via pointers therein would agree with the abstract
| semantics. In this type of implementation, objects referred to
| by interrupt service routines activated by the signal function
| would require explicit specification of volatile storage, as
| well as other implementation-defined restrictions.
所以實作有很大的彈性可以亂搞。實務上的問題(包括模糊的標準如何解
讀)可以參考這裡:
http://www.gnu.org/software/autoconf/manual/html_node/Volatile-Objects.html
※ 編輯: Favonia 來自: 140.112.30.39 (07/24 22:10)
※ 編輯: Favonia 來自: 140.112.30.39 (07/30 10:31)