跳轉到內容

C 程式設計/副作用和順序點

來自 Wikibooks,開放世界中的開放書籍
上一個:常見實踐 C 程式設計 下一個:序列化

在 C 中,更普遍地說,在計算機科學中,如果函式或表示式修改了其範圍之外的狀態,或者與其呼叫函式或外部世界有可觀察的互動,則該函式或表示式被稱為具有副作用。按照慣例,返回值會影響呼叫函式,但這通常不被認為是副作用。

一些副作用是

  • 修改全域性變數或靜態變數
  • 修改函式引數
  • 將資料寫入顯示器或檔案
  • 讀取資料
  • 呼叫其他具有副作用的函式

在存在副作用的情況下,程式的行為可能取決於歷史;也就是說,求值的順序很重要。瞭解和除錯具有副作用的函式需要了解上下文及其可能的歷史。[1][2]

順序點定義了計算機程式執行中的任何一點,在該點,保證所有先前求值的副作用都已執行,並且尚未執行後續求值的任何副作用。它們在 C 的引用中經常被提及,因為它們是確定表示式的有效性以及如果有效,其可能結果的核心概念。有時需要新增更多順序點來使表示式定義,並確保唯一的有效求值順序。

  1. 一個表示式的求值可以先於另一個表示式的求值,或者等效地,另一個表示式的求值後於第一個表示式的求值。
  2. 表示式的求值是不確定順序的,這意味著一個是先於另一個,但哪個是未指定的。
  3. 表示式的求值是無序的

無序求值的執行可能重疊,如果它們共享狀態,則會導致災難性的未定義行為。這種情況可能出現在平行計算中,導致競爭條件。

歧義示例

[編輯 | 編輯原始碼]

考慮兩個函式f()g()。在 C 中,+運算子沒有與順序點相關聯,因此在表示式f()+g()中,f()g()有可能先執行。逗號運算子引入了順序點,因此在程式碼f(),g()中,求值的順序是定義的:首先呼叫f(),然後呼叫g()

當在單個表示式中多次修改同一個變數時,順序點也會發揮作用。一個經常被引用的例子是 C 表示式i=i++,它明顯地將i分配給它之前的值,並遞增ii的最終值是不明確的,因為根據表示式求值的順序,遞增可能發生在賦值之前、之後或與賦值交織在一起。特定語言的定義可能指定一種可能的行為,或者只是說行為是未定義的。在 C 中,求值此類表示式會導致未定義行為。[3]

在 C[4]中,順序點出現在以下位置。

  1. &&(邏輯與)、||(邏輯或)(作為短路求值的一部分)和逗號運算子的左側和右側運算元的求值之間。例如,在表示式*p++ != 0 && *q++ != 0中,*p++ != 0子表示式的所有副作用在嘗試訪問q之前完成。
  2. 在三元“問號”運算子的第一個運算元和第二個或第三個運算元的求值之間。例如,在表示式a = (*p++) ? (*p++) : 0中,在第一個*p++之後有一個順序點,這意味著它在執行第二個例項之前已經被遞增。
  3. 在完整表示式結束時。此類別包括表示式語句(例如賦值a=b;)、return 語句、ifswitchwhiledo-while語句的控制表示式,以及for語句中的所有三個表示式。
  4. 在函式呼叫中,函式被進入之前。引數求值的順序沒有指定,但此順序點意味著所有引數的副作用在函式被進入之前都已完成。在表示式f(i++) + g(j++) + h(k++)中,f被呼叫,引數為i的原始值,但在進入f的主體之前,i被遞增。類似地,在進入gh之前,分別更新jk。但是,沒有指定f()g()h()執行的順序,也沒有指定ijk遞增的順序。如果f的主體訪問變數jk,它可能會發現這兩個變數都被遞增了,或者都沒有被遞增,或者只有一個變數被遞增。(函式呼叫f(a,b,c)不是逗號運算子的用法;abc的求值順序是未指定的。)
  5. 在函式返回時,返回值被複制到呼叫上下文之後。(此順序點僅在 C++ 標準中指定;它僅在 C 中隱式存在。)
  6. 在初始化程式結束時;例如,在宣告int a = 5;中,在對5求值之後。
  7. 在每個宣告序列中的每個宣告符之間;例如,在int x = a++, y = a++中,在對a++的兩次求值之間。(這不是逗號運算子的例子。)
  8. 在與輸入/輸出格式說明符關聯的每個轉換之後。例如,在表示式printf("foo %n %d", &a, 42)中,在%n被求值之後,並在列印42之前,有一個順序點。

參考文獻

[編輯 | 編輯原始碼]
  1. “函數語言程式設計研究主題” D. Turner 編著,Addison-Wesley,1990 年,第 17-42 頁。檢索自:Hughes, John, 為什麼函數語言程式設計很重要 (PDF)
  2. Collberg, CSc 520 程式語言原理,亞利桑那大學計算機科學系
  3. C99 規範的第 6.5 章 #2 條:“在先前和下一個順序點之間,物件在其儲存值最多被表示式求值修改一次。此外,僅訪問先前值以確定要儲存的值。
  4. C99 規範的附錄 C 列出了可以假設順序點的情況。
[編輯 | 編輯原始碼]


上一個:常見實踐 C 程式設計 下一個:序列化
華夏公益教科書