黃爸爸狗園

本園只有sanitizer,沒有狗籠

0%

Stop Using `constexpr` (And Use This Instead!)

這標題,真的很clickbait..... 這份筆記會針對constexpr與編譯期初始化的關聯做說明

  • constexpr,一般來說會用在標示某個變數需要於編譯期(compile time)就完成初始化
1
2
3
4
5
6
7
8
int get_val(int i) {
return i*2;
}

int main() {
int value = get_val(2) ;
return value ;
}
  • 讓我們來看一看這個很簡單的範例出來的asm(x86_64 gcc -O0)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
get_val(int):
push rbp
mov rbp, rsp
mov DWORD PTR [rbp-4], edi
mov eax, DWORD PTR [rbp-4]
add eax, eax
pop rbp
ret
main:
push rbp
mov rbp, rsp
sub rsp, 16
mov edi, 2
call get_val(int)
mov DWORD PTR [rbp-4], eax
mov eax, DWORD PTR [rbp-4]
leave
ret
  • 基本上就是很老實地把數值算出來,但是如果啟用最佳化又會是什麼情況(x86_64 gcc -O3)

    1
    2
    3
    4
    5
    6
    get_val(int):
    lea eax, [rdi+rdi]
    ret
    main:
    mov eax, 4
    ret

  • 數值直接在編譯的時候算出來,寫進code了

  • 在影片裡頭提到constexpr到底會不會被編譯期初始化這一點永遠是不確定的,他會受到最佳化等級還有編譯器本身的影響

    • 我自己的經驗是,多半都會,唯一你必須要確保你有加上constexpr
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      #include <array>

      constexpr std::array<int, 100> get_val() {
      std::array<int, 100> result{};
      for (int n = 0 ; n < result.size() ; ++n) {
      result[ n ] = n ;
      } // for

      return result;
      }

      int main() {
      constexpr auto value = get_val()[45] ;
      return value ;
      }
      此範例中的value,無論最佳化等級開多少,都會是編譯期求解
  • 我們現在要來進一步探討constexpr variable的storage duration,我把影片中的例子人肉OCR出來了

    • here
    • 看似沒什麼問題的程式碼,居然觸發了ASAN,到底是怎麼回事?
    • 我們前面提到constexpr會讓變數在編譯期初始化但我們仔細看一下asm(in main(),為了簡潔我把ASAN關掉了)
      1
      2
      3
      4
      lea     rdi, [rbp - 4016]
      movabs rsi, offset .L__const.main.values
      mov edx, 4000
      call memcpy@PLT
    • 驚人的事實是,array本身的確是constexpr,但是我們用了另外一個指標p去取他的值
      • 然後還在scope裏頭,這下可好,p會指向我們從executable搬到stack上的array
      • 你是不是發現了,即便宣告成constexpr,但她的storage duration依然不是static,這個constexpr的array居然是automatic(我指它的storage duration)
      • 這個程式在-O3的時候,最佳化直接編譯求值了,所以不會被ASAN抓到
      • 但是在-O0的時候我們有去做搬移的動作,然後就華麗的爆炸
  • 所以結論是,我們原本所想的constexpr跟實際上的constexpr是有差異的

    • 最接近我們所想的應該是static constexpr,我們把先前的範例改一改
      • 在constexpr前加上static,你就會看到剛剛提到的那個搬移動作不見了
      • 因為現在values的storage duration是static
    • 我的老天鵝阿

延伸閱讀