簡體   English   中英

創建一個始終返回零的函數,但優化器不知道

[英]Create a function that always returns zero, but the optimizer doesn't know

我想創建一個總是返回零的函數,但這個事實對於優化器來說不應該是顯而易見的,因此使用該值的后續計算不會因“已知零”狀態而不斷地折疊掉。

在沒有鏈接時優化的情況下,這通常就像將它放在自己的編譯單元中一樣簡單:

int zero() {
  return 0;
}

優化器無法跨單元查看,因此不會發現此函數的始終為零的特性。

但是,我需要一些適用於LTO的東西,以及盡可能多的未來聰明的優化。 我考慮從全球閱讀:

int x;

int zero() {
  return x;
}

...但在我看來,一個足夠聰明的編譯器可以注意到x永遠不會被寫入並仍然決定zero()始終為零。

我考慮過使用volatile ,比如:

int zero() {
  volatile int x = 0;
  return x;
}

...但是,揮發性讀取所需的副作用的實際語義並不完全清楚,似乎不會排除該函數仍然返回零的可能性。

這種始終為零但不是編譯時的值在幾種情況下很有用,例如強制兩個值之間的無操作依賴性。 喜歡的東西: a += b & zero()導致a依賴於b在最終二進制,但不改變的值a

不要通過告訴我“標准不能保證任何方式來做到這一點”來回答這個問題 - 我很清楚,我正在尋找一個實用的答案,而不是標准的語言。

如果編譯器可以解決這個問題我會很驚訝:

int not_a_zero_honest_guv()
{
    // static makes sure the initialization code only gets called once
    static int const i = std::ifstream("") ? 1:0;
    return i;
}

int main()
{
    std::cout << not_a_zero_honest_guv();
}

這使用函數local static的復雜(不可預測)運行時初始化。 如果頑皮的小編譯器發現空文件名總是會失敗,那么在那里放一些非法的文件名。

首先拋開:我相信OP的第三個建議:

int zero() {
  volatile int x = 0;
  return x;
}

實際上會工作(但這不是我的答案;見下文)。 兩個星期前這個完全相同的函數是主題是它是否允許編譯器優化掉本地volatile變量? 有很多討論和不同的意見,我在此不再贅述。 但是對於最近的測試,請參閱https://godbolt.org/g/SA7k5P


我的答案是在上面添加一個static ,即:

int zero() {
  static volatile int x;
  return x;
}

請參閱此處的一些測試: https//godbolt.org/g/qzWYJt

現在隨着static的增加,“可觀察行為”的抽象概念變得更加可信。 通過一些工作,我可以找出x的地址,特別是如果我禁用地址空間布局隨機化 這可能是在.bss段。 然后,通過更多的工作,我可以將調試器/黑客工具附加到正在運行的進程,然后更改 x的值。 對於volatile ,我告訴編譯器我可能會這樣做,因此不允許通過優化x來改變這種“可觀察行為”。 (它也許可以通過內聯來優化調用 zero ,但我不在乎。)

標題是否允許編譯器優化本地volatile變量? 有點誤導,因為討論集中在堆棧上的x而不是局部變量 因此不適用於此。 但我們可以將x從本地范圍更改為文件范圍甚至全局范圍,如:

volatile int x;
int zero() {
  return x;
}

這不會改變我的論點。


進一步討論:

是的, volatile有時會出現問題:例如,請參閱https://godbolt.org/g/s6JhpLhttps://godbolt.org/g/s6JhpL中顯示的指向易失性問題,以及是否通過易失性參考/指針訪問聲明的非易失性對象所述訪問的易變規則?

是的,有時(總是?)編譯器有bug。

但我想說這個解決方案不是一個邊緣案例,並且編譯器編寫者之間存在共識,我將通過查看現有分析來做到這一點。

John Regehr的2010年博文“ Volatile Structs Are Broken”報告了一個在gcc和Clang中優化了易失性訪問的錯誤。 (它在三個小時內得到修復。)一位評論員引用了標准(強調增加):

“6.7.3 ......對具有volatile限定類型的對象的訪問構成是實現定義的。

Regehr同意了,但補充說,對於如何處理非邊緣案件已達成共識:

是的,構成對volatile變量的訪問是實現定義的。 但是你錯過了這樣一個事實:所有合理的C實現都認為從volatile變量讀取是一個讀訪問,而寫一個volatile變量是一個寫訪問。

供進一步參考。 看到:

這些是關於編譯器錯誤和程序員錯誤的報告。 但它們表明應該/確實有多么volatile ,並且這個答案符合這些規范。

你會發現每個編譯器都有一個擴展來實現這一點。

GCC:

__attribute__((noinline))
int zero()
{
    return 0;
}

MSVC:

__declspec(noinline)
int zero()
{
    return 0;
}

在clang和gcc上,破壞一個變量是有效的,但是會產生一些開銷

int zero()
{
    int i = 0;
    asm volatile(""::"g"(&i):"memory");
    return i;
}

在gcc上的O3下編譯到

    mov     DWORD PTR [rsp-4], 0
    lea     rax, [rsp-4]
    mov     eax, DWORD PTR [rsp-4]
    ret

和鏗鏘

    mov     dword ptr [rsp - 12], 0
    lea     rax, [rsp - 12]
    mov     qword ptr [rsp - 8], rax
    mov     eax, dword ptr [rsp - 12]
    ret

暫無
暫無

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

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