【问题标题】:why no need to add `extern` for external functions?为什么不需要为外部函数添加`extern`?
【发布时间】:2020-08-31 00:39:37
【问题描述】:

下面是我的代码:

//main.c
//I'm not using header file here,I know it is bad practice, it is just for demo purpose.

int main()
{
   func();
   return 0;
}
//test.c

void func()
{
   ...
}

我们可以看到上面的代码可以编译,可以通过链接器链接,但同样的事情不适用于变量:

//main.c 

int main()
{
   sum += 1;
   return 0;
}
//test.c

int sum = 2020;

那么这段代码将无法编译,无法链接,我们必须在main.c中的main函数前添加extern int sum;

但是为什么我们不需要在main.c 中添加extern 为:

//main.c 

extern void func(); //or `void func();` since functions are by default external
// without above line, it still compile

int main()
{
   func();
   return 0;
}

这里是不是有点不一致?

注意:通过说“函数默认是外部的。”,我的理解是:我们可以在不输入 extern 的情况下保存一些按键,所以 void func(); == extern void func();,但是我们还是需要在main.c的main函数前加上void func();,不是吗?

【问题讨论】:

  • “我们可以看到上面的代码编译了”你确定吗?在我看来 func() 没有在 main.c 任何地方声明。
  • 在您的情况下,隐式函数声明 (int func(void)) 与实际函数 (void func(void)) 匹配,因此您确实有 未定义的行为 i>.
  • int func(void) 不在您的代码中,因为它是隐式。这就是隐式的意思。你没有明确声明它,所以编译器为你做了。
  • 正如我所说,它是隐式声明的。编译器会为您创建该声明。
  • @amjad 函数和变量是两个不同的东西,没有理由期望一些任意的“一致性”

标签: c gcc linker


【解决方案1】:

自 C99 以来,这两个程序都不正确,可能会被编译器拒绝。未经事先声明,不得在表达式中使用标识符。

在 C89 中有一条规则,如果您编写类似于函数调用的内容,并且之前未声明函数名称,则编译器会插入函数声明 int f();。对于不带括号的其他标识符的使用没有类似的规则。

即使设置为 C99 或更高版本的模式,某些编译器(取决于编译器标志)也会发出诊断信息,然后无论如何都会执行 C89 行为。

注意:您的程序在 C89 中仍然会导致未定义的行为,因为隐式声明是 int func(); 但函数定义有 void func() 这是不兼容的类型。

【讨论】:

  • 但他没有问。
  • @P__J__:他们没有问什么?问题的主体询问为什么函数标识符不需要声明,即使它是对象标识符。这个答案回答了这个问题。问题的标题措辞不佳,但正文很明显。
【解决方案2】:

编译器不需要知道任何关于函数的信息,就可以生成调用它的代码。在没有原型的情况下,它可能会生成错误代码,但它可以生成一些东西(原则上,至少——标准合规性可能会默认禁止它)。编译器知道平台的调用约定——它知道根据需要将函数参数放入堆栈或寄存器。它知道要写一个链接器以后可以找到并修复的符号,等等。

但是当你写“sum++”时,编译器不知道如何生成代码。它甚至不知道“sum”是什么东西。递增浮点数所需的代码与递增整数所需的代码完全不同,并且可能与递增指针所需的代码不同。编译器不需要知道“sum”在哪里——这是链接器的工作——但它需要知道是什么,才能生成有意义的机器代码。

【讨论】:

  • “它知道根据需要将函数参数放入堆栈或寄存器” - 这是不正确的,例如x64 ABI 指定浮点参数与整数参数使用不同的寄存器,可变参数函数对非可变参数有不同的处理
  • 恕我直言,您错了——至少对于 gcc。而且,老实说,检查只需要两分钟。在生成函数调用时,编译器必须知道参数的类型。函数原型的存在可能会导致这些参数被区别对待,这就是编译器可能生成不正确代码的原因。但它总是可以生成一些代码,只要它知道类型。在许多(可能是大多数)情况下,生成的代码将是正确的。这就是为什么在许多情况下,即使没有函数原型,您也会得到正确的行为。
【解决方案3】:

但是我们不需要为 main.c 中的函数添加 extern 作为 extern void func();或 void func();(因为函数隐含 extern 前缀)并且代码仍然可以编译?

没错。函数默认是外部的。

要使函数特定于本地源文件(翻译单元),您需要为它们指定static

另一方面,变量仅在源文件中可见。如果你想让一些变量在定义它的源文件之外可见,你需要extern

【讨论】:

  • " 函数默认是外部的。",不是说我们不用输入 'extern` 就可以保存一些按键,所以void func(); == extern void func();,但我们还是需要在前面加上void func()主要方法,不是吗?
  • @amjad, 1) 是的,您可以通过省略extern 来节省击键,但使用extern 表示这是故意的。很多人忘记使用static。 2) C 没有对象,因此它没有方法。 3)不清楚最后一点是什么意思。如果你在谈论main,它的正确返回类型是intvoid 不正确。如果你在谈论一般的函数,那么void 是必要的,因为func(... 在语法中已经意味着其他东西
  • @ikegami 我不是在说main方法,我只是说为什么我们不需要在main.c中添加extern void func();
  • @amjad 同样,没有“主要方法”之类的东西。方法是与类关联的函数,而 C 没有类。 /// 因为extern 是默认值。你永远需要在函数上使用extern。但这样做会让你的意图更加清晰。
  • 在文件范围内声明的没有关键字static的变量具有外部链接,就像函数一样
【解决方案4】:

有两个完全不同的主题 - 函数原型和链接。

void foo(void);

提供编译器需要的extern函数原型,用于知道参数的个数和类型以及返回值的类型。函数具有外部链接 - 即可以被其他编译单元访问

static void foo(void);

提供static 函数原型。函数没有外部链接 - 即其他编译单元无法访问它

默认情况下,函数具有外部链接。

对象(全局范围)。

int x; 

定义具有外部链接和类型int 的对象x。 如果您在另一个编译单元中定义另一个 x 对象,链接器将抱怨并发出错误。

extern int x; 

只声明对象x而不定义它。对象x 必须在其他编译单元中定义。

【讨论】:

  • 静音DV-ter。解释你的 DV。
  • (我不是 DVer)“如果您在另一个编译单元中定义另一个 x 对象,链接器将抱怨并发出错误。” - 实际上它是未定义的行为,不需要诊断,并且有一个常见的扩展,即只要 x 中最多一个具有初始化程序,就不会给出诊断,请参阅 C11 附录 J.5.11
  • @M.M 实际上链接器消息不在标准范围内。我知道的所有实现在链接时都会发出重复的符号。
  • 链接器是实现的一部分,并且实现消息(无论是否需要)都包含在标准中。它指定在这种情况下不需要任何消息
  • 另外,“函数没有外部链接 - 即它不能被其他编译单元访问”是不正确的。该函数仍然可以被其他单元访问,只是名称标识符不能
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-08-22
  • 2013-10-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多