C++ 为什么printf(“%f”,0);给出未定义的行为?
声明C++ 为什么printf(“%f”,0);给出未定义的行为?,c++,c,printf,implicit-conversion,undefined-behavior,C++,C,Printf,Implicit Conversion,Undefined Behavior,声明 printf("%f\n",0.0f); printf("%f\n",0); 打印0 然而,声明 printf("%f\n",0.0f); printf("%f\n",0); 打印随机值 我意识到我表现出某种未定义的行为,但我不明白具体原因 所有位均为0的浮点值仍然是有效的float,其值为0。 float和int在我的机器上大小相同(如果相关的话) 为什么在printf中使用整数文本而不是浮点文本会导致这种行为 另外,如果我使用 int i = 0; printf("%f\n",
printf("%f\n",0.0f);
printf("%f\n",0);
打印0
然而,声明
printf("%f\n",0.0f);
printf("%f\n",0);
打印随机值
我意识到我表现出某种未定义的行为,但我不明白具体原因
所有位均为0的浮点值仍然是有效的float
,其值为0。float
和int
在我的机器上大小相同(如果相关的话)
为什么在printf
中使用整数文本而不是浮点文本会导致这种行为
另外,如果我使用
int i = 0;
printf("%f\n", i);
我不知道是什么让人困惑 您的格式字符串需要一个
双精度;而是提供一个int
这两种类型是否具有相同的位宽度是完全不相关的,只是它可以帮助您避免从这样的中断代码中获得硬内存冲突异常。%f“
格式需要类型为double
的参数。您给它一个类型为int
的参数。这就是行为未定义的原因
本标准不保证所有零位都是0.0
(尽管它通常是)或任何double
值的有效表示,或者int
和double
大小相同(请记住它是double
,而不是float
),或者,即使它们大小相同,它们以同样的方式作为参数传递给变量函数
它可能恰好在您的系统上“工作”。这是未定义行为最糟糕的症状,因为它使诊断错误变得困难
7.21.6.1第9段:
。。。如果任何参数不是相应的
转换规范,行为未定义
float
类型的参数升级为double
,这就是printf(“%f\n”,0.0f)
工作的原因。小于int
的整数类型的参数提升为int
或无符号int
。在printf(“%f\n”,0)
的情况下,这些升级规则(由N1570 6.5.2.2第6段规定)没有帮助
请注意,如果将常量0
传递给需要double
参数的非变量函数,则假设该函数的原型可见,则行为定义良好。例如,sqrt(0)
(在#include
之后)隐式地将参数0
从int
转换为double
——因为编译器可以从sqrt
的声明中看出它需要一个double
参数。对于printf
,它没有此类信息。变量函数如printf
是特殊的,在编写调用时需要更加小心
为什么使用整数文本而不是浮点文本会导致这种行为
因为除了作为第一个参数的const char*formatstring
之外,printf()
没有类型化参数。它使用c样式的省略号(…
)来表示所有其余部分
它只是决定如何根据格式字符串中给定的格式类型解释传递到那里的值
您将有与尝试时相同的未定义行为
int i = 0;
const double* pf = (const double*)(&i);
printf("%f\n",*pf); // dereferencing the pointer is UB
通常,当您调用一个需要double
的函数,但提供了int
时,编译器将自动为您转换为double
。这在printf中不会发生,因为函数原型中没有指定参数的类型-编译器不知道应该应用转换。首先,正如其他几个答案中所述,但在我看来不是这样,解释得足够清楚:在大多数情况下,如果库函数采用double
或float
参数,则可以提供整数。编译器将自动插入转换。例如,sqrt(0)
是定义良好的,其行为将与sqrt((double)0)
完全相同,并且对于其中使用的任何其他整数类型表达式也是如此
printf
不同。这是不同的,因为它需要可变数量的参数。其功能原型为
extern int printf(const char *fmt, ...);
所以你写的时候,
printf(message, 0);
编译器没有任何关于printf
希望第二个参数是什么类型的信息。它只有参数表达式的类型,即int
。因此,与大多数库函数不同,程序员有责任确保参数列表与格式字符串的预期匹配
(现代编译器可以查看格式字符串,并告诉您类型不匹配,但他们不会开始插入转换来实现您的意思,因为您会注意到,与几年后使用不太有用的编译器重建代码相比,您的代码最好现在就断开。)
现在,问题的另一半是:在大多数现代系统中,(int)0和(float)0.0都表示为32位,它们都是零,为什么它不工作呢?C标准只是说“这不是必须的,你是靠你自己的”,但让我解释一下为什么它不起作用的两个最常见的原因;这可能会帮助您理解为什么不需要它
首先,出于历史原因,当您通过变量参数列表传递一个浮点值时,它会被提升为双精度
,在大多数现代系统中,双精度是64位宽。因此,printf(“%f”,0)
只将32个零位传递给需要64个零位的被调用方
第二个同样重要的原因是,浮点函数参数的传递位置可能与整数参数不同。例如,大多数CPU都有单独的整数和浮点值寄存器文件
va_list arg;
....
case('%f')
va_arg ( arg, double ); //va_arg is a macro, and so you can pass it the "type" that will be used for casting the int pointer argument of printf..
....
if (__ldbl_is_dbl)
{
args_value[cnt].pa_double = va_arg (ap_save, double);
...
}
char *p = (double *) &arg + sizeof arg; //printf parameters area pointer
double i2 = *((double *)p); //casting to double because va_arg(arg, double)
p += sizeof (double);