c語言內嵌彙編代碼之volatile究竟何時用

在閱讀本文之前,請先閱讀gcc的相關文檔,確保對如何在c中使用彙編語言有個基本的認識。

文檔地址爲:

https://gcc.gnu.org/onlinedoc...


  • basic asm 以及沒有 output operands 的 extended asm 默認就是volatile 的,所以它們不用顯式指定 volatile。
  • volatile 的最終目的是爲了防止gcc的某些錯誤優化,所以它只需要用在那些可能發生錯誤優化的地方,濫用 volatile 會導致本應該優化的代碼無法優化,最終導致性能損耗。
  • gcc如果發現 asm 語句的 output operands 在c語言中沒有被使用,則優化後的代碼可能會直接移除該語句。
  • gcc如果認爲一個c函數中的多條相同的asm語句的 output operands 結果相同,則可能會只保留其中一條asm語句,在該c函數使用到這條 asm語句 output operands 的地方,統一用相同的結果(比如,如果asm語句在循環中,則會提到循環外,如果asm語句在一個c函數中被順序執行,則只保留第一條asm語句,刪除後面的asm語句)。
  • 上面兩個優化場景就是gcc可能發生錯誤優化的地方,這些地方要注意是否要使用 volatile。
  • volatile 無法保證多條asm語句在優化前後順序相同,如果要保證順序,可以把多條asm語句中的彙編代碼都寫到一個asm語句裏。

下面用示例講解下相關概念:

#include <assert.h>
#include <stdint.h>
#include <stdio.h>

void do_check(uint32_t dwSomeValue) {
  uint32_t dwRes;
  asm("bsfl %1,%0" : "=r"(dwRes) : "r"(dwSomeValue) : "cc");
  assert(dwRes == 3);
}

int main(int argc, char *argv[]) { do_check(8); }

編譯後再以彙編形式查看do_check方法:

$ gcc -O3 main.c && objdump --disassemble=do_check a.out
0000000000001190 <do_check>:
    1190:  0f bc ff               bsf    %edi,%edi
    1193:  83 ff 03               cmp    $0x3,%edi
    1196:  75 01                  jne    1199 <do_check+0x9>
    1198:  c3                     retq
    1199:  50                     push   %rax
    119a:  e8 c1 ff ff ff         callq  1160 <do_check.part.0>

由於assert宏中使用了asm語句中的輸出參數dwRes,所以即使asm語句沒有指定volatile,do_check方法也是在正常執行的。

下面看下把assert方法去掉之後的do_check彙編代碼:

$ gcc -O3 -D NDEBUG main.c && objdump --disassemble=do_check a.out
0000000000001130 <do_check>:
    1130:  c3                     retq

由上可見,因爲我們在執行gcc時加了-D NDEBUG參數,定義了NDEBUG宏,所以上面的assert宏最終會變爲空指令。

也就是說,do_check方法中沒有任何地方在使用asm語句中的輸出參數dwRes,所以gcc就會在優化後的代碼中刪除掉該asm語句,所以上面的do_check方法最終變成了空方法。

那我們將上面的asm語句加上volatile再試下:

void do_check(uint32_t dwSomeValue) {
  uint32_t dwRes;
  asm volatile("bsfl %1,%0" : "=r"(dwRes) : "r"(dwSomeValue) : "cc");
  assert(dwRes == 3);
}

編譯後再以彙編形式查看do_check方法:

$ gcc -O3 -D NDEBUG main.c && objdump --disassemble=do_check a.out
0000000000001130 <do_check>:
    1130:  0f bc ff               bsf    %edi,%edi
    1133:  c3                     retq

由上可以看到,這次即使指定了-D NDEBUG參數,使assert宏變成了空操作,導致do_check方法中沒有任何地方使用dwRes變量,但由於volatile的存在,該asm語句還是沒有被優化掉。

通過上面的例子,我們就可以看到 volatile 是如何防止 gcc 優化代碼的,但是在上面的例子中,該優化是一個正確的優化,所以不應該加 volatile。

如果有其他的asm語句,雖然它的輸出參數沒有被使用,但也不應該被優化掉,這個時候就應該使用 volatile 了。

希望對你有所幫助。

完。

更多原創文章,請關注我微信公衆號:

底層技術研究

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章