C99 inline 为什么我们不能继续使用它?

C99 inline why can't we just keep using it?

我刚刚了解到内联使我的代码在使用函数时更快,但我想知道为什么我们不内联每个函数,以正常方式编写函数有什么意义为什么我们有如此强大的关键字(我知道编译器会选择是否内联函数,但最好建议内联每个函数)?

另外:我正在使用 Clion 尝试内联以下函数,但出现错误:

#include <stdio.h>
#include <time.h>


inline double maxf(int a,int b)
{
    if (a>b)
        return a;
    return b;
}

int main() {
    clock_t begin = clock();
    double arr[999999]={0};
    double max=arr[0];
    arr[9898]=99999999;
    for (int i=1;i<999999;i++)
    {
            max=maxf(arr[i],max);
    }
    clock_t end = clock();
    double time_spent = (double)(end - begin) / CLOCKS_PER_SEC;
    printf("%lf",time_spent);
    return 0;
}

[ 50%] Building C object CMakeFiles/untitled.dir/main.c.o
[100%] Linking C executable untitled
Undefined symbols for architecture x86_64:
  "_maxf", referenced from:
      _main in main.c.o
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
make[3]: *** [untitled] Error 1
make[2]: *** [CMakeFiles/untitled.dir/all] Error 2
make[1]: *** [CMakeFiles/untitled.dir/rule] Error 2
make: *** [untitled] Error 2

对导致此问题的原因有任何想法吗?

问题是编译器没有看到函数:maxf() 在代码调用该函数的地方。

这是由于 inline 关键字。

建议从函数签名中删除该关键字并插入包含该关键字的原型,类似于:

inline double maxf( double a, double b);

double maxf( double a, double b)
{
    if (a>b)
        return a;
    return b;
} 

您在该程序上遇到 linker 错误的原因是您的程序使用了数学库中的函数 maxf,而您没有 link 与 -lm。这只发生在某些优化级别。

请注意,编译器可能会决定既不内联也不调用 maxf,因为它可以看到变量 max 未被使用,因此实际上不需要向其保存值。如果编译器知道 maxf 没有可见的 side-effects(并且它知道因为 maxf 是标准库函数),它可以因此消除对 max 的赋值和因此调用 maxf。事实上,编译器甚至可能知道 maxf 的语义是什么,并通过在编译时进行预计算来避免调用。

为了避免在基准测试中出现这种情况,您应该执行以下两项操作:

  • 更改 maxf 的名称,使其不与标准库函数名称冲突。 (下面我假设您将其重命名为 myMax.
  • 将变量 max 声明为 volatile 或以某种 non-predictable 方式使用它,以便存储发生。

您还应该修复函数的原型,因为它是用 double 参数调用的。

现在,让我们假设您已经相应地修正了您的基准测试,并且您编译了它。在某些优化级别,您仍然会收到 linker 错误,因为如果没有优化(或者在 Clang 的情况下,优化级别小于 2),则不会执行内联。 (在 GCC 中 -O1 级,该函数仅被内联,因为在翻译单元中只有一次调用。如果它在两个地方被调用,您需要优化级别 2 来触发内联,就像 Clang 一样。)

但很明显有 maxf 的定义(或者,在更正的代码中,myMax 或类似的定义)。那么为什么编译器不能使用它呢?

答案与 inline 的实际语义有关,这可能有助于回答您的第一个问题。众所周知(并且可以在上面的冗长讨论中看到),编译器对内联函数的决定或多或少独立于程序员在 inline 说明符中的建议。 (将此与编译器已忽略很长时间的 now-deprecated register 说明符进行比较。)

但是inline确实有重要的意义。虽然编译器可以根据自己的启发式方法、代码分析和优化设置确定内联函数是否是个好主意,但它无法知道您在其他翻译单元中使用该函数的意图是什么link已创建可执行文件。

也许在其他一些翻译单元中,函数myMax被称为外部函数。在这种情况下,编译器将需要包含 myMax 的编译,无论它是否选择内联该文件中的所有使用。

另一方面,myMax 有可能在它出现的每个翻译单元中内联;换句话说,所有翻译单元都包含 myMax 的定义。但这会导致一个问题,因为当翻译单元 link 在一起时,这些不同的定义会发生冲突,从而产生不同的 linker 错误。 (重复的名称定义。)您可以通过声明函数 static 来解决这个问题,但在那种情况下,您可能会发现可执行文件中的各个模块都有自己的函数静态定义,因为编译器选择不这样做将它内联在那些模块中。这可能会显着增加可执行文件的大小。

这就是 inline 声明的用武之地。函数的 inline 声明意味着 "this is the definition to use in cases where this function is being inlined"。如果编译器发现函数声明为 inline 而未显式声明 extern,它不会发出函数的定义 ,即使它选择不内联它 。它依赖于在某些外部翻译单元中定义的函数。

因此您可以安全地将 inline 声明放入 header 文件中。这将确保该函数的所有内联使用都使用相同的定义,而不会在 linker 中引起任何重名问题。但是您仍然需要确保该函数恰好在一个翻译单元中有定义。 (这有点类似于程序中不同翻译单元声明全局变量的相关问题。全局变量必须在所有翻译单元中声明一致,但只在一个翻译单元中定义。)

inline 函数的情况下,您指出函数的定义绝对应该使用 extern 声明进行编译。如果您在 header 文件中将 myMax 声明为 inline,则可以通过将以下内容放入 exactly one 实现文件中来满足此要求:

#include "myMax.h"
extern double myMax(double a, double b);

请注意,您不需要定义——将使用 header 中的定义——但您确实需要获取原型正确。