为什么C语言没有正确的隐式转换实现?

4
以下代码将隐式将 'c' 转换为 99,并打印出 99。
printf("%d", 'c');

但是以下代码输出的结果是0.000000:
printf("%f", 23);

在第二种情况下,为什么整数23没有像第一种情况中那样转换为23.00000?这反映了C语言中隐式转换的实现不够好吗?提前致谢。
编辑:如果像其中一个答案所建议的那样,从int到float不存在升级,那么为什么我们在写```float result = 2 + 3;```时会从int升级到float呢?
float x = 23;

为什么呢?

15
c没有被隐式转换为99;就是99。至于后者,你希望C语言中的printf如何知道你并没有传递一个float呢? - WhozCraig
4
没有回答说将int转换为double是不可能的,只是说它不会发生在可变参数函数的参数中。为了更明显地说明为什么不能这样做,想象一下代码实际上是这样的:printf(format, 23);。编译器如何知道 format (在运行时)被赋值为 "%f" 的值? - rici
1
值得一提的是,现今许多编译器都有扩展功能,可以对带有 const char * 参数 fmt 的可变参数函数进行分析,并检查你传递的参数是否与格式字符串字面量正确匹配。 - Qix - MONICA WAS MISTREATED
1
@Qix:是的,但它们只发出警告,并且只能检查作为字面值提供的格式字符串,这并不是必需的。不管怎样,没有编译器会插入代码来根据格式转换printf参数的类型。 (顺便说一句,gcc希望使用#pragma告诉它函数类似于printf或scanf)。 - rici
@rici 当然,它只会警告你。编译器很少为你插入代码,更不会改变你的参数类型 :) - Qix - MONICA WAS MISTREATED
显示剩余6条评论
2个回答

12
在C语言中,'c'是一个int类型而不需要进行转换。即使它是一个char类型(在C++中是这样的),char也是一种整数类型,在变参函数的参数列表中(例如printf函数),所有变参都会进行算术提升,这将自动将char扩展为int或float扩展为double,但不会将int扩展为double。请注意保留原文中的HTML标记。

1
但是当我们写 int x = 2.33; 时,将 int 提升为 double 是发生的,为什么会这样呢? - user4642421
6
这是将double转换为int的过程,发生在赋值运算符中,该运算符可以知道两个操作数的类型。但编译器并不知道作为可变参数函数参数的“期望”类型。 - rici
3
@coder12345 这不是从 int 升级到 double,而是从 double 降级到 int。这也与调用带有可变参数的函数不同。一个更紧凑的例子是 void foo(float x){} 并使用 foo('c'); 进行调用。目标类型在编译时已知。(向 uptick rici 致意,很好的答案) - WhozCraig
2
在可变参数函数的特殊情况下,它既不是升级也不是降级。这是因为可变参数是无类型的,所以它是对内存的重新解释。 - VoidStar
1
@coder12345:因为编译器正确地识别了类型差异。就像它通常做的那样。但是像 printf 这样的可变参数函数是一种特殊情况,其中类型系统无法识别格式说明符 %f 请求的是浮点数。基本上,在 printf 中,类型系统是“关闭”的。 - VoidStar
显示剩余3条评论

9

printf是一个可变参数函数,这意味着除了第一个参数以外的其它参数在访问时实际上是无类型的。这排除了在printf的参数列表中进行任何智能类型转换的可能性(尽管有某些默认转换会发生,有点盲目且与格式说明符无关)。

在第一个例子中,“c”已经是一个int。但是在浮点数的情况下,它没有执行必要的转换,将一个int转换为float,因为编译器不知道它应该这样做,或者更准确地说,根据规范,除了相信格式说明符是关于类型的真相之外,没有其他事情需要做。它认为数据已经是一个浮点数,因为格式说明符%f告诉它如此,并且它看不到传递的23的类型。

这使得开发人员很容易意外地导致崩溃 - 开发人员必须确保格式说明符(例如%d)与提供的数据匹配。例如,将“%s”指定符与int输入混合使用会导致运行时崩溃,因为编译器将简单地“相信”格式说明符不是错误的。更糟糕的是,试着完全省略数据... printf(“%f%f%f”)会导致它寻找甚至不存在的参数,这将导致崩溃。不幸的是,编译器必须接受并构建这个崩溃的代码。

如果您想更仔细地了解这个难题,请搜索va_list/va_start/va_stop,这是printf使用的机制。请理解这个机制允许我们循环遍历发送到printf中的数据,但是类型信息丢失了。它是无类型的二进制数据,并且为了使用它,它使用格式说明符%f来意识到那里有一个浮点数。然后将二进制数据解释为float并将其写入字符串中以显示出来。

从另一个角度来看,编译器忽略了像%f这样的字符串内容在其类型系统中的存在...%f被编译了,但它只在运行函数时应用。但是在运行时,所有类型信息都丢失了 - C具有类型系统,但它仅在编译时存在。基本上,像%f这样的字符串数据和C类型系统是完全不同的概念,从未进行过通信。因此,请尝试想象一下没有任何像%f这样的字符串数据的程序。编译器如何知道运行时int->float转换?

请注意,以下内容仅适用于可变参数函数,而非普通函数调用。普通函数调用的类型处理方式类似于大多数现代语言。另一方面,可变参数函数是C语言独有的。与void *指针类似,它们应该被视为危险的,并小心使用。

编辑: float x = 23;会导致提升,因为该语言具有功能型类型系统(尽管类型仅在编译时已知)。类型系统无法在可变参数函数参数内部工作,即printf的输入参数。


那么为什么它不像Java一样将23视为23.00呢? - user4642421
6
因为类型信息没有保存在 printf 实现中,所以会出现这种问题。这种错误行为只出现在可变参数函数(例如 printf)中,常规函数调用没有这个问题。printf 接收的是未命名二进制的 va_list 类型,除非格式说明符 %f 告诉它对应的数据类型,否则它无法解释内存中的数据。 - VoidStar
1
@coder12345:因为给定printf("%f", 23),编译器不知道期望的类型是double。格式字符串不必在编译时就已知;它可以是char *format = "%f"; printf(format, 23)。底线:C语言不同于其他语言。 - Keith Thompson
4
“_broken_”是一个有争议的术语。 ;) - Qix - MONICA WAS MISTREATED
1
printf 需要 double 类型,而不是编译器,因此编译器无法知道它应该如何提升值,只能遵循默认提升。 - phuclv
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接