下面的代码示例来自K. N. King的 C Programming. A Modern Approach。它不能正常工作:输出是1
而不是9
。作者说这是默认参数提升引起的问题:
在调用square时,编译器还没有看到原型,所以它不知道square需要一个int
类型的参数。相反,编译器对x
执行默认参数提升,没有任何效果。因为它需要一个int
类型的参数,但却得到了一个double
值,
但他并没有给予很具体的解释,我想知道这个问题到底是怎么发生的?
#include <stdio.h>
int main(void)
{
double x = 3.0;
printf("Square: %d\n", square(x));
return 0;
}
int square(int n)
{
return n * n;
}
字符串
我运行了程序,它不工作,但我想知道到底是什么问题在运行时导致它。
2条答案
按热度按时间sd2nnvve1#
标题第一:
C中的默认参数提升导致错误....
不,错误不是由“default argument promotion”引起的。
该错误是由于编译器在调用
square
时缺乏对它的了解而导致的。当时编译器不知道函数square
所期望的参数类型。今天这样的代码是非法的(未定义的行为)。
然而,在过去(不那么好)的日子里,它被接受(一些编译器仍然这样做)。规则是编译器应该对函数调用中使用的参数进行“默认参数提升”。例如,
float
将在调用之前转换为double
,而short
将转换为int
,等等。对于发布的代码,应该应用“default argument promotion”,但它没有效果,因为
x
已经是double
。编译器将只生成机器码来传递double
,即x
。不会发生转换。当函数
square
读取n
时,问题就来了。函数将假设它被传递了一个int
,机器码将根据“获取int
参数的规则"读取值。这是这部分问题的关键
我想知道这个问题到底是怎么发生的。
它是关于“调用约定”的。一组规则描述(除其他事项外)如何将参数传递给函数。这些约定不是C标准的一部分。调用约定被保留为“实现的事情”。不同的系统可能会以不同的方式完成它。例如Windows和Linux使用不同的调用约定。
因此,要找出“问题究竟是如何发生的?”你需要研究特定系统的调用约定。
以System V ABI为例。这里第一个浮点参数使用XMM 0传递,第一个整数参数使用RDI传递。因此,对于您的代码,这意味着调用方将在XMM 0中存储值
3.0
,而被调用方将从RDI读取,因为它需要一个整数。显然,这是行不通的。在您的系统上,可能会发生与上述不同的情况,但常见的问题是相同的:调用方和被调用方期望的是不同的东西,
double
和int
。即使某些调用约定指定了相同的“HW资源”来传递
double
和int
,也会有其他低级问题。double
和int
的大小可能不同。浮点值3.0
的二进制编码与整数值3
的二进制编码不同。5lhxktic2#
首先,问题中的代码不是有效的C99,更不用说C11,C18或C23了。在C99和更高版本中,函数必须在使用前声明(main()除外)。只有C90允许函数在没有声明的情况下使用-但这不是一个标准,目前已经过时。您必须在调用之前声明函数。
也就是说,给定代码片段中的另一个问题正是引用中提到的,当需要
int
时,您发送了double
值。由于double
和int
是不兼容的类型,因此该程序调用了未定义的行为。输出无法以任何方式调整。引用C标准,章节6.5.2.2/P6
如果你需要知道是什么导致了程序的特定执行的 * 确切 * 输出,你需要参考硬件和编译器(以及运行环境,如果适用)的文档。