簡體   English   中英

在C99中,f()+ g()未定義或僅僅未指定?

[英]In C99, is f()+g() undefined or merely unspecified?

我曾經認為在C99中,即使函數fg的副作用受到干擾,雖然表達式f() + g()不包含序列點,但fg會包含一些,所以行為會未指定:f()將在g()之前調用,或者在f()之前調用g()。

我不再那么肯定了。 如果編譯器內聯函數(即使函數未聲明inline聯函數,編譯器可能決定這樣做)然后重新排序指令,該怎么辦? 可能有人得到上述兩種不同的結果嗎? 換句話說,這是未定義的行為嗎?

這不是因為我打算寫這種東西,而是在靜態分析器中為這樣的語句選擇最佳標簽。

表達式f() + g()包含最少4個序列點; 一個在調用f() (在評估其所有零參數之后); 一個在調用g() (在評估其所有零參數之后); 一個作為對f()的調用返回; 和一個作為對g()的調用返回。 此外,與f()相關的兩個序列點在與g()相關的兩個序列點之前或之后發生。 您無法分辨的是序列點將發生在哪個順序 - f點是否出現在g點之前,反之亦然。

即使編譯器內聯代碼,它也必須遵守“似乎”規則 - 代碼必須與函數不交錯時的行為相同。 這限制了損壞的范圍(假設沒有錯誤的編譯器)。

因此,未指定評估f()g()的順序。 但其他一切都很干凈。


在評論中, supercat問:

我希望源代碼中的函數調用仍然作為序列點,即使編譯器自己決定內聯它們也是如此。 聲明為“內聯”的函數是否仍然存在,或者編譯器是否獲得額外的自由度?

我相信'似乎'規則適用,並且編譯器沒有額外的寬容度來省略序列點,因為它使用明確的inline函數。 認為(懶得去尋找標准中的確切措辭)的主要原因是允許編譯器根據其規則內聯或不內聯函數,但程序的行為不應該改變(除了性能)。

另外,關於(a(),b()) + (c(),d())的排序可以說什么? c()和/或d()可以在a()b()之間執行,或者a()b()c()d()之間執行?

  • 顯然,a在b之前執行,c在d之前執行。 我相信c和d有可能在a和b之間執行,盡管編譯器生成類似代碼的可能性很小; 類似地,a和b可以在c和d之間執行。 雖然我在'c和d'中使用'和',但這可能是'或' - 也就是說,這些操作序列中的任何一個都滿足約束條件:

    • 絕對允許
    • A B C D
    • CDAB
    • 可能允許(保留≺b,c≺d訂購)
    • ACBD
    • ACDB
    • 農發行
    • CABD


    我認為這涵蓋了所有可能的序列。 另請參閱Jonathan Leffler和AnArrayOfFunctions之間聊天 - 要點是AnArrayOfFunctions認為根本不允許“可能允許”的序列。

如果這樣的事情是可能的,那就意味着內聯函數和宏之間存在顯着差異。

內聯函數和宏之間存在顯着差異,但我不認為表達式中的順序是其中之一。 也就是說,可以用宏替換函數a,b,c或d中的任何函數,並且可以發生宏體的相同排序。 在我看來,主要區別在於使用內聯函數,在函數調用中有保證的序列點 - 如主要答案中所述 - 以及逗號運算符。 使用宏,您將丟失與功能相關的序列點。 (所以,也許這是一個顯着的差異......)然而,在很多方面,這個問題就像是有多少天使可以在別針頭上跳舞的問題 - 這在實踐中並不是很重要。 如果有人在代碼審查中向我提供了表達式(a(),b()) + (c(),d()) ,我會告訴他們重寫代碼以使其清楚:

a();
c();
x = b() + d();

這假設b()d()沒有關鍵的排序要求。

有關序列點列表,請參見附錄C. 函數調用(被評估的所有參數和傳遞給函數的執行之間的點)是序列點。 正如你所說的那樣,未指定哪個函數首先被調用,但是這兩個函數中的每一個都將看到另一個函數的所有副作用,或者根本沒有。

@dmckee

好吧,這不適合評論,但這是事情:

首先,你寫一個正確的靜態分析儀。 在這種情況下,“正確”意味着如果對分析的代碼有任何疑問,它將不會保持沉默,所以在這個階段你快樂地混淆未定義和未指定的行為。 它們在關鍵代碼中都是壞的和不可接受的,你正確地警告它們兩者。

但是你只想為一個可能的錯誤警告一次,而且你知道你的分析儀將在“精確度”和“召回”的基准測試中與其他可能不正確的分析儀相比,所以你不能對同一個問題發出兩次警告......無論是真警還是誤報(你不知道哪一個。你永遠都不知道哪個,否則就太容易了)。

所以你想發出一個警告

*p = x;
y = *p;

因為只要p是第一個語句的有效指針,就可以假定它是第二個語句的有效指針。 並且不推斷這會降低您在精確度量上的得分。

所以你教你的分析器假設p是一個有效的指針,只要你第一次在上面的代碼中警告它,這樣你就不會第二次發出警告。 更一般地說,您學習忽略與您已經警告過的內容相對應的值(和執行路徑)。

然后,您意識到沒有多少人在編寫關鍵代碼,因此您可以根據初始正確分析的結果,為其余的代碼進行其他輕量級分析。 比方說,一個C程序切片器。

並告訴“他們”:您不必檢查第一次分析發出的所有(可能是,通常是錯誤的)警報。 切片程序的行為與原始程序相同,只要它們都不會被觸發。 切片器生成的程序與“已定義”執行路徑的切片標准等效。

並且用戶樂意忽略警報並使用切片器。

然后你意識到也許存在誤解。 例如, memmove大多數實現(您知道,處理重疊塊的實現)實際上在使用不指向同一塊的指針(比較不指向同一塊的地址)調用時調用未指定的行為。 並且您的分析器忽略了兩個執行路徑,因為兩者都未指定,但實際上兩個執行路徑都是等效的,並且一切都很好。

所以不應該對警報的含義有任何誤解,如果有人想要忽略它們,只應排除明確無誤的未定義行為。

這就是你最終對區分未指明行為和未定義行為的強烈興趣。 沒有人可以責怪你忽視后者。 但是程序員會在不考慮它的情況下編寫前者,當你說你的切片器排除了程序的“錯誤行為”時,他們就不會感到他們擔心。

這是一個絕對不適合評論的故事的結尾。 向那些讀到那么遠的人道歉。

暫無
暫無

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

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