C和C++中inline关键字的区别。

8
考虑以下具有inline函数的C++代码:
// this function is in a header-file:
// recursion prevents inlining
inline int calc(int k){
    if(k<=1) return 1;
    return calc(k-1)+calc(k-2);
}

// this function is in a source-file:
int fun(int k){
    return calc(k);
}

我在这里使用递归来模拟编译器无法内联函数calc的情况。

生成的汇编代码(使用-Os编译,查看https://godbolt.org/上的实时演示):

...
        .weak   calc(int)
        .type   calc(int), @function
calc(int):
    // some assembler


       .text
        .globl  fun(int)
        .type   fun(int), @function
fun(int):
...
        jmp     calc(int)

正如预期的那样,编译器无法内联calc函数,因此会为其发出代码,但由于使用了inline关键字,它成为一个弱符号。

将相同的代码编译为C语言,会产生不同的结果(使用-Os选项,可以在https://godbolt.org/上实时查看):

.Ltext0:
        .globl  fun
        .type   fun, @function
fun:
...
        jmp     calc

最显著的区别是:对于calc没有任何代码生成,所以基本上链接器无法将可执行文件链接起来,因为缺少calc的定义。

显然,inline在C和C++中有不同的含义。

C和C++中inline的区别是什么?如何定义calc,使其可以同时在C和C++的头文件中使用?


1
Inline是一个有趣的混乱。[而且还有更多种类的它。] (https://dev59.com/RXVC5IYBdhLWcg3wrDNd) - user14215102
请参考链接问题的答案获取“让它工作”的变体。 - user14215102
如果您尝试使用__attribute((always_inline)),它会给您一个错误:error: inlining failed in call to 'always_inline' 'int calc(int)': recursive inlining。或者指定extern,它将执行C++中的操作。请参见https://en.cppreference.com/w/c/language/inline中的“注意”部分以了解差异。 - Brandon
比较 inline 行为和 -Os 标志看起来有点奇怪 - 内联通常会导致更大的代码大小,而 -Os 是一个最小化代码大小的标志。因此,它看起来是一种不同的启发式方法,使用 -O2 进行检查更自然。 - dewaffled
@dewaffled 我选择了 -Os,因为生成的汇编代码最清晰。内联函数是这样选择的,无论哪种优化级别都不会被内联。 - ead
1
是的,它们是不同的。基本上,C++版本的意思是“这是一个弱符号”,而C版本的意思是“没有弱符号;如果你需要一个符号,请单独定义一个强符号”。也就是说,创建一个单独的extern函数。 - n. m.
2个回答

4

虽然在C和C++中,inline关键字的目标相同:在多个翻译单元中提供函数的定义以便在编译时使用,但这些语言使用不同的方法来处理其后果——在不同的翻译单元中出现相同符号的多个定义。

当C或C++编译器真正内联inline函数时,在翻译单元中不再需要它的定义,也不会发出相应的符号。例如:

inline int fun(){return 2;}

int doit(){
    return fun();
} 

生成的汇编代码可以在此链接(live)中查看。
doit():
        movl    $2, %eax
        ret

然而,当函数没有被内联时,C++标准规定了以下内容:
  1. 一个内联函数在程序中可以有多个定义,只要每个定义出现在不同的翻译单元中且所有定义都相同。
  2. 该函数在每个翻译单元中具有相同的地址。
这或多或少描述了弱符号 - 毫不奇怪,C++编译器在OP的代码中发出了一个弱符号 - 因为C++标准不保证该符号将在另一个翻译单元中定义。
另一方面,C标准保证内联函数的定义必须在另一个翻译单元(即具有外部链接)中提供,因此(为避免同一符号的多重定义),内联函数不会被发出-仅仅是调用。
因此,必须有一个c文件,在其中通过以下方式“实例化”函数:
inline int calc(int k)

这与只有头文件的库的理念不太兼容。一种替代方案是,使用static关键字进行内部链接,即:

static inline int calc(int k){
    if(k<=1) return 1;
    return calc(k-1)+calc(k-2);
}

这将在每个翻译单元中发出一个本地符号(如果未内联),无论编译为C还是C ++。这意味着,同一函数存在多个定义(即在每个翻译对象中,该函数具有不同的地址),这可能会导致更大的可执行文件,并且与C ++默认行为相比是一个挫折。

另一种选择是使用以下inline定义:

#ifdef __cplusplus
    #define INLINE inline
#else
    #define INLINE static inline
#endif

并将其用作

INLINE int calc(int k){
    ...
}

0

1
我认为OP正在寻找一些可以放入头文件中并被多个翻译单元使用的东西。extern inline不是这个东西。 - user14215102

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接