C -函数的单定义规则

clj7thdc  于 2022-12-03  发布在  其他
关注(0)|答案(2)|浏览(118)

我是C语言的新手,读到每个函数只能定义一次,但我似乎无法将这与我在控制台中看到的一致。例如,我可以覆盖printf的定义,而不会出现错误或警告:

#include <stdio.h>

extern int printf(const char *__restrict__format, ...) {
    putchar('a');
}

int main() {
    printf("Hello, world!");
    return 0;
}

因此,我试着在标准中查找一个定义规则,并在第155页找到了第6.9(5)节,其中说(着重号是添加的):
一个 external definition 是一个外部声明,它也是一个函数的定义(而不是一个内联定义)或一个对象的定义。如果一个用外部链接表示的标识符被用在一个表达式[...]中,在整个程序的某个地方应该有一个正好是这个标识符的外部定义;否则,不得超过一个。
我对链接的理解是非常不可靠的,所以我不确定这是否是相关的子句,或者“整个程序”的确切含义是什么。但是,如果我把“整个程序”理解为<stdio.h>+我的源文件中的所有内容,那么是否应该禁止我在源文件中重新定义printf,因为它已经在“整个程序”中定义过了(即在程序的stdio位中)?
我很抱歉,如果这个问题是一个傻瓜,我找不到任何现有的答案。

bis0qfac

bis0qfac1#

C标准没有定义如果函数有多个定义会发生什么。
......我是不是应该被禁止......
C标准对你做什么没有管辖权。它规定了如何解释C程序,而不是人类可以如何行为。虽然它的一些规则是用“shall”写的,但这不是对程序员的命令,告诉他们可以做什么或不可以做什么。它是一种修辞手段,用于指定C程序的语义。C 2018 4 2告诉我们它的实际含义:
如果出现在约束或运行时约束之外的“应”或“不应”要求被违反,则行为未定义...
因此,当您提供printf的定义,而标准C程式库提供printf的定义时,C标准并不会指定会发生什麽。在一般的实践中,可能会发生下列几种情况:

  • 连接器会使用您的printf。不会使用程式库中的printf
  • 编译器具有printf的内置知识,并且不管您对printf的定义如何,都使用该知识。
  • 如果您的printf位于单独的源模块中,并且该模块被编译并插入到库中,则程序使用哪个printf取决于向链接器指定库的顺序。

虽然C标准没有定义函数(或一般的外部符号)有多个定义时会发生什么,但链接器通常会这样做。通常,当链接器处理库文件时,其行为是:

  • 检查程式库中的每个模块。如果模块定义的符号已由先前并入的对象模块指涉,但尚未定义,则在连接器正在建置的输出中包含该模块。如果模块未定义任何此类符号,请勿使用它。

因此,对于普通函数,出现在库文件中的多个定义的行为是由链接器定义的,即使它不是由C标准定义的。(但是可能会有一些复杂的情况,假设一个程序使用cossin,并且当链接器发现定义了sincos两者的库模块时,链接器已经包括了定义了cos的模块。因为连接器有sin的未解析指涉,所以它会包含这个程式库模块,这会引入cos的第二个定义,造成多重定义错误)。
尽管链接器的行为可能已经定义好了,但仍然存在编译器内置了标准库函数的问题。考虑this example。在这里,我添加了第二个printf,所以程序具有:

printf("Hello, world!");
printf("Hello, world!\n");

程式输出为“aHello,world.\n”。这表示程式在第一个printf呼叫中使用您的定义,但在第二个printf呼叫中使用标准行为。程式的行为就像是在同一个程式中有两个不同的printf定义。
对于第二次调用,编译器决定,由于printf("Hello, world!\n");打印的字符串没有转换规范,并且以换行符结尾,它可以使用效率更高的puts例程,所以汇编语言使用call puts作为第二个printf。编译器无法对第一个printf执行此操作,因为它没有以换行符结尾,而puts会自动添加换行符。

roqulrg3

roqulrg32#

请注意declarationdefinition。这两个术语完全不同。

  • stdio.h只提供declaration。因此,当你在文件中声明/定义时,只要原型是相似的,就可以这样做。
  • 你可以在你的源文件中自由定义。如果它是可用的,最终的程序将链接到你的而不是库中的。

相关问题