【问题标题】:Why C does not have a correct implementation of implicit conversion?为什么 C 没有正确实现隐式转换?
【发布时间】:2015-08-29 06:12:28
【问题描述】:

以下代码打印 99,其中 'c' 已隐式转换为 99。

printf("%d", 'c');

但下面的代码打印出 0.000000:

printf("%f", 23);

为什么在第二种情况下,整数 23 没有像第一种情况那样转换成 23.00000?这是否反映了 C 中隐式转换的糟糕实现?提前致谢。

编辑:如果正如答案之一所暗示的那样,从 int 到 float 的提升是不可能的,那么为什么在我们编写时会提升 int 到 float

float x = 23;

为什么会这样?

【问题讨论】:

  • c 没有隐式转换为 99; it is 99。至于后者,您究竟希望 C 中的printf 知道您没有float 传递给它吗?
  • 当您调用printf 时,编译器无法猜测您要压入堆栈的内容,因为它是一个可变参数函数(接受未知数量的参数,其类型未知,除了第一个)。因此它将每个大小小于int 的整数类型(例如charshort)提升(转换)为int,以及每个大小小于double 的非整数类型(例如@987654334 @) 到double。这就是为什么您的第二个示例产生错误输出的原因。编译器将23 推送为int(在大多数平台上为4 字节),但printf 需要double(在大多数平台上为8 字节)。
  • @coder12345:没有答案说从 int 到 double 的转换是不可能的;只是它不会发生在可变参数函数的参数上。为了更清楚为什么不这样做,想象一下代码实际上是:printf(format, 23);。编译器如何知道 format 被分配(在运行时)给值 "%f"
  • 为了记录,现在的许多编译器都有扩展,它们使用名为 const char * 的参数对可变参数函数执行分析,称为 fmt 并将类型检查您传递的参数以确保它们匹配正确使用格式字符串文字。
  • @Qix:是的,但他们会发出警告,而且他们只能检查以文字形式提供的格式字符串,这绝不是必需的。无论如何,没有编译器插入代码来根据格式转换 printf 参数的类型。 (顺便说一下,gcc 期望一个编译指示告诉它一个函数是 printf-like 或 scanf-like)。

标签: c++ c implicit-conversion


【解决方案1】:

在 C 中,“c”int。不执行任何转换。

即使它是 char(在 C++ 中),char 也是整数类型,并且在像 printf 这样的可变参数函数的参数列表中,所有可变参数都经过算术提升,这将自动将char 扩大到int,或将float 扩大到double——但不能将int 扩大到double

【讨论】:

  • 但是当我们写 int x = 2.33; 时确实会发生 int 到 double 的提升;为什么会这样??
  • @coder12345:这是从 double 到 int 的转换,它发生在赋值运算符中,它知道两个操作数的类型。但是编译器不知道可变参数函数的参数的“预期”类型。
  • @coder12345 这不是从intdouble 的提升;这是从doubleint降级。这与使用可变参数调用函数也不相同。更严格的示例是void foo(float x){} 并使用foo('c'); 调用。目标类型在编译时是已知的。 (和上升 rici;很好的答案)
  • 在可变参数函数的特殊情况下,它既不是提升也不是降级。这是对记忆的重新解释,因为可变参数是无类型的。
  • @coder12345:因为那是编译器正确识别类型差异。像往常一样。但是像printf 这样的可变参数函数是一种特殊情况,类型系统无法识别格式说明符%f 请求的浮点数。基本上,printf 内部的类型系统是“关闭”的。
【解决方案2】:

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

在第一个示例中,'c' 已经是 int。但是在浮点数的情况下,它没有执行将int 转换为float 所需的转换,因为编译器不知道它应该,或者更准确地说,根据规范,没有什么可做的但相信格式说明符是关于类型的真实来源。它认为数据已经是浮点数,因为格式说明符%f 告诉它,它看不到传入的 23 的类型。

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

如果您想更深入地了解这个难题,请使用printf 使用的 google va_list/va_start/va_stop。理解这个机制允许我们循环发送到printf的数据,但是类型信息丢失了。它是无类型的二进制数据,要使用它,它使用格式说明符%f 来实现浮点数。然后它将二进制数据解释为float,并在显示之前将其写入字符串。

从另一个角度来看它是编译器在其类型系统中忽略了像%f 这样的字符串内容...... %f 已编译但它仅在运行时应用函数运行时。但是在运行时,所有类型信息都丢失了——C 有一个类型系统,但它只是编译时的。基本上,%f 之类的字符串数据和 C 类型系统完全是外来概念,从不交流。所以试着想象你的程序没有像%f这样的字符串数据。编译器如何知道运行编译时 int->float 转换?

同样,这仅适用于可变参数函数,不适用于常规函数调用。普通函数调用表现出类似于大多数现代语言的类型处理。另一方面,可变参数函数是 C 独有的。与 void * 指针类似,它们应该被视为危险并谨慎使用。

编辑: float x = 23; 导致提升,因为该语言具有功能类型系统(尽管类型仅在编译时才知道)。类型系统在可变参数函数参数中不起作用,即printf 的输入。

【讨论】:

  • 那为什么它不像 Java 等其他语言那样将 23 视为 23.00 呢??
  • 因为类型信息没有持久化到printf 实现中。这种破坏行为特定于可变参数函数,例如printf。常规函数调用没有这个问题。 printf 接收到va_list 这是无类型二进制文件,它无法解释内存,除非格式说明符 %f 告诉它它是什么。
  • @coder12345:因为给定printf("%f", 23),编译器不知道预期的类型是double。格式字符串不必在编译时就知道;可能是char *format = "%f"; printf(format, 23)。底线:C 不是其他语言。
  • "broken" 是一个有争议的术语。 ;)
  • double 类型是printf 所期望的,而不是编译器,因此编译器无法知道它应该如何提升该值并简单地遵循默认提升
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-30
  • 1970-01-01
  • 1970-01-01
  • 2011-02-26
  • 1970-01-01
  • 2013-04-17
相关资源
最近更新 更多