我知道内联函数不会使用堆栈来复制参数,但它只是替换函数体,无论它在哪里被调用。
考虑这两个功能:
inline void add(int a) {
a++;
} // does nothing, a won't be changed
inline void add(int &a) {
a++;
} // changes the value of a
如果堆栈不用于发送参数,编译器如何知道变量是否会被修改?在替换这两个函数的调用后代码是什么样的?
答案 0 :(得分:0)
是什么让你觉得堆叠?即使有,你认为它会用于传递参数是什么?
你必须明白有两个推理层次:
在语言级别,如果通过非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节。