【问题标题】:forward declaration using extern (in context of C/C++)使用 extern 的前向声明(在 C/C++ 的上下文中)
【发布时间】:2017-02-11 02:15:47
【问题描述】:

我很困惑是否使用 extern 在 C 中进行函数的前向声明。场景是每个函数都在单独的 .c/.cpp 文件中。我从这个问题中了解到 - External linkage of const in C ,如果所有都是 .c 文件,我不应该使用 extern 进行前向声明,无论函数是否定义在同一个文件中。

但是我想更多地了解何时显式使用 extern 进行前向声明(我认为当前向声明的函数是用与调用不同的语言定义的时,需要 extern),以及任何需要注意的事项知道。

【问题讨论】:

  • 函数默认为extern,你可以在前向声明中使用或省略这个关键字,含义不变。
  • @n.m.使用 extern,编译器会忽略签名,我的意思是类型检查没有完成。所以,不需要的时候不要使用。
  • “编译器忽略签名”错误。谁告诉你的?
  • @n.m.对不起,我误会了……
  • “编译器忽略签名”(感知)问题发生在您在 c 文件中使用 extern 进行前向声明时。编译器实际上正在按照您的要求进行操作,但是您的 extern 声明可能与另一个文件中的实际函数声明不匹配。编译器不检查(链接器也不检查)。这就是为什么我们将前向声明放在头文件中,而不是将它们随机分布在 c 文件中。也就是说:c 文件中的extern 是否修改了声明,或者它是默认值?,为什么人们一直试图这样做!

标签: c++ c extern forward-declaration linkage


【解决方案1】:

我认为,当前向声明的函数是用与调用语言不同的语言定义时,extern 将是必需的。

我应该警告您,您的问题措辞不当。我很确定您对何时使用感兴趣

extern "C" int foo(int);

在 C++ 中,但我不确定,我希望我不会浪费我的时间。

让我们区分编译器和链接器。我省略了一些细节,但没有任何影响你问题的答案。

编译器使用前向声明。在声明函数时,您向编译器提供有关如何使用它的信息。你声明函数F,当编译器遇到正在使用的F时,它就知道要做什么了。 (在 K&R 时代,在没有声明的情况下,编译器使用默认值,有时会导致有趣的结果。所以现在它们在 C 和 C++ 中都是强制性的。)

通常一个函数的前向声明是一个函数原型:它提供参数类型。在 C 中,这不是绝对必要的。你可以写

int foo();

告诉编译器foo是一个返回int的函数,但不知道它需要什么参数。然后你有责任把这些弄好,因为编译器无法检查。

extern 声明——无论是函数还是变量——通知编译器符号及其类型,并表示定义将由另一个模块提供。编译器留下一个占位符供链接器稍后填充。

例如,如果您查看 getopt(3) 手册页,您会看到 optarg 被声明为 extern;它在 C 运行时库中定义,但您可以使用它,因为它已被声明。

现在我们来看看链接器,以及 C++ 的 extern "C"

对于链接器,模块公开符号。未声明static 的全局变量和函数具有外部链接,这意味着它们可以被其他模块使用。

在 C 中,函数名和外部符号之间存在 1:1 的对应关系。名字就是符号。例如getopt(3)在C标准库libc中有一个同名符号:

$ nm -g $(find /usr/lib/ -name libc.a) 2>/dev/null | grep 'T getopt' 
0000000000001620 T getopt
0000000000000000 T getopt_long
0000000000000040 T getopt_long_only

在 C++ 中,名称不是符号。可以重载 C++ 函数;相同的名称可以代表采用不同参数的不同函数。编译器构造一个对参数类型进行编码的符号。比较:

$ echo 'int foo(int foo) { return foo * foo; }' > a.c && cc  -c a.c && nm a.o
0000000000000000 T foo
$ echo 'int foo(int foo) { return foo * foo; }' > a.C && c++ -c a.C && nm a.o
0000000000000000 T _Z3fooi

符号名称的编码通常称为name manglingnm(1) 具有去杂化功能:

$ echo 'int foo(int foo) { return foo * foo; }' > a.C && c++ -c a.C && nm -C a.o
0000000000000000 T foo(int)

请注意,参数类型出现在名称旁边。

在 C++ 中,extern "C" 将函数声明或定义为 C 函数。这是一个定义:

$ echo 'extern "C" int foo(int foo) { return foo * foo; }' > a.C && c++ -c a.C && nm a.o
0000000000000000 T foo
  • 在声明中,它告诉编译器该函数在别处定义,并使用 C 符号。

  • 在定义中,它告诉编译器发出一个 C 符号,即不要破坏名称,以便其他 C 模块可以使用该函数。

我希望这能回答你的问题。

【讨论】:

  • 感谢詹姆斯写出如此详尽的答案。我正在编写一些嵌入式代码并从程序集中调用了一个 c++ 函数,这很有帮助!
【解决方案2】:

在 C 语言中,extern inline 之类的东西具有特殊含义,其中显式的 extern 会有所不同。

除此之外,在函数声明中明确指定 extern 没有任何意义,因为 C 和 C++ 中的函数始终具有默认的外部链接。

“不同语言”的问题可能是关于extern "C"之类的东西,但这与普通的extern完全不同。

【讨论】:

  • 谢谢,这让我很困惑。我将 extern 函数声明与 extern "C" 用法混合在一起,并想知道 extern 何时对函数声明很重要。
  • 块范围内的函数声明是否有所作为:即void f() { extern void g(); }
【解决方案3】:

函数默认有外部链接,这意味着

extern int foo(void);

int foo(void);

完全一样的意思。对于所有函数声明都是如此,除了那些以某种方式涉及static的函数声明。

特别是,extern 的存在与否对类型检查没有影响,并且函数是否以不同的语言定义是无关紧要的。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-03-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-22
    相关资源
    最近更新 更多