内联函数机制

时间:2013-10-27 12:34:11

标签: c++ inline

我知道内联函数不会使用堆栈来复制参数,但它只是替换函数体,无论它在哪里被调用。

考虑这两个功能:

inline void add(int a) {
   a++; 
} // does nothing, a won't be changed
inline void add(int &a) {
   a++; 
} // changes the value of a

如果堆栈不用于发送参数,编译器如何知道变量是否会被修改?在替换这两个函数的调用后代码是什么样的?

4 个答案:

答案 0 :(得分:0)

是什么让你觉得堆叠?即使有,你认为它会用于传递参数是什么?

你必须明白有两个推理层次:

  • 语言级别:定义了应该发生什么的语义
  • 机器级别:执行编码到CPU指令中的所述语义

在语言级别,如果通过非const引用传递参数,它可能会被函数修改。语言水平不知道这个神秘的“堆栈”是什么。 注意:inline关键字对函数调用是否内联几乎没有影响,它只是说定义是内联的。

在机器级别......有很多方法可以实现这一目标。进行函数调用时,必须遵守调用约定。此约定定义如何在调用者和被调用者之间交换函数参数(和返回类型)以及他们中谁负责保存/恢复CPU寄存器。通常,因为它是如此低级别,所以此约定会根据每个CPU系列进行更改。

例如,在x86上,一些参数将直接在CPU寄存器中传递(如果适合),而其余参数(如果有)将在堆栈上传递。

答案 1 :(得分:0)

如果你强迫它内联方法,我已经检查了GCC至少做了什么:

inline static void add1(int a) __attribute__((always_inline)); 
void add1(int a) {
   a++; 
} // does nothing, a won't be changed

inline static void add2(int &a) __attribute__((always_inline));
void add2(int &a) {
   a++; 
} // changes the value of a

int main() {

label1:
    int b = 0;
    add1(b);

label2:
    int a = 0;
    add2(a);

    return 0;
}

此程序集输出如下所示:

.file   "test.cpp"
.text
.globl  main
.type   main, @function
main:
.LFB2:
    .cfi_startproc
    pushl   %ebp
    .cfi_def_cfa_offset 8
    .cfi_offset 5, -8
    movl    %esp, %ebp
    .cfi_def_cfa_register 5
    subl    $16, %esp
.L2:
    movl    $0, -4(%ebp)
    movl    -4(%ebp), %eax
    movl    %eax, -8(%ebp)
    addl    $1, -8(%ebp)
.L3:
    movl    $0, -12(%ebp)
    movl    -12(%ebp), %eax
    addl    $1, %eax
    movl    %eax, -12(%ebp)
    movl    $0, %eax
    leave
    .cfi_restore 5
    .cfi_def_cfa 4, 4
    ret
    .cfi_endproc
.LFE2:

有趣的是,即使add1()的第一次调用在函数调用之外实际上没有做任何事情,也没有进行优化。

答案 2 :(得分:0)

  

如果堆栈不用于发送参数,那么如何?   编译器知道变量是否会被修改?

正如Matthieu M.已经指出的那样,语言结构本身对stack一无所知。你为函数指定了inline关键字只是为了给编译器一个提示,并表示希望你希望这个例程被内联。如果发生这种情况完全取决于编译器。

编译器试图预测在特定情况下该过程的优点。如果编译器决定内联函数会使代码变慢或不可接受地变大,则不会内联它。或者,如果由于语法依赖性而无法实现,例如使用函数指针进行回调的其他代码,或者在动态/静态代码库中将函数外部导出。

  

替换这两者的调用后代码是什么样的   功能

在使用

进行编译时,此功能都没有被内联
g++ -finline-functions -S main.cpp

你可以看到它,因为在反汇编

void add1(int a) {
    a++;
}
void add2(int &a) {
   a++; 
}

inline void add3(int a) {
   a++; 
} // does nothing, a won't be changed

inline void add4(int &a) {
   a++; 
} // changes the value of a

inline int f() { return 43; }

int main(int argc, char** argv) {

    int a = 31;
    add1(a);
    add2(a);
    add3(a);
    add4(a);
    return 0;
}

我们看到正在制作的每个例程调用

main:
.LFB8:
        .cfi_startproc
        .cfi_personality 0x3,__gxx_personality_v0
        pushq   %rbp
        .cfi_def_cfa_offset 16
        movq    %rsp, %rbp
        .cfi_offset 6, -16
        .cfi_def_cfa_register 6
        subq    $32, %rsp
        movl    %edi, -20(%rbp)
        movq    %rsi, -32(%rbp)
        movl    $31, -4(%rbp)
        movl    -4(%rbp), %eax
        movl    %eax, %edi
        call    _Z4add1i        // function call
        leaq    -4(%rbp), %rax
        movq    %rax, %rdi
        call    _Z4add2Ri       // function call
        movl    -4(%rbp), %eax
        movl    %eax, %edi
        call    _Z4add3i        // function call
        leaq    -4(%rbp), %rax
        movq    %rax, %rdi
        call    _Z4add4Ri       // function call
        movl    $0, %eax
        leave
        ret
        .cfi_endproc

使用-O1进行编译将从程序中删除所有函数,因为它们什么都不做。 但是增加了

__attribute__((always_inline))

允许我们看看代码内联时会发生什么:

void add1(int a) {
    a++;
}

void add2(int &a) {
   a++; 
}

inline static void add3(int a) __attribute__((always_inline));
inline void add3(int a) {
   a++; 
} // does nothing, a won't be changed

inline static void add4(int& a) __attribute__((always_inline));
inline void add4(int &a) {
   a++; 
} // changes the value of a

int main(int argc, char** argv) {

    int a = 31;
    add1(a);
    add2(a);
    add3(a);
    add4(a);
    return 0;
}

现在:g++ -finline-functions -S main.cpp结果为:

main:
.LFB9:
        .cfi_startproc
        .cfi_personality 0x3,__gxx_personality_v0
        pushq   %rbp
        .cfi_def_cfa_offset 16
        movq    %rsp, %rbp
        .cfi_offset 6, -16
        .cfi_def_cfa_register 6
        subq    $32, %rsp
        movl    %edi, -20(%rbp)
        movq    %rsi, -32(%rbp)
        movl    $31, -4(%rbp)
        movl    -4(%rbp), %eax
        movl    %eax, %edi
        call    _Z4add1i        // function call
        leaq    -4(%rbp), %rax
        movq    %rax, %rdi
        call    _Z4add2Ri       // function call
        movl    -4(%rbp), %eax
        movl    %eax, -8(%rbp)
        addl    $1, -8(%rbp)    // addition is here, there is no call
        movl    -4(%rbp), %eax
        addl    $1, %eax        // addition is here, no call again
        movl    %eax, -4(%rbp)
        movl    $0, %eax
        leave
        ret
        .cfi_endproc

答案 3 :(得分:0)

inline关键字有两个关键效果。一个结果是,它是对实现的暗示“在调用点处函数体的内联替换优先于通常的函数调用机制。”这个用法是提示,而不是授权,因为“在调用点执行此内联替换不需要实现”

另一个主要影响是它如何修改一个定义规则。根据ODR,程序必须包含在程序中使用的任何给定非内联函数的一个定义。这对于内联函数不太适用,因为“内联函数应该在每个使用它的翻译单元中定义......”。在100个不同的转换单元中使用相同的内联函数,链接器将面对该函数的100个定义。这不是问题,因为同一函数“......的多个实现在每种情况下都应具有完全相同的定义。”一种看待这种情况的方法:仍然只有一个定义;它只是看起来链接器有很多。

注意:所有引用的材料均来自C ++ 11标准的第7.1.2节。