9

以下是 src1.c 的内容:

#include <stdio.h>
extern int w;
//int go(char); // no need to declare here. WHY????
  main(){
    char a='f';
    go(a);
    printf("%d\n", w);
}

这是 src2.c 的内容:

#include <stdio.h>
int w = 99;
int go(char t){
   printf("%c\n%d\n",t,sizeof(t));
}

为什么Linux 编译后不需要go在文件中声明函数?src1.c

 cc src1.c src2.c; 

编译器是否将文件中的go函数定义放在src2.c主函数代码之上,这样就不需要声明了?

在我这样做:

#include <stdio.h>
int go(char); // need to declare here, because if not, arguments of go will be promoted to intS and they would conflict with char parameters defined in go. Error is droped!
  main(){
    char a='f';
    go(a);
} 
  int go(char t){
   printf("%c\n%d\n",t,sizeof(t));
}

所以每个人都说,在没有原型的情况下可以传递任何数量和类型的参数是错误的。在这种情况下,它们被提升为ints,但必须与定义中指定的一致。


我做了一些测试,发现即使编译没有错误,它也不能正常工作。

src1:

#include <stdio.h>
int go(int t){
    printf("%d\n%d\n",t,sizeof(t));
}

sr2.c:

#include <stdio.h>
int go(int); //if I omit this prototype, program outputs 1 which is far from correct answer :)
main(){ 
    double b=33453.834;
    go(b);
}

所以最后答案只能是未定义的行为。

谢谢马克西姆·斯库里丁

4

5 回答 5

8

在使用函数之前,确实不是必须要有一个函数原型,但这是 C 早期的一个怪癖。

当没有原型存在时,编译器无法检查传递给函数或由函数返回的实际类型,如果用法和声明不匹配,这可能会非常糟糕。

当编译器在调用时没有看到原型gogo(b);,它假定它具有以下原型int go(<any number of arguments can be there>)。默认参数提升是在函数调用之前对参数执行的。当然,如果go另一个翻译模块中没有函数,你会得到一个链接器错误。

从 c99 标准:

6.5.2.2 函数调用

如果表示被调用函数的表达式的类型不包含原型,则对每个参数执行整数提升,而浮点类型的参数将提升为双精度。这些被称为默认参数提升. 如果参数的数量不等于参数的数量,则行为未定义。如果函数使用包含原型的类型定义,并且原型以省略号 (, ...) 结尾,或者提升后的参数类型与参数类型不兼容,则行为未定义。如果函数定义的类型不包含原型,并且提升后的参数类型与提升后的参数类型不兼容,则行为未定义,但以下情况除外:

— 一种提升类型是有符号整数类型,另一种提升类型是对应的无符号整数类型,并且值可以在两种类型中表示;

— 两种类型都是指向字符类型或 void 的限定或非限定版本的指针。

6.3.1.1 布尔值、字符和整数

2/ 如果一个int可以表示原始类型的所有值,则将该值转换为一个int;否则,它将转换为无符号整数。这些被称为整数促销.48) 所有其他类型都是

更新:

编译器是否将 src2.c 文件中的 go 函数定义放在 main 函数的代码之上,这样就不需要声明了?

不,它什么也没放。上述标准的引用表明不需要原型。go每个文件都是独立编译的,所以在编译 src1.c 时,它对 src2.c 和里面的函数定义一无所知。

所以每个人都说,在没有原型的情况下传递任何数量和类型的参数都是错误的。在这种情况下,它们被提升为 intS,但必须与定义中指定的一致。

这是可能的,并且在系统范围的更改之后我遇到了一些晦涩的错误,这些错误由于某种原因编译得很好而没有任何警告(实际上,这是未定义的行为)。同样,由于每个 *.c 文件都是独立编译的,现在它可以检查go在另一个翻译单元中定义的参数数量及其函数类型。如果该函数采用您提供的更多参数,则“未使用”参数将填充一些随机数据。您应该记住,如果参数不匹配 - 这是未定义的行为,这意味着任何事情都可能发生。

于 2012-10-26T11:10:02.193 回答
0

默认情况下

go() 将是int go(). 即返回 int 并接受任意数量的参数。因此您的实际功能与默认功能类型匹配。

于 2012-10-26T11:07:18.187 回答
0

当您使用这两个源文件制作一个可执行文件时,最终的可执行文件将具有定义,go()因此不需要它。

但是最好在两个源文件中放入declaration一个header file然后包含该头文件

这是头文件 someheader.h

#ifndef __SMH_
#define __SMH_

int go(char);

#endif

现在像这样包含它

#include "someheader.h"

src1.csrc2.c

于 2012-10-26T11:08:26.187 回答
0

首先,函数声明应该放在头文件中。现在回答您的问题:
当您编译两个文件时,然后在链接时,链接器在 src2.o 中找到 go() 的符号定义,从而解析可执行文件中的符号引用,这就是为什么你的程序有效。

您正在尝试使用sizeof()which 是一个编译时运算符,因此它会输出1,因为您在一个角色上起诉它。
您还将整数值 >255 传递给 char 变量,这将导致溢出并且 t 将存储 1789modulo255 。

于 2012-10-26T11:09:54.410 回答
0

当编译器go()在 str1.c 中看到时,它假定该函数是在其他地方定义的。链接器仅在链接时搜索go().

我认为,您正在分别编译这两个文件并将它们链接在一起,这很好。因为在链接时go()存在的定义。

如果您尝试单独编译 str1.c (gcc str1.c 而不是gcc -c str1.c),您将收到有关go()链接器未找到的错误。

更新:

甚至编译器的隐式声明也不符合标准(自 C99 起)

从技术上讲,如果在编译器可以看到它的定义之前调用每个函数,则无论其返回类型如何,它都应该有一个原型。隐式声明 int 返回函数不再有效(在 C89 中有效)并且自 C99(和 C11)以来已被删除。

虽然,大多数编译器仍然只发出警告,而不是错误。但是,如果某些编译器由于不存在函数原型而拒绝编译,那么您就不能抱怨它,因为它不符合标准。

于 2012-10-26T11:10:22.423 回答