6

我正在尝试用内联函数替换一些宏子例程,以便编译器可以优化它们,因此调试器可以进入它们等等。如果我将它们定义为普通函数,它可以工作:

void do_something(void)
{
  blah;
}

void main(void)
{
  do_something();
}

但如果我将它们定义为内联:

inline void do_something(void)
{
  blah;
}

void main(void)
{
  do_something();
}

它说“错误:未定义的外部”。这意味着什么?在黑暗中捅了一刀,我试过了

static inline void do_something(void)
{
  blah;
}

void main(void)
{
  do_something();
}

并且没有更多的错误。函数定义和函数调用在同一个 .c 文件中。

有人可以解释为什么一个有效而另一个无效吗?

(第二个相关问题:如果我想在多个 .c 文件中使用内联函数,我应该把它们放在哪里?)

4

3 回答 3

10

首先,编译器并不总是内联标记为inline;的函数。例如,如果您关闭所有优化,它可能不会内联它们。

定义内联函数时

inline void do_something(void)
{
  blah
}

并使用该函数,即使在同一个文件中,对该函数的调用也由链接器而不是编译器解析,因为它是隐含的“外部”。但是这个定义本身并不能提供函数的外部定义。

如果您包含一个没有inline

void do_something(void);

在可以看到inline 定义的 C 文件中,编译器将提供函数的外部定义,错误应该会消失。

起作用的原因static inline是它使函数仅在该编译单元内可见,因此允许编译器解析对该函数的调用(并对其进行优化)并在该编译单元内发出该函数的代码。然后链接器不必解析它,因此不需要外部定义。

放置内联函数的最佳位置是在头文件中,并声明它们static inline。这消除了对外部定义的任何需求,因此它解决了链接器问题。但是,这会导致编译器在每个使用它的编译单元中为函数发出代码,因此可能导致代码膨胀。但是由于该函数是内联的,它可能还是很小的,所以这通常不是问题。

另一种选择是将其定义extern inline在标题中,并在一个 C 文件中提供和extern 声明,而不使用inline修饰符。

gcc 手册是这样解释的:

通过声明内联函数,您可以指示 GCC 更快地调用该函数。GCC 可以实现这一点的一种方法是将该函数的代码集成到其调用者的代码中。这通过消除函数调用开销使执行速度更快;此外,如果任何实际参数值是常量,则它们的已知值可能允许在编译时进行简化,因此不需要包含所有内联函数的代码。对代码大小的影响难以预测;根据具体情况,使用函数内联的目标代码可能更大或更小。您还可以指示 GCC 尝试使用选项将所有“足够简单”的函数集成到它们的调用者中-finline-functions

GCC 实现了声明函数内联的三种不同语义。一个在所有内联声明中都存在属性时可用,-std=gnu89或者-fgnu89-inline当属性出现时,另一个在、或 (不带)时可用,第三个在编译 C++ 时使用。gnu_inline-std=c99-std=c1x-std=gnu99-std=gnu1x-fgnu89-inline

要内联声明函数,请inline在其声明中使用关键字,如下所示:

 static inline int
 inc (int *a)
 {
   return (*a)++;
 }

如果您正在编写要包含在 ISO C90 程序中的头文件,请编写__inline__而不是inline.

这三种内联类型在两种重要情况下的行为相似:当inline关键字用于static函数时,如上面的示例,以及函数首次声明时不使用 inline关键字,然后用 定义inline,如下所示:

 extern int inc (int *a);
 inline int
 inc (int *a)
 {
   return (*a)++;
 }

在这两种常见情况下,程序的行为就像您没有使用inline关键字一样,除了它的速度。

当一个函数同时是 inline 和static时,如果对该函数的所有调用都集成到调用者中,并且从不使用函数的地址,则永远不会引用该函数自己的汇编代码。在这种情况下,GCC 不会实际输出函数的汇编代码,除非您指定选项 -fkeep-inline-functions。一些调用由于各种原因不能被集成(特别是函数定义之前的调用不能被集成,定义中的递归调用也不能被集成)。如果有一个非集成调用,那么该函数像往常一样被编译为汇编代码。如果程序引用它的地址,该函数也必须像往常一样编译,因为它不能被内联。

请注意,函数定义中的某些用法可能使其不适合内联替换。这些用法包​​括:可变参数的使用、alloca 的使用、可变大小数据类型的使用、计算的 goto 的使用、非本地 goto 的使用和嵌套函数。-Winline当标记的函数inline不能被替换时,使用会发出警告,并给出失败的原因。

根据 ISO C++ 的要求,GCC 认为在类的主体中定义的成员函数被标记为内联,即使它们没有使用inline关键字显式声明。您可以使用 覆盖它-fno-default-inline

GCC 在不优化时不会内联任何函数,除非您指定always_inline函数的属性,如下所示:

 /* Prototype.  */
 inline void foo (const char) __attribute__((always_inline));

本节的其余部分特定于 GNU C90 内联。

当内联函数不是static时,编译器必须假设可能有来自其他源文件的调用;由于一个全局符号在任何程序中只能定义一次,因此不能在其他源文件中定义该函数,因此不能集成其中的调用。因此,非static内联函数总是以通常的方式自行编译。

如果在函数定义中同时指定inlineextern,则该定义仅用于内联。在任何情况下,该函数都不会自行编译,即使您明确引用其地址也是如此。这样的地址变成了外部引用,就好像您只声明了函数,而没有定义它。

这种组合inlineextern几乎具有宏的效果。使用它的方法是将函数定义放在带有这些关键字的头文件中,然后将定义的另一个副本(缺少inlineand extern)放在库文件中。头文件中的定义将导致对函数的大多数调用被内联。如果该函数的任何用途仍然存在,它们将引用库中的单个副本。

于 2012-02-22T17:07:59.363 回答
1

对于inline与 C99 一起使用的函数(它们只在语言中出现),您必须在头文件中给出定义

inline void do_something(void)
{
  blah
}

并在一个编译单元(又名.c)中放置某种“实例化”

void do_something(void);

没有inline.

于 2012-02-22T17:02:03.773 回答
0

如果要从多个文件中使用它们,则必须将它们放在头文件中。

对于链接器错误:函数的默认声明意味着它是“extern”,但由于它是内联的,因此链接器可以找到编译器生成的符号存根,因此会出现错误。

于 2012-02-22T16:43:47.110 回答