C 中的默认参数升级导致错误,但我不知道确切的原因

Default argument promotions in C cause error,but I don't know exactly why

提问人:ll cool 提问时间:11/4/2023 最后编辑:chqrliell cool 更新时间:11/4/2023 访问量:96

问:

下面的代码示例摘自 K. N. King 的 C 编程。 一种现代的方法。它无法正常工作:输出是 而不是 .笔者说,这是默认参数提升导致的问题:19

在调用 square 时,编译器还没有看到原型,所以它不知道 square 需要 type 的参数。相反,编译器会在 上执行默认参数升级,但不起作用。由于它需要一个类型的参数,但已经给出了一个值,intxintdouble

但是他没有给出一个非常具体的解释,我想知道问题到底是怎么发生的?

#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;
}

我运行了该程序,但它不起作用,但我想知道运行时中究竟是什么问题导致了它。

c types 参数 式转换

评论

2赞 Gene 11/4/2023
在 C 中,函数或函数的原型必须出现在调用之前,才能使参数和返回值转换按您通常期望的方式工作。这不是真的。double 参数按原样传递给期望其参数为整数的函数。double 和 int 表示数字的方式非常不同。因此,该程序具有“未定义的行为”。任何像样的编译器都应该发出至少一个警告。可能有几个。对于 C,“警告”通常意味着“错误”。在你确切地了解你在做什么之前,你应该这样对待他们。
0赞 Vlad from Moscow 11/4/2023
@ll酷 问题在您提供的报价中进行了描述。
0赞 user207421 11/4/2023
K.N. King的第二句话是不正确的。未对 应用“默认参数升级”。它一直是一个.xdouble
0赞 Eric Postpischil 11/4/2023
@user207421:文本说“编译器在 x 上执行默认参数升级,但不起作用。这是正确的。在这种情况下,将执行默认参数升级,默认参数升级的规则是将双精度保留为双精度。

答:

0赞 Sourav Ghosh 11/4/2023 #1

首先,题中的代码不是有效的 C99,更不用说 C11、C18 或 C23 了。在 C99 及更高版本中,函数必须在使用之前声明(main() 除外)。只有 C90 允许在不声明的情况下使用函数 - 但这不是一个长期标准,目前已经过时了。您必须在调用之前声明该函数。

也就是说,给定代码段中的另一个问题正是报价中提到的,您在预期时发送了一个值。由于 和 不是兼容的类型,因此此程序调用未定义的行为。输出不能以任何方式证明其合理性。doubleintdoubleint

引用 C 标准,第 6.5.2.2/P6 章

...如果使用包含原型的类型定义函数,并且原型以省略号 (, ...) 结尾,或者升级后的参数类型与参数类型不兼容,则行为未定义。

如果您需要知道是什么导致了程序的某个执行的确切输出,则需要参考该过程中使用的硬件和编译器(以及运行环境,如果适用)的文档。

评论

2赞 Jonathan Leffler 11/4/2023
问题中的代码无效 C99,更不用说 C11、C18 或 C23 了。在 C99 及更高版本中,函数必须先声明,然后才能使用(除了 )。只有 C90 允许在不声明的情况下使用函数——但只有草率的 C 代码才使用该许可证。在创建标准时,有很多代码现在被认为是草率的。从那以后的30+年里,这种草率变得越来越不可原谅。main()
0赞 Sourav Ghosh 11/4/2023
@JonathanLeffler 绝对,先生,非常重要。我会添加一个注释。
3赞 Support Ukraine 11/4/2023 #2

标题第一:

C 语言中的默认参数升级导致错误......

不,错误不是由“默认参数升级”引起的。

该错误是由于编译器不知道何时调用它而引起的。此时编译器不知道函数预期的参数类型。squaresquare

今天,这样的代码是非法的(未定义的行为)。

然而,在过去(并非如此)美好的日子里,它被接受(一些编译器仍然这样做)。规则是编译器应该对函数调用中使用的参数执行“默认参数提升”。例如,a 会在调用前变成 a,而 a 会变成 ,等等。floatdoubleshortint

对于已发布的代码,应应用“默认参数升级”,但它没有效果,因为已经是 .编译器将只生成用于传递 的机器代码,即 照原样。不会发生任何转换。xdoubledoublex

当函数读取 时,问题就来了。该函数将假设它被传递了,机器代码将根据“获取 int 参数的规则”读取该值。squarenint

这就是这部分问题的关键:

...我想知道问题到底是如何发生的吗?

这是关于“调用约定”。一组规则,描述(除其他事项外)如何将参数传递给函数。这些约定不是 C 标准的一部分。调用约定被保留为“实现的东西”。不同的系统可能以不同的方式做到这一点。例如,Windows 和 Linux 使用不同的调用约定。

因此,要找出“问题究竟是如何发生的?”,您需要研究特定系统的调用约定。对此没有单一的解释。

例如,考虑 System V ABI。这里,第一个浮点参数是使用 XMM0 传递的,第一个整数参数是使用 RDI 传递的。因此,对于代码,这意味着调用方将把值存储在 XMM0 中,而被调用方将从 RDI 读取,因为它需要一个整数。显然,这是行不通的。3.0

在您的系统上,可能会发生与上述不同的情况。但常见的问题是一样的:调用方和被调用方对 .doubleint

即使某些调用约定指定了相同的“硬件资源”来传递 和 ,也会存在其他低级问题。和的大小可能会有所不同。点值的二进制编码与整数值的二进制编码不同。doubleintdoubleint3.03

评论

0赞 Eric Postpischil 11/4/2023
不要将非法和未定义混为一谈。C 语言中有些东西是未定义的,因为 C 委员会希望它们在不同的实现中以不同的行为使用。它们并不违法。C 标准没有指定完整的编程语言。它有意指定了一种要扩展的基本语言,并且它保留了许多未定义的东西,以便可以以不同的方式扩展基本语言,以满足人们的需求或愿望。