汇编中的内联 C++ 方法
Inline c++ method in assembly
让我们创建一个名为 myClass 的小 class。我感兴趣的是,当方法是否为 inlined 时,.asm 看起来有何不同。我在 cpp 文件中制作了两个程序,with 和 without inline 关键字,但是 .asm 输出是相同。我知道 inline 只是编译器的一个提示,我很可能是优化的受害者,但是是否有可能在一个小的内联 cpp 示例中看到差异而不是 asm 中的内联方法?
小时:
#ifndef CLASS_H
#define CLASS_H
class myClass{
private:
int a;
public:
int getA() const;
};
#endif
cpp:
#include <class.h>
inline int myCLass::getA() const{
return a;
};
主要内容:
#include "class.h"
int main(){
myClass a;
a.getA();
return 0;
}
gcc:
gcc -S -O0 main.cpp
两种情况下的asm输出:
.section __TEXT,__text,regular,pure_instructions
.build_version macos, 10, 14
.globl _main ## -- Begin function main
.p2align 4, 0x90
_main: ## @main
.cfi_startproc
## %bb.0:
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset %rbp, -16
movq %rsp, %rbp
.cfi_def_cfa_register %rbp
subq , %rsp
leaq -8(%rbp), %rdi
movl [=14=], -4(%rbp)
callq __ZNK7myClass4getAEv
xorl %ecx, %ecx
movl %eax, -12(%rbp) ## 4-byte Spill
movl %ecx, %eax
addq , %rsp
popq %rbp
retq
.cfi_endproc
## -- End function
.subsections_via_symbols
要使函数可靠地内联,函数的完整定义必须在出现调用的翻译单元中可用。
由于 myClass::getA()
的定义对 main.c
不可见,因此无法内联该函数。它可能内嵌在 myClass.cpp
中出现的任何调用中,但您没有任何这些调用。
要允许内联此函数,您需要在头文件中将其声明为 inline
并包含其定义,例如
class myClass {
…
inline int getA() const { return a; }
…
}
inline
关键字与告诉编译器内联函数调用没有什么关系。 inline
所做的是允许函数在 header.
中被 定义 内联
在函数定义中使用 inline
关键字允许在不违反单一定义规则的情况下在多个翻译单元中定义函数,只要所有定义都相同即可。
在 header 中定义内联函数可以帮助编译器内联调用函数,方法是允许完整定义在多个翻译单元中可见,但这就是 inline
关键字必须要做的全部处理内联调用。
您没有给编译器内联 myClass::getA()
的机会(如另一个答案中所述)。要比较内联方法和外联方法,请比较下面代码中 getA()
和 getAlt()
的用法
// file.h
class myClass
{
int a=1;
public:
int getA() const { return a; } // implicitly inline
int getAlt() const;
};
// file.cc
#include "file.h"
int myClass::getAlt() const
{ return a; }
// main.cc
#include "file.h"
int main()
{
myClass x;
return x.getA() - x.getAlt();
}
除了过程间优化(ipo;因为这可能允许编译器甚至内联 myClass::getAlt()
)之外,您可以使用完全优化。
gcc -O0
不会启用 -finline-functions
,因此即使函数在同一文件中也不会尝试。另见 。 (不要费心尝试使用 __attribute__((always_inline))
:你会得到内联,事情不会优化。
您可以使用 gcc -O3 -fwhole-program *.cpp
内联来启用跨源文件的内联。 (无论它们是否被声明为 inline
,都由编译器决定什么是最好的)。
inline
的要点是让编译器知道如果它确实选择将函数内联到所有调用者中,则不需要发出函数的独立定义。 (因为这个函数的定义,而不仅仅是声明,将出现在所有使用它的翻译单元中。所以如果其他文件决定不内联它,可以在那里发出定义。)
现代编译器仍然使用它们的常规启发式方法来决定是否值得内联。例如具有多个调用者的大型函数可能不会被内联,以避免代码膨胀。 static
告诉编译器没有其他翻译单元可以看到这个函数,所以如果这个文件中只有一个调用者,它很可能会在那里内联。 (如果你有一个大函数,让它成为 static inline
是个坏主意。你会在每个文件中得到一份定义的副本,它没有内联,而且内联过于激进。对于一个小函数来说,可能会在任何地方内联,你应该仍然只使用 inline
,而不是 static inline
,所以万一有任何东西需要函数的地址,所有文件只会共享一个定义。inline
告诉 linker 合并函数的重复定义而不是出错。此行为是 inline
真正做的更重要的部分之一,而不是实际提示编译器你想要它内联。)
gcc -fwhole-program
(所有源文件都在同一个命令行上)为编译器提供了足够的信息来自行做出所有这些决定。它可以查看一个函数在整个程序中是否只有一个调用者,并将其内联而不是创建一个独立的定义加上 arg 设置和 call
.
gcc -flto
允许 link-time 优化类似于整个程序,但不需要命令行上的所有 .cpp
文件一次。相反,它将 GIMPLE 代码存储在 .o
文件中并在 link 时间完成优化。
让我们创建一个名为 myClass 的小 class。我感兴趣的是,当方法是否为 inlined 时,.asm 看起来有何不同。我在 cpp 文件中制作了两个程序,with 和 without inline 关键字,但是 .asm 输出是相同。我知道 inline 只是编译器的一个提示,我很可能是优化的受害者,但是是否有可能在一个小的内联 cpp 示例中看到差异而不是 asm 中的内联方法?
小时:
#ifndef CLASS_H
#define CLASS_H
class myClass{
private:
int a;
public:
int getA() const;
};
#endif
cpp:
#include <class.h>
inline int myCLass::getA() const{
return a;
};
主要内容:
#include "class.h"
int main(){
myClass a;
a.getA();
return 0;
}
gcc:
gcc -S -O0 main.cpp
两种情况下的asm输出:
.section __TEXT,__text,regular,pure_instructions
.build_version macos, 10, 14
.globl _main ## -- Begin function main
.p2align 4, 0x90
_main: ## @main
.cfi_startproc
## %bb.0:
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset %rbp, -16
movq %rsp, %rbp
.cfi_def_cfa_register %rbp
subq , %rsp
leaq -8(%rbp), %rdi
movl [=14=], -4(%rbp)
callq __ZNK7myClass4getAEv
xorl %ecx, %ecx
movl %eax, -12(%rbp) ## 4-byte Spill
movl %ecx, %eax
addq , %rsp
popq %rbp
retq
.cfi_endproc
## -- End function
.subsections_via_symbols
要使函数可靠地内联,函数的完整定义必须在出现调用的翻译单元中可用。
由于 myClass::getA()
的定义对 main.c
不可见,因此无法内联该函数。它可能内嵌在 myClass.cpp
中出现的任何调用中,但您没有任何这些调用。
要允许内联此函数,您需要在头文件中将其声明为 inline
并包含其定义,例如
class myClass {
…
inline int getA() const { return a; }
…
}
inline
关键字与告诉编译器内联函数调用没有什么关系。 inline
所做的是允许函数在 header.
在函数定义中使用 inline
关键字允许在不违反单一定义规则的情况下在多个翻译单元中定义函数,只要所有定义都相同即可。
在 header 中定义内联函数可以帮助编译器内联调用函数,方法是允许完整定义在多个翻译单元中可见,但这就是 inline
关键字必须要做的全部处理内联调用。
您没有给编译器内联 myClass::getA()
的机会(如另一个答案中所述)。要比较内联方法和外联方法,请比较下面代码中 getA()
和 getAlt()
的用法
// file.h
class myClass
{
int a=1;
public:
int getA() const { return a; } // implicitly inline
int getAlt() const;
};
// file.cc
#include "file.h"
int myClass::getAlt() const
{ return a; }
// main.cc
#include "file.h"
int main()
{
myClass x;
return x.getA() - x.getAlt();
}
除了过程间优化(ipo;因为这可能允许编译器甚至内联 myClass::getAlt()
)之外,您可以使用完全优化。
gcc -O0
不会启用 -finline-functions
,因此即使函数在同一文件中也不会尝试。另见 __attribute__((always_inline))
:你会得到内联,事情不会优化。
您可以使用 gcc -O3 -fwhole-program *.cpp
内联来启用跨源文件的内联。 (无论它们是否被声明为 inline
,都由编译器决定什么是最好的)。
inline
的要点是让编译器知道如果它确实选择将函数内联到所有调用者中,则不需要发出函数的独立定义。 (因为这个函数的定义,而不仅仅是声明,将出现在所有使用它的翻译单元中。所以如果其他文件决定不内联它,可以在那里发出定义。)
现代编译器仍然使用它们的常规启发式方法来决定是否值得内联。例如具有多个调用者的大型函数可能不会被内联,以避免代码膨胀。 static
告诉编译器没有其他翻译单元可以看到这个函数,所以如果这个文件中只有一个调用者,它很可能会在那里内联。 (如果你有一个大函数,让它成为 static inline
是个坏主意。你会在每个文件中得到一份定义的副本,它没有内联,而且内联过于激进。对于一个小函数来说,可能会在任何地方内联,你应该仍然只使用 inline
,而不是 static inline
,所以万一有任何东西需要函数的地址,所有文件只会共享一个定义。inline
告诉 linker 合并函数的重复定义而不是出错。此行为是 inline
真正做的更重要的部分之一,而不是实际提示编译器你想要它内联。)
gcc -fwhole-program
(所有源文件都在同一个命令行上)为编译器提供了足够的信息来自行做出所有这些决定。它可以查看一个函数在整个程序中是否只有一个调用者,并将其内联而不是创建一个独立的定义加上 arg 设置和 call
.
gcc -flto
允许 link-time 优化类似于整个程序,但不需要命令行上的所有 .cpp
文件一次。相反,它将 GIMPLE 代码存储在 .o
文件中并在 link 时间完成优化。