9

亲爱的 StackOverflowers,

我得到了一段在 Microsoft Visual Studio C++ 2012 上编译的简单代码:

int add(int x, int y)
{
    return x + y;
}

typedef int (*func_t)(int, int);

class A
{
public:
    const static func_t FP;
};

const func_t A::FP = &add;

int main()
{
 int x = 3;
 int y = 2;
 int z = A::FP(x, y);
 return 0;
}

编译器生成以下代码:

int main()
{
000000013FBA2430  sub         rsp,28h  
int x = 3;
int y = 2;
int z = A::FP(x, y);
000000013FBA2434  mov         edx,2  
000000013FBA2439  lea         ecx,[rdx+1]  
000000013FBA243C  call        qword ptr [A::FP (013FBA45C0h)]  
return 0;
000000013FBA2442  xor         eax,eax
}

我在“完全优化”(/Obx 标志)和“任何适合”的内联函数扩展上编译了这个。(/Ob2 标志)

我想知道为什么编译器没有特别内联这个调用,因为它是 const。你们中的任何人都知道为什么它没有内联以及是否可以使编译器内联它?

基督教

编辑:我现在正在运行一些测试,并且 MSVC 在以下情况下也无法内联函数指针:

-我将 const 指针移出类并使其成为全局。

-我将 const 指针移出类并使其在 main 中本地化。

-我将指针设为非 const 并将其移入本地。

- 当我将返回类型设为 void 并且不给它任何参数时

我开始相信 Microsoft Visual Studio 根本无法内联函数指针......

4

4 回答 4

2

问题不在于内联,编译器在每一个机会都会这样做。问题是 Visual C++ 似乎没有意识到指针变量实际上是一个编译时常量。

测试用例:

// function_pointer_resolution.cpp : Defines the entry point for the console application.
//

extern void show_int( int );

extern "C" typedef int binary_int_func( int, int );

extern "C" binary_int_func sum;
extern "C" binary_int_func* const sum_ptr = sum;

inline int call( binary_int_func* binary, int a, int b ) { return (*binary)(a, b); }

template< binary_int_func* binary >
inline int callt( int a, int b ) { return (*binary)(a, b); }

int main( void )
{
    show_int( sum(1, 2) );
    show_int( call(&sum, 3, 4) );
    show_int( callt<&sum>(5, 6) );
    show_int( (*sum_ptr)(1, 7) );
    show_int( call(sum_ptr, 3, 8) );
//  show_int( callt<sum_ptr>(5, 9) );
    return 0;
}

// sum.cpp
extern "C" int sum( int x, int y )
{
    return x + y;
}

// show_int.cpp
#include <iostream>

void show_int( int n )
{
    std::cout << n << std::endl;
}

这些函数被分成多个编译单元,以便更好地控制内联。具体来说,我不想show_int内联,因为它会使汇编代码变得混乱。

第一个问题是有效代码(注释行)被 Visual C++ 拒绝。 G++ 没有问题,但 Visual C++ 抱怨“预期的编译时常量表达式”。这实际上是对所有未来行为的良好预测。

在启用优化和正常编译语义(无跨模块内联)的情况下,编译器生成:

_main   PROC                        ; COMDAT

; 18   :    show_int( sum(1, 2) );

    push    2
    push    1
    call    _sum
    push    eax
    call    ?show_int@@YAXH@Z           ; show_int

; 19   :    show_int( call(&sum, 3, 4) );

    push    4
    push    3
    call    _sum
    push    eax
    call    ?show_int@@YAXH@Z           ; show_int

; 20   :    show_int( callt<&sum>(5, 6) );

    push    6
    push    5
    call    _sum
    push    eax
    call    ?show_int@@YAXH@Z           ; show_int

; 21   :    show_int( (*sum_ptr)(1, 7) );

    push    7
    push    1
    call    DWORD PTR _sum_ptr
    push    eax
    call    ?show_int@@YAXH@Z           ; show_int

; 22   :    show_int( call(sum_ptr, 3, 8) );

    push    8
    push    3
    call    DWORD PTR _sum_ptr
    push    eax
    call    ?show_int@@YAXH@Z           ; show_int
    add esp, 60                 ; 0000003cH

; 23   :    //show_int( callt<sum_ptr>(5, 9) );
; 24   :    return 0;

    xor eax, eax

; 25   : }

    ret 0
_main   ENDP

sum_ptr使用和不使用之间已经存在巨大差异sum_ptr。使用语句sum_ptr生成间接函数调用call DWORD PTR _sum_ptr,而所有其他语句生成直接函数调用call _sum,即使源代码使用函数指针也是如此。

如果我们现在通过编译 function_pointer_resolution.cpp 和 sum.cpp/GL并链接 with来启用内联/LTCG,我们会发现编译器内联了所有直接调用。间接呼叫保持原样。

_main   PROC                        ; COMDAT

; 18   :    show_int( sum(1, 2) );

    push    3
    call    ?show_int@@YAXH@Z           ; show_int

; 19   :    show_int( call(&sum, 3, 4) );

    push    7
    call    ?show_int@@YAXH@Z           ; show_int

; 20   :    show_int( callt<&sum>(5, 6) );

    push    11                  ; 0000000bH
    call    ?show_int@@YAXH@Z           ; show_int

; 21   :    show_int( (*sum_ptr)(1, 7) );

    push    7
    push    1
    call    DWORD PTR _sum_ptr
    push    eax
    call    ?show_int@@YAXH@Z           ; show_int

; 22   :    show_int( call(sum_ptr, 3, 8) );

    push    8
    push    3
    call    DWORD PTR _sum_ptr
    push    eax
    call    ?show_int@@YAXH@Z           ; show_int
    add esp, 36                 ; 00000024H

; 23   :    //show_int( callt<sum_ptr>(5, 9) );
; 24   :    return 0;

    xor eax, eax

; 25   : }

    ret 0
_main   ENDP

底线:是的,只要不是从变量中读取该函数指针,编译器就会通过编译时常量函数指针进行内联调用。 这种函数指针的使用得到了优化:

call(&sum, 3, 4);

但这没有:

(*sum_ptr)(1, 7);

所有测试都使用 Visual C++ 2010 Service Pack 1 运行,为 x86 编译,托管在 x64 上。

Microsoft (R) 32 位 C/C++ 优化编译器版本 16.00.40219.01 用于 80x86

于 2013-05-09T22:49:49.157 回答
1

我认为你的结论是对的:“......根本不能内联函数指针”。

这个非常简单的例子也破坏了优化:

static inline
int add(int x, int y)
{
    return x + y;
}

int main()
{
    int x = 3;
    int y = 2;
    auto q = add;
    int z = q(x, y);
    return z;
}

您的示例对于编译器来说更加复杂,因此不足为奇。

于 2013-05-09T20:05:30.813 回答
0

You can try __forceinline. Nobody is going to be able to tell you exactly why it isn't inlined. Common sense says to me that it should be, however. /O2 should favor code speed over code size (inlining)... Strange.

于 2013-05-09T16:33:40.233 回答
0

这不是一个真正的答案,而是一个“可能的解决方法”:微软的 STL 曾经提到 lambdas 比 f ptrs 更容易内联,所以你可以尝试一下。

作为一个琐事,Bjarne 经常提到 sort 比 qsort 更快,因为 qsort 采用函数 ptr,但就像其他人已经注意到 gcc 内联它们没有问题......所以也许 Bjarne 应该尝试 gcc :P

于 2014-02-24T18:04:31.120 回答