2017-05-04 103 views
2
class Base 
{ 
public: 
    virtual void fnc(size_t nm) 
    { 
     // do some work here 
    } 

    void process() 
    { 
     for(size_t i = 0; i < 1000; i++) 
     { 
      fnc(i); 
     } 
    } 
} 

C++編譯器能否並且會從流程函數中優化對fnc函數的調用,考慮到每次在循環內部調用它時都會使用相同的函數? 或者每次調用該函數時它會從vtable中獲取函數地址?虛擬函數編譯器優化C++

+4

這個問題的答案很可能取決於你的編譯器,編譯器版本和編譯標誌。只要定義的行爲沒有改變,優化主要由實現決定。 –

+1

簡而言之,如果這是您的擔憂,它就可以優化它。如果沒有,請隨時發送一個錯誤報告給您的編譯器供應商 – KABoissonneault

+0

我只是認爲可能有一些規則說它必須優化它。看起來像我常識。 –

回答

1

我查了godbolt.org的example。結果是NO,沒有編譯器優化。

這裏的測試來源:

class Base 
{ 
public: 
// made it pure virtual to decrease clutter 
    virtual void fnc(int nm) =0; 
    void process() 
    { 
     for(int i = 0; i < 1000; i++) 
     { 
      fnc(i); 
     } 
    } 
}; 

void test(Base* b) { 
    return b->process(); 
} 

和生成的ASM:

test(Base*): 
     push rbp  ; setup function call 
     push rbx 
     mov  rbp, rdi ; Base* rbp 
     xor  ebx, ebx ; int ebx=0; 
     sub  rsp, 8 ; advance stack ptr 
.L2: 
     mov  rax, QWORD PTR [rbp+0] ; read 8 bytes from our Base* 
             ; rax now contains vtable ptr 
     mov  esi, ebx    ; int parameter for fnc 
     add  ebx, 1     ; i++ 
     mov  rdi, rbp    ; (Base*) this parameter for fnc 
     call [QWORD PTR [rax]]  ; read vtable and call fnc 
     cmp  ebx, 1000    ; back to the top of the loop 
     jne  .L2 
     add  rsp, 8     ; reset stack ptr and return 
     pop  rbx 
     pop  rbp 
     ret 

,你可以看到它讀取每個調用虛函數表。我想這是因爲編譯器無法證明你不會在函數調用中改變vtable(例如,如果你將位置調用放在新的位置或者什麼傻的東西),所以在技術上,虛擬函數調用可能會在迭代之間改變。

+0

我真的很驚訝。略。 –

+0

那真是無賴 –

0

我寫了一個非常小的實現,並使用g++ --save-temps opt.cpp編譯它們。此標誌保留臨時預處理文件,彙編文件,&目標文件。我用virtual關鍵字跑了一次,一次沒有。這是該計劃。

class Base 
{ 
    public: 
     virtual int fnc(int nm) 
     { 
      int i = 0; 
      i += 3; 
      return i; 
     } 

     void process() 
     { 
      int x = 9; 
      for(int i = 0; i < 1000; i++) 
      { 
       x += i; 
      } 
     } 
    }; 

    int main(int argc, char* argv[]) { 
     Base b; 

     return 0; 
    } 

當我跑virtual關鍵字上的x86_64的Linux機器是得到的組件:

.file "opt.cpp" 
    .section .text._ZN4Base3fncEi,"axG",@progbits,_ZN4Base3fncEi,comdat 
    .align 2 
    .weak _ZN4Base3fncEi 
    .type _ZN4Base3fncEi, @function 
_ZN4Base3fncEi: 
.LFB0: 
    .cfi_startproc 
    pushq %rbp 
    .cfi_def_cfa_offset 16 
    .cfi_offset 6, -16 
    movq %rsp, %rbp 
    .cfi_def_cfa_register 6 
    movq %rdi, -24(%rbp) 
    movl %esi, -28(%rbp) 
    movl $0, -4(%rbp) 
    addl $3, -4(%rbp) 
    movl -4(%rbp), %eax 
    popq %rbp 
    .cfi_def_cfa 7, 8 
    ret 
    .cfi_endproc 
.LFE0: 
    .size _ZN4Base3fncEi, .-_ZN4Base3fncEi 
    .text 
    .globl main 
    .type main, @function 
main: 
.LFB2: 
    .cfi_startproc 
    pushq %rbp 
    .cfi_def_cfa_offset 16 
    .cfi_offset 6, -16 
    movq %rsp, %rbp 
    .cfi_def_cfa_register 6 
    subq $32, %rsp 
    movl %edi, -20(%rbp) 
    movq %rsi, -32(%rbp) 
    movq %fs:40, %rax 
    movq %rax, -8(%rbp) 
    xorl %eax, %eax 
    leaq 16+_ZTV4Base(%rip), %rax 
    movq %rax, -16(%rbp) 
    movl $0, %eax 
    movq -8(%rbp), %rdx 
    xorq %fs:40, %rdx 
    je .L5 
    call [email protected] 
.L5: 
    leave 
    .cfi_def_cfa 7, 8 
    ret 
    .cfi_endproc 
.LFE2: 
    .size main, .-main 
    .weak _ZTV4Base 
    .section .data.rel.ro.local._ZTV4Base,"awG",@progbits,_ZTV4Base,comdat 
    .align 8 
    .type _ZTV4Base, @object 
    .size _ZTV4Base, 24 
_ZTV4Base: 
    .quad 0 
    .quad _ZTI4Base 
    .quad _ZN4Base3fncEi 
    .weak _ZTI4Base 
    .section .data.rel.ro._ZTI4Base,"awG",@progbits,_ZTI4Base,comdat 
    .align 8 
    .type _ZTI4Base, @object 
    .size _ZTI4Base, 16 
_ZTI4Base: 
    .quad _ZTVN10__cxxabiv117__class_type_infoE+16 
    .quad _ZTS4Base 
    .weak _ZTS4Base 
    .section .rodata._ZTS4Base,"aG",@progbits,_ZTS4Base,comdat 
    .type _ZTS4Base, @object 
    .size _ZTS4Base, 6 
_ZTS4Base: 
    .string "4Base" 
    .ident "GCC: (Ubuntu 6.2.0-5ubuntu12) 6.2.0 20161005" 
    .section .note.GNU-stack,"",@progbits 

沒有virtual關鍵字,最後的組裝是:

 
    .file "opt.cpp" 
    .text 
    .globl main 
    .type main, @function 
main: 
.LFB2: 
    .cfi_startproc 
    pushq %rbp 
    .cfi_def_cfa_offset 16 
    .cfi_offset 6, -16 
    movq %rsp, %rbp 
    .cfi_def_cfa_register 6 
    movl %edi, -20(%rbp) 
    movq %rsi, -32(%rbp) 
    movl $0, %eax 
    popq %rbp 
    .cfi_def_cfa 7, 8 
    ret 
    .cfi_endproc 
.LFE2: 
    .size main, .-main 
    .ident "GCC: (Ubuntu 6.2.0-5ubuntu12) 6.2.0 20161005" 
    .section .note.GNU-stack,"",@progbits 

現在不像發佈的問題,這個例子甚至沒有使用虛擬方法和結果組合這個數字要大得多。我沒有嘗試編譯優化,但給它一個。

1

通常,編譯器可以優化任何不會改變程序可觀察行爲的東西。有一些例外,例如從函數返回時刪除非平凡的拷貝構造函數,但可以假定在C++抽象機器中不會改變程序輸出或副作用的期望代碼生成的任何變化都可以由編譯器完成。

那麼,虛擬化功能可以改變可觀察行爲嗎?根據this article,是的。

相關段落:

[...]優化器將不得不假定[虛函數]可能 改變傳遞的對象vptr的。 [...]

void A::foo() { // virtual 
static_assert(sizeof(A) == sizeof(Derived)); 
new(this) Derived; 
} 

這是調用放置new運算符 - 它不分配新的內存,它只是在提供的位置創建一個新的對象。所以,通過在類型A的對象所在的地方構建Derived對象,我們將vptr更改爲指向Derived的vtable。這段代碼甚至合法嗎? C++標準回答是肯定的。」

因此,如果編譯器不具有訪問虛擬函數的定義(並且知道在編譯型的具體類型的*this),那麼這種優化是有風險的。

根據相同的文章,您使用-fstrict-vtable-pointers在Clang的允許這種優化,以使您的代碼少C++標準相符的風險。

+0

如果有人可以幫助我的格式化,幫助將不勝感激 – KABoissonneault