谁检测到拼写错误的函数名?编译器还是链接器?

Posted

技术标签:

【中文标题】谁检测到拼写错误的函数名?编译器还是链接器?【英文标题】:Who detects misspelled function name? Compiler or Linker? 【发布时间】:2017-01-06 19:42:23 【问题描述】:

根据 C How to Program (Deitel):

像 printf 和 scanf 这样的标准库函数不是 C 编程语言的一部分。例如,编译器在 printf 或 scanf 中找不到拼写错误。当编译器编译 printf 语句时,它只是在目标程序中为“调用”库函数提供空间。但是编译器不知道库函数在哪里——链接器知道。当链接器运行时,它会定位库函数并在目标程序中插入对这些库函数的正确调用。现在目标程序已经完成,可以执行了。因此,链接的程序称为可执行文件。如果函数名称拼写错误,链接器会发现错误,因为它将无法将 C 程序中的名称与库中任何已知函数的名称匹配。

由于头文件的存在,这些陈述让我怀疑。这些文件包含在预处理阶段,在编译之前,据我所知,编译器会使用这些文件。

如果我写print 而不是printf,编译器怎么看不到没有用该名称声明的函数并抛出错误?

如果是书上说的,为什么编译器不看头文件,我还能在头文件中声明函数呢?

【问题讨论】:

编译器可以看到没有使用该名称声明的函数并抛出错误。这取决于编译器选项和使用的 C 版本。 现代编译器至少应该在编译期间给出警告。但是一些编译器会假设缺少函数声明,然后链接器会发现错误。请启用所有编译器警告,因为如果函数确实存在但未声明,则这些假设可能是错误的。 请注意,一些拼写错误最终会调用与预期不同的声明函数。有时编译器可以发现——如果调用中的函数参数与原型不匹配。有时,编译器和链接器都无法发现——如果调用中的函数参数与原型匹配,但这只是错误的函数。 【参考方案1】:

所以如果我写 print 而不是 printf 编译器怎么不能看到没有用该名称声明的函数并抛出错误?

你是对的。如果你在任何函数名中打错字,任何现代编译器都应该抱怨它。例如,gcc 抱怨以下代码:

$ cat test.c 
int main(void)

    unknown();
    return 0;

$ gcc -c -Wall -Wextra -std=c11 -pedantic-errors test.c
test.c: In function ‘main’:
test.c:3:5: error: implicit declaration of function ‘unknown’ [-Wimplicit-function-declaration]
     unknown();
     ^

但是,在 C 语言的 C99 之前的时代,编译器看不到其声明的任何函数,它会假定该函数返回一个 int。因此,如果您在 C99 之前的模式下编译,则不需要编译器发出警告。

幸运的是,自 C99 以来,此 implicit int rule 已从 C 语言中删除,并且需要编译器在现代 C (>= C99) 中对其进行诊断。

但如果你只提供函数的声明或原型:

$ cat test.c 
int unknown(void); /* function prototype */

int main(void)

    unknown();
    return 0;

$ gcc -c -Wall -Wextra -std=c89 -std=c11 test.c
$

(注意:我使用-c 标志只编译而不链接;但是如果你不使用-c,那么编译和链接将在一个步骤中完成,错误仍然来自链接器) .

尽管事实上,您在任何地方都没有unknown() 的定义,但这没有问题。这是因为编译器假定unknown() 已在别处定义,并且仅当链接器要解析符号unknown 时,如果找不到unknown() 的定义,它会报错。

通常,头文件只提供必要的声明或原型(我在上面的示例中直接在文件本身中提供了unknown 的原型——它也可以通过头文件完成)通常不是实际的定义。因此,作者在这个意义上是正确的,链接器是发现错误的那个。

【讨论】:

【参考方案2】:

如果我写 print 而不是 printf,编译器怎么看不到没有用该名称声明的函数并抛出错误?

编译器可以看到指定函数的标识符的范围内没有声明。大多数会在这些情况下发出警告,有些会发出错误,或者可以配置为这样做。

但这与编译器检测到函数不存在不同。是编译器检测到函数名没有被声明。如果您正确拼写函数名称但不包含之前的声明,编译器将表现出相同的行为。

此外,C90 和预标准化 C 允许在没有任何事先声明的情况下调用函数。此类调用不符合 C99 或更高版本,但出于兼容性目的,大多数编译器仍然接受它们(通常带有警告)。

如果是书上说的,为什么编译器不看头文件,我还能在头文件中声明函数呢?

编译器确实看到了它们,并且确实使用了声明。此外,它依赖于原型(如果声明提供了原型)在调用函数时执行适当的参数和返回值转换。此外,如果您使用其参数类型由默认参数提升更改的函数,那么如果在调用点的范围内没有原型,则您对此类函数的调用是不合格的。未定义的行为结果。

【讨论】:

请注意,GCC 5.1.0 及更高版本默认为 C11 模式(而 GCC 4.x 及更低版本默认为 C90 模式),因此现代版本的 GCC(当前为 6.3.0)报告缺少自动声明。 @JonathanLeffler,现实世界中使用的编译器在默认情况下是否警告未声明的函数是相关的,但据我所知,C90(和 C99)与 C11 没有什么不同在将标识符用作函数调用表达式中的操作数之前,要求事先声明标识符以指定函数或函数指针。换句话说,我不认为差异本质上是基于实现的语言标准的版本。 C90 不需要在使用函数之前声明它们。如果编译器遇到一个标识符后跟一个左括号((美国的括号)并且该标识符没有其他信息,那么它被认为是一个返回int的函数(带有不确定的非可变参数列表)。该规则意味着使用 GCC 4.x 或更早版本不会抱怨缺少函数声明而没有额外的选项来使其抱怨。在 GCC 5.x 及更高版本中,投诉会自动触发,无需额外选项。有很多相关的选项。 @JonathanLeffler,你是对的,我的错。 C90 与此处的 C11 具有基本所有相同的要求和定义,但 C90 还包含一个特殊规定,用于隐式声明用作函数调用操作数的其他未声明的标识符。

以上是关于谁检测到拼写错误的函数名?编译器还是链接器?的主要内容,如果未能解决你的问题,请参考以下文章

VB中子程序或函数未定义是啥意思

11.Python迭代器

闭包迭代器

MPLAB X32 编译器和将函数移动到 RAM 会导致链接器错误

链接器错误,使用 g++ 链接到由 gcc 编译的库,未定义对该函数的引用

关于函数名字是存放在那里???