关于C函数原型和编译的问题

时间:2013-04-18 13:51:35

标签: c gcc compiler-construction

使用以下代码:

int main(){
    printf("%f\n",multiply(2));
    return 0;
}

float multiply(float n){
    return n * 2;
}

当我尝试编译时,我得到一个警告:“'%f'期望'double',但是参数的类型为'int'”和两个错误:“'multiply'的冲突类型”,“之前的隐式声明'乘以'在这里。'

问题1 :我猜这是因为,鉴于编译器在第一次碰到它时不知道函数'multiply',他将发明一个原型,并发明原型总是假设'int'都被返回并作为参数。因此,本发明的原型将是“int multiply(int)”,因此也就是错误。这是对的吗?

现在,以前的代码甚至都不会编译。但是,如果我打破两个文件中的代码:

#file1.c
 int main(){
    printf("%f\n",multiply(2));
    return 0;
 }

#file2.c
float multiply(float n){
    return n * 2;
}

并执行“gcc file1.c file2.c -o file”它仍然会发出一个警告(printf期望是double但是正在获取int)但是错误将不再出现并且它将被编译。

问题2 :为什么我将代码分解为2个文件进行编译?

问题3 :一旦我运行上面的程序(版本分成2个文件),结果就是在屏幕上打印了0.0000。怎么会?我猜测编译器再次发明了一个与该功能不匹配的原型,但为什么会打印0?如果我将printf(“%f”)更改为printf(“%d”),它会打印1.再次,对幕后发生的事情的任何解释?

提前多多感谢。

4 个答案:

答案 0 :(得分:4)

  

因此,本发明的原型将是“int multiply(int)”,因此也就是错误。这是对的吗?

绝对。这样做是为了向后兼容缺少函数原型的前ANSI C,并且没有类型声明的所有内容都是隐式int。编译器编译你的main,创建int multiply(int)的隐式定义,但是当它找到真正的定义时,它会发现谎言并告诉你它。

  

为什么我将代码分成2个文件进行编译?

编译器永远不会发现关于原型的谎言,因为它一次编译一个文件:它假定multiply接受int,并在int中返回main 1}},并且在multiply.c中没有发现任何矛盾。但是,运行此程序会产生未定义的行为。

  

一旦我运行上面的程序(版本分成2个文件),结果就是屏幕上打印了0.0000。

这是上述未定义行为的结果。该程序将编译和链接,但由于编译器认为multiply采用int,它永远不会将2转换为2.0F,而multiply将永远不会找出。同样,在int函数中将float 重新解释为multiply加倍计算的错误值将再次视为int。< / p>

答案 1 :(得分:1)

问题1:是的,你是对的。如果没有函数原型,则默认类型为int

问题2:当您将此代码编译为一个文件时,编译器会看到已经有一个名为multiply的函数,它的类型与假定的不同({{1}而不是double)。因此编译不起作用。

当您将其分成两个文件时,编译器会生成两个int个文件。在第一个中,它假设.o函数将在其他文件中。然后链接器将两个文件链接到一个二进制文件中,并根据名称multiply()在第一个multiply文件中的编译器所假定的float multiply()的位置插入int multiply()的调用。 / p>

问题3:如果您将.o int视为2,您将得到一个非常小的数字(~1 / 2 ^ 25),所以在那之后你将它乘以2并且对于格式float它仍然太小。这就是您看到%f

的原因

答案 2 :(得分:1)

未指定的函数的返回类型为int(这就是您收到警告的原因,编译器认为它返回一个整数)和未知数量的未指定参数。

如果你在多个文件中分解你的项目,只需在从其他文件调用函数之前声明一个函数原型,一切都会正常工作。

答案 3 :(得分:1)

<强>问题1:

  

因此,本发明的原型将是&#34; int multiply(int)&#34;,因此   错误。这是对的吗?

不是exactelly是因为它取决于你的Cx(C89,C90,C99,......)

对于函数返回值,在C99之前明确规定如果没有可见的函数声明,则转换器提供一个。这些隐式声明默认为返回类型int

来自C Standard的理由(6.2.5第506页)

  

在C90之前,没有功能原型。开发人员期望   能够互换签名和未签名的论点   相同整数类型的版本。必须施展论据,如果   函数定义中的参数类型具有不同的符号,   被视为与C的易于进行的类型检查系统和a   很少侵入性的。原型的引入并没有完全发挥作用   远离论证的可互换性问题。省略号   符号规定没有人知道1590省略号   提供没有信息的预期参数类型。同样,为   函数返回值,在C99之前明确指定了   如果没有看到功能声明,则翻译提供一个。   这些隐式声明默认为返回类型的int。如果   实际的函数碰巧返回类型unsigned int,这样的   默认声明可能返回了意外的结果。许多   开发人员对功能声明有一种随意的态度。该   我们其他人不得不忍受委员会的后果   想要打破他们写的所有源代码。该   函数返回值的可互换性现在是一个有争议的问题,   因为C99要求在...处显示函数声明   调用点(不再提供默认声明)

问题2:

  

为什么我将代码分成2个文件进行编译?

它将进行编译,它将被视为在第一个问题中表示为相同的