c語言內嵌彙編代碼之volatile究竟什麼時候用

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

文檔地址爲:sass

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彙編代碼:ui

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

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

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

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

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 了。

但願對你有所幫助。

完。

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

底層技術研究

相關文章
相關標籤/搜索