【问题标题】:What's is the idea behind C99 inline?C99 inline 背后的想法是什么?
【发布时间】:2014-06-09 23:30:31
【问题描述】:

我对 C99 中的 inline 感到困惑。

这就是我想要的:

  1. 我希望我的函数在任何地方都能内联,而不仅仅局限于一个翻译单元(或一个编译单元,.c 文件)。
  2. 我希望函数的地址一致。如果我将函数的地址保存在函数指针中,我希望函数可以从指针调用,并且我不希望在不同的翻译单元中重复相同的函数(基本上,我的意思是没有static inline)。李>

C++ inline 正是这样做的。

但是(如果我错了,请纠正我)在 C99 中没有办法得到这种行为。

我可以使用static inline,但它会导致重复(不同翻译单元中相同函数的地址不一样)。我不想要这种重复。

所以,这是我的问题:

  1. C99 中 inline 背后的想法是什么?
  2. 与 C++ 的方法相比,这种设计有什么好处?

参考资料:

  1. 这是一个高度评价 C99 inline 的链接,但我不明白为什么。这种“仅在一个编译单元中”的限制真的那么好吗?
    http://gustedt.wordpress.com/2010/11/29/myth-and-reality-about-inline-in-c99/
  2. 这是 C99 inline 的基本原理。我读过,但没看懂。
    Is "inline" without "static" or "extern" ever useful in C99?
  3. 一篇不错的帖子,提供了使用inline函数的策略。
    http://www.greenend.org.uk/rjk/tech/inline.html

答案总结

如何在 C99 中获得 C++ inline 行为(是的,我们可以)

head.h

#ifndef __HEAD_H__
#define __HEAD_H__

inline int my_max(int x, int y) {
    return (x>y) ? (x) : (y);
}

void call_and_print_addr();

#endif

src.c

#include "head.h"
#include <stdio.h>

// This is necessary! And it should occurs and only occurs in one [.c] file
extern inline int my_max(int x, int y); 

void call_and_print_addr() {
    printf("%d %u\n", my_max(10, 100), (unsigned int)my_max);
}

main.c

#include <stdio.h>
#include "head.h"

int main() {
    printf("%d %u\n", my_max(10, 100), (unsigned int)my_max);
    call_and_print_addr();

    return 0;
}

编译:gcc -O3 main.c src.c -std=c99
检查程序集:gcc -O3 -S main.c src.c -std=c99,您会发现my_maxcall_and_print_addr()main() 中都内联。

其实这和 ref 1 和 ref 3 给出的指令完全一样。那我怎么了?

我用了一个太旧版本的GCC(3.4.5)进行实验,它给了我“my_max的多重定义”错误信息,这才是我如此的真正原因使困惑。耻辱。

C99 和 C++ 的区别inline

其实你可以把上面的例子编译成g++:g++ main.c src.c

extern inline int my_max(int x, int y);

在 C++ 中是多余的,但在 C99 中是必需的。

那么它在 C99 中的作用是什么?

再次,使用gcc -O3 -S main.c src.c -std=c99,您会在src.s 中找到类似的内容:

_my_max:
    movl    4(%esp), %eax
    movl    8(%esp), %edx
    cmpl    %eax, %edx
    cmovge  %edx, %eax
    ret
    .section .rdata,"dr"

如果将extern inline int my_max(int x, int y); 剪切并粘贴到main.c 中,您会在main.s 中找到这些汇编代码。

因此,通过extern inline,您告诉编译器真正的函数my_max()(您可以通过其地址调用它)将在哪里定义和编译。

现在回顾 C++,我们无法指定它。我们永远不会知道my_max() 会在哪里,这就是@Potatoswatter 的“模糊联系”。

正如@Adriano 所说,大多数时候,我们并不关心这个细节,但 C99 确实消除了歧义。

【问题讨论】:

  • 你不能“到处内联”,例如,如果函数递归,或者正如你提到的,它是通过指针调用的,会发生什么?
  • 这个问题可以通过删除乱七八糟的部分来改善。
  • “复制真的让我很不舒服”。那你为什么要使用内联?向您的编译器提出这样的建议,您可以免费(并且乐意)生成大量重复的代码......
  • @Simon 对不起,我学习英语作为第二语言。我没有试图生气,但也许不知何故它看起来很冒犯。还有 Oliver Matthews,感谢您编辑这篇文章。
  • @Alex “不可能在任何地方都内联函数同时拥有唯一的地址。”。 C++ 标准明确要求inline 函数具有唯一地址,并且实现没有问题。 (函数不是内联的,函数调用是;函数调用,内联或其他,没有地址,但函数有)。

标签: c c99 extern inline-functions


【解决方案1】:

要获得类似 C++ 的行为,您需要为每个具有潜在内联调用的 TU 提供 inline 定义,并为一个 TU 提供外部可见的定义。这正是 C 标准的相关部分(函数说明符)中的示例 1 所说明的内容。 (在该示例中,外部可见性通过随后声明函数 extern 追溯应用于 inline 定义:此声明可以在 .c 文件中的 .h 文件中的定义之后完成,这变成了通常的用法在它的头上。)

如果实际上可以在任何地方完成内联,您就不需要extern 函数。但是,在递归和引用函数地址等上下文中使用非内联调用。从某种意义上说,您可以通过省略extern 部分来获得“始终内联”的语义,但是对于任何简单的函数调用,这可能会任意失败,因为标准不要求内联调用只是因为没有其他选择。 (这是linked question的主题。)

C++ 用“模糊链接”的实现概念来处理这个问题;这在标准中没有指定,但在编译器内部它是非常真实且棘手的。 C 编译器应该比 C++ 更容易编写;我相信这是语言之间存在差异的原因。

【讨论】:

  • 不,您需要为程序提供一个 extern 声明,而不是每个 TU 中的声明。
  • @n.m.谢谢,我误解了这一点。我确实认为这样的声明是很好的说明性风格。
  • 如果您将多个 extern 声明放在同一个 inline 函数的不同编译单元中,您可能会收到链接器错误(重复符号)。
  • @Potatoswatter:如果函数在编译单元中定义为inline,则不会。在同一编译单元中具有 inline 定义的函数的 extern 声明意味着“在此编译单元中编译函数并使其作为符号在其他编译单元外部可见”
  • @ChrisDodd 啊,现在我明白了,内联定义之后的 extern 声明会追溯导致前面的定义被转发到链接器。抱歉,我正在从头开始处理 C99 规范,因为我不是真正的 C 程序员:P。
【解决方案2】:

我希望我的函数在任何地方都能内联,而不仅仅局限于一个翻译单元(或一个编译单元,一个 [.c] 文件)。

使用 inline,您可以礼貌地要求编译器内联您的函数(如果它有时间和心情)。它与一个编译单元无关,充其量它甚至可以在每个调用站点中内联,并且它不会在任何地方有一个主体(并且它的代码将在任何地方重复)。它的目的是内联,速度有利于大小。

我希望函数的地址一致。如果我将函数的地址保存在函数指针中,我希望函数可以从指针调用,并且我不希望在不同的翻译单元中重复相同的函数。 (基本上,我的意思是没有“静态内联”)

再一次,你不能。如果函数是内联的,则没有任何函数指针指向它。当然,编译器需要一个编译单元来存放函数(因为,是的,您可能需要一个函数指针,或者有时它可能决定不在特定调用站点内联该函数)。

根据您的描述,static inline 似乎不错。 IMO 不是,每个编译单元中的函数体(使用时,见上段)会导致代码重复(以及函数指针比较问题,因为每个编译单元都有自己的 version功能)。正是在这里,C99 做了一件相当不错的事情:声明了一个放置函数体的地方(当需要时)。编译器不会为你做这件事(如果你关心它的话),并且没有任何东西需要实现。

C99 中内联的想法是什么?

选择一个好的东西(内联函数),但要消除歧义(每个 C++ 编译器都做了自己的工作来确定函数体的位置)。

与 C++ 的方法相比,这种设计有什么好处?

老实说,我看不出这么大的问题(即使您链接的文章也对这个好处非常模糊)。在现代编译器中,您不会看到任何问题,并且您永远不会关心它。为什么C做的很好? IMO 因为它消除了歧义,即使 - 坦率地说 - 我更希望我的编译器在我不关心它的时候为我做这件事(我想是 99.999%)。

这就是说,但我可能错了,C 和 C++ 有不同的目标。如果您使用的是 C(不是没有类和很少 C++ 功能的 C++),那么您可能想要解决这类细节,因为它们在您的上下文中很重要,因此 C 和 C++ 不得不对此有所分歧。 没有更好的设计:只是针对不同的受众做出不同的决定。

【讨论】:

  • 礼貌地要求你的编译器内联你的函数(如果它有时间和心情的话)我再也不能以同样的方式思考我的编译器了。
  • “如果函数是内联的,那么就没有任何函数指针指向它”。函数不是内联的,而是函数调用。当然,没有指向函数调用的指针,不管是内联的还是其他的。 inline 函数有一个地址。
  • @n.m.一个函数调用是内联的(带有函数体),即使不是形式上正确的“内联函数”也被广泛用于表达这个概念。内联函数在其 extern 声明中有一个地址(在 C99 中),但它不是内联的地址(这有关系吗?可能只有当您注入代码时)。
  • 非常感谢您的帖子!
猜你喜欢
  • 2021-12-22
  • 2012-07-03
  • 1970-01-01
  • 2015-01-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多