【问题标题】:C99 inline why can't we just keep using it?C99 inline 为什么我们不能继续使用它?
【发布时间】:2020-07-05 02:26:37
【问题描述】:

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

plus:使用 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

对导致此问题的原因有什么想法吗?

【问题讨论】:

  • 如果你这样做了,它可能会使你的程序大小成倍增加。想象一下,如果你多次调用一个大函数,它是inlined。
  • ab 应该是 doubles。 double maxf(int a,int b)
  • 编译器没有 无论如何都会生成函数inline。关键字是这样做的邀请“6.7.4.5 将函数设为内联函数建议尽可能快地调用函数。此类建议的有效程度由实现定义。”
  • 内联并不总是使代码更快。
  • 关于:arr[9898]=99999999; 由于数组 arr[] 是一个由 double 值组成的数组,因此文字:99999999 也应该是双精度数。建议:arr[9898]=99999999.0;

标签: c compilation c-preprocessor inline c99


【解决方案1】:

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

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

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

  • 更改maxf 的名称,使其不会与标准库函数名称冲突。 (下面我假设你将它重命名为myMax
  • 要么将变量max 声明为volatile,要么以某种不可预测的方式使用它,以便存储发生。

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

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

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

答案与inline 的实际语义有关,这可能有助于回答您的第一个问题。我们都知道(并且可以在上面冗长的讨论中看到),编译器对内联函数的决定或多或少独立于程序员在inline 说明符中的建议。 (将此与现已弃用的 register 说明符进行比较,编译器长期以来一直忽略该说明符。)

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

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

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

这就是inline 声明的用武之地。函数的inline 声明意味着“这是在此函数被内联的情况下使用的定义”。如果编译器看到一个声明为inline 的函数没有明确声明为extern,它不会发出函数的定义即使它选择不内联它。它依赖于在某个外部翻译单元中定义的函数。

因此,您可以安全地将inline 声明放在头文件中。这将确保函数的所有内联使用都使用相同的定义,而不会导致链接器中的重复名称出现任何问题。但是您仍然需要确保该函数在一个翻译单元中具有定义。 (这有点类似于声明程序中各个翻译单元使用的全局变量的相关问题。全局变量必须在所有翻译单元中一致地声明,但只能在一个中定义。)

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

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

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

【讨论】:

    【解决方案2】:

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

    这是由于 inline 关键字造成的。

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

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

    【讨论】:

    • 但是为什么会这样呢?为什么 inline 会导致这样的问题?
    • 关于:但是为什么会这样呢?为什么 inline 会导致这样的问题? 我不知道确切的答案。这确实是语言律师的问题
    • @Daniel(和 user3629249):也许我的回答会有所帮助。 (如果没有,请告诉我,我会努力改进。)
    猜你喜欢
    • 2020-02-24
    • 2012-11-17
    • 2011-03-06
    • 2022-01-11
    • 2011-02-01
    • 2016-03-12
    • 2022-11-03
    • 2019-04-15
    • 2014-06-09
    相关资源
    最近更新 更多