我有代码:
#include <stdio.h>
int main() {
int a = sum(1, 3);
return 0;
}
int sum(int a, int b, int c) {
printf("%d\n", c);
return a + b + c;
}
我知道我必须先声明函数,然后才可以调用它们,但是我想了解发生了什么。
(由gcc v6.3.0
编译)
我忽略了implicit declaration of function warning
并运行了几次程序,输出如下:
1839551928
-2135227064
41523672
// And more strange numbers
我有2个问题:
1)这些数字是什么意思?
2)函数main
如何知道如何在没有声明的情况下调用函数sum
?
答案 0 :(得分:3)
我将假设您问题中的代码是您实际上正在编译并运行的代码:
int main() {
int a = sum(1, 3);
return 0;
}
int sum(int a, int b, int c) {
printf("%d\n", c);
return a + b + c;
}
对printf
的呼叫无效,因为您没有必需的#include <stdio.h>
。但这不是您要问的,因此我们将忽略它。 该问题已被编辑以添加include指令。
在标准C中,从1999年标准开始,调用没有可见声明的函数(在这种情况下为sum
)是约束违反。这意味着需要进行诊断(但是,合格的编译器如果选择仍然可以成功地编译程序)。除了语法错误之外,约束违规是最接近C的说法,即违法。 (#error
指令除外,该指令必须导致翻译单元被拒绝。)
在C99之前,C有一个“隐式int
”规则,这意味着,如果调用不带可见声明的函数,则会创建一个隐式声明。该声明适用于返回类型为int
且传递的参数为(提升的)参数类型的函数。您的调用sum(1, 3)
将创建一个隐式声明int sum(int, int)
,并生成一个调用,就好像函数是通过这种方式定义的。
由于未采用这种方式定义,因此行为未定义。 (很有可能其中一个参数的值(可能是第三个参数的值)是从任意寄存器或内存位置获取的,但是该标准对调用的实际作用没有任何说明。)
C99(ISO C标准的1999版)删除了隐式int
规则。如果您使用合格的C99或更高版本的编译器来编译代码,则要求编译器为sum(1, 3)
调用诊断错误。为了与旧代码向后兼容,许多编译器将输出非致命警告并生成假定定义与隐式声明匹配的代码。而且许多编译器默认情况下是不合格的,甚至可能不会发出警告。 (顺便说一句,如果您的编译器确实打印了错误或警告消息,则将其包含在问题中将非常有帮助。)
您的程序有错误。合格的C编译器必须至少警告您,并可能拒绝它。如果尽管发出警告仍运行它,则行为是不确定的。
答案 1 :(得分:1)
这是undefined behavior的6.5.2.2 Function calls, paragraph 9 of the C standard:
如果函数的类型与表示被调用函数的表达式所指向的类型(表达式的类型)不兼容,则行为是不确定的。
6.5.2.2 Function calls, paragraph 6下允许没有原型的功能:
如果表示被调用函数的表达式的类型不包含原型,则对每个参数执行整数提升,而将float类型的参数提升为双精度。这些称为默认参数提升。如果参数数量不等于参数数量,则行为是不确定的。 ...
再次注意:如果传递的参数与预期的参数不匹配,则行为未定义。
答案 2 :(得分:1)
在严格符合标准的C语言中,如果您在使用函数之前未声明函数,它将假定该函数的某些默认参数类型,这是基于C类型较弱的C的早期版本,并且仅保留用于向后兼容。一般不应使用它。 我在这里跳过了细节,但在您的情况下,假设sum取2个整数并返回一个整数。
像在此处那样用错误数量的参数调用函数是未定义的行为。调用sum时,编译器会认为它需要两个整数,因此会将两个整数传递给它。但是,当实际调用该函数时,它将尝试再读取一个整数c
。由于您仅传递了2个整数,因此c
的空间包含随机废话,这是您在打印输出时看到的内容。请注意,由于这是未定义的行为,因此不必执行任何操作。例如,它可能已经给定了b和c的值。
显然,此行为令人困惑,您不应该依赖未定义的行为,因此最好使用更严格的编译器设置进行编译,以使该程序无法编译。 (正确的版本会声明sum在main之上。)
答案 3 :(得分:0)
1)由于调用函数“ sum”时未提供参数“ c”的值,因此该函数内部的值未定义。如果在main之前声明了函数,则程序甚至都不会编译,并且会出现“错误:函数调用的参数太少”错误。
2)通常不是。必须在调用之前声明函数,以便编译器可以检查函数签名。在这种情况下,编译器优化为您解决了这个问题。
答案 4 :(得分:0)
我不确定100%C是否完全像这样工作,但是您的函数调用像内存中的堆栈一样工作。调用函数时,参数将放在该堆栈上,因此在进行功能连接时,可以通过在内存中选择较少的x位置来访问它们。所以:
您致电summ(1, 3)
堆栈将具有1,顶部具有3。
执行该功能时,将看到1º参数的最后存储位置(它将恢复1),然后是2º参数之前的存储位置(恢复3),但是,存在3º参数,因此它可以访问该位置在那之前。
这个职位很残酷,不是您本人提出的,每次运行都不同。
希望很清楚。记住栈的工作方式是相反的,因此每次添加内容时,它都会移到上一个内存位置,而不是下一个。