可以/做C编译器优化内联函数的地址吗?

时间:2011-04-05 05:43:03

标签: c micro-optimization

假设我有以下代码:

int f() {
  int foo = 0;
  int bar = 0;

  foo++;
  bar++;

  // many more repeated operations in actual code
  foo++;
  bar++;

  return foo+bar;
}

将重复的代码抽象为单独的函数,我们得到

static void change_locals(int *foo_p, int *bar_p) {
  *foo_p++;
  *bar_p++;
}

int f() {
  int foo = 0;
  int bar = 0;

  change_locals(&foo, &bar);
  change_locals(&foo, &bar);

  return foo+bar;
}

我希望编译器内联change_locals函数,并将结果代码中*(&foo)++之类的内容优化为foo++

如果我没记错的话,取一个局部变量的地址通常会阻止一些优化(例如它不能存储在寄存器中),但是如果没有对地址进行指针运算并且它没有从功能?使用较大的change_locals,如果在MSVC中声明inline__inline)会有所不同吗?

我对GCC和MSVC编译器的行为特别感兴趣。

3 个答案:

答案 0 :(得分:3)

gcc会忽略

inline(及其所有表兄_inline__inline ...)。除了较低的优化级别外,它可能内联它决定的任何优势。

gcc -O3 for x86的代码过程是:

        .text
        .p2align 4,,15
.globl f
        .type   f, @function
f:
        pushl   %ebp
        xorl    %eax, %eax
        movl    %esp, %ebp
        popl    %ebp
        ret
        .ident  "GCC: (GNU) 4.4.4 20100630 (Red Hat 4.4.4-10)"

它返回零,因为* ptr ++没有按你的想法行事。将增量更正为:

    (*foo_p)++;
    (*bar_p)++;

结果

        .text
        .p2align 4,,15
.globl f
        .type   f, @function
f:
        pushl   %ebp
        movl    $4, %eax
        movl    %esp, %ebp
        popl    %ebp
        ret

所以它直接返回4.它不仅内联它们,而且还优化了计算。

来自vs 2005的Vc ++提供了类似的代码,但它也为change_locals()创建了无法访问的代码。我使用命令行

/O2 /FD /EHsc /MD /FA /c /TP

答案 1 :(得分:2)

  

如果我没记错的话,服用   通常是局部变量的地址   阻止一些优化(例如它   不能存储在寄存器中),但是   没有指针时这是否适用   算术是在地址上完成的   它没有逃脱功能吗?

一般的答案是,如果编译器可以确保没有其他人会改变其背后的值,则可以安全地将其放在寄存器中。

将此想象为编译器首先执行内联,然后将所有那些*&foo(由内联产生)转换为简单foo,然后再决定是否应将它们放在内存中的寄存器中堆栈。

  

有了更大的change_locals,是吗?   如果宣布它会有所作为   内联(MSVC中的__inline)?

同样,一般来说,编译器是否决定使用启发式方法来内联。如果您明确指定要内联的内容,编译器可能会将其加权到其决策过程中。

答案 2 :(得分:1)

我使用以下方法测试了gcc 4.5,MSC和IntelC:

#include <stdio.h>

void change_locals(int *foo_p, int *bar_p) {
  (*foo_p)++;
  (*bar_p)++;
}

int main() {
  int foo = printf("");
  int bar = printf("");

  change_locals(&foo, &bar);
  change_locals(&foo, &bar);

  printf( "%i\n", foo+bar );
}

他们所有人都内联/优化了foo + bar值,但也做到了 生成change_locals()的代码(但没有使用它)。

不幸的是,仍然无法保证他们会这样做 任何一种这样的“本地功能”。

GCC

__Z13change_localsPiS_:
    pushl   %ebp
    movl    %esp, %ebp
    movl    8(%ebp), %edx
    movl    12(%ebp), %eax
    incl    (%edx)
    incl    (%eax)
    leave
    ret

_main:
    pushl   %ebp
    movl    %esp, %ebp
    andl    $-16, %esp
    pushl   %ebx
    subl    $28, %esp
    call    ___main
    movl    $LC0, (%esp)
    call    _printf
    movl    %eax, %ebx
    movl    $LC0, (%esp)
    call    _printf
    leal    4(%ebx,%eax), %eax
    movl    %eax, 4(%esp)
    movl    $LC1, (%esp)
    call    _printf
    xorl    %eax, %eax
    addl    $28, %esp
    popl    %ebx
    leave
    ret