以下代码片段之间有什么区别:1.
double a = 10.154430; int b = a; printf("%d", b); //prints 10 (WHY?)
double a = 10.154430; printf("%d", a); //prints garbage value as expected`
现在,在这两种情况下,我都没有做显式类型转换,但如果我正确地工作...为什么?(int存储双精度值,这不应该隐式工作)
0sgqnhkj1#
请注意,第一种情况相当于printf("%d", (int)a);从double到int的强制转换是一种特殊情况,它们将浮点数 * 转换 * 为整数。(In实际上,大多数其他强制转换,例如在诸如unsigned long和short的各种整数类型之间的强制转换,“保留”内部表示的大多数位;在这方面,从整型到浮点型或从浮点型到整型的强制转换非常特殊,因为这涉及到一些“附加”处理)第二种情况printf("%d", a)实际上是undefined behavior(UB)。您使用的printf的参数类型与其控制格式字符串不兼容。UB实际上可以是worse。另请参见n1570的§6.5.4
printf("%d", (int)a);
double
int
unsigned long
short
printf("%d", a)
printf
50pmv0ei2#
代码片段1和代码片段2存在差异,即:
igsr9ssn3#
在第一种情况下,有一个隐式类型转换。
int b = a; // Implicit conversion. OK
相当于
int b = (int)a; // Explicit conversion. OK
(The只要类型转换值在int类型范围内,上述类型转换即可。)而在第二种情况下,你的程序没有使用正确的double数据类型的格式规范,从而调用了未定义的行为。你得到的垃圾值是未定义行为的一个可能结果。
2w3rbyxf4#
当printf("%d", a)被调用时,下面的代码被压入到调用栈中。
"%d" // address of string literal a // 8 bytes pushed onto stack assuming sizeof(double) is 8 return address of caller
然后调用到printf函数本身的跳转。当printf启动时,它有第一个参数,格式字符串("%d"),在一个已知的堆栈偏移量,但它不知道有多少参数也被推到堆栈上,它依赖于格式代码告诉它如何解释堆栈上的后续字节。它解析格式字符串并读取%d,然后当它看到%d时,它假设下一个被压入堆栈的值是一个4字节的整数,然后它从堆栈中读取4个字节,并做任何需要的处理将内存打印成整数,但实际上,它真正打印的是组成浮点值的一半字节。仅看一个Intel示例,假设以下代码。
"%d"
%d
double d = 3.14; printf("%x\n", d); // push 8-byte double, but print as integer hex return 0;
打印输出:51eb851f英特尔处理器存储在内存中的3.14的IEEE浮点表示如下:双精度浮点数的前4个字节与打印的相同(除了字节顺序相反,因为Intel是“little endian”)。
51eb851f
uz75evzq5#
现在,在这两种情况下,我都没有做显式类型转换,但如果我正确地工作...为什么?(int存储双精度值,这不应该隐式工作)案例1涉及到使用简单的赋值运算符。操作数满足此运算符的约束,特别是使用案例左操作数具有原子、限定或非限定算术类型,右操作数具有算术类型请注意,这两个参数的类型不必相同。它们都是算术类型就足够了。在这种情况下,指定行为的主要方面是右操作数的值被转换为赋值表达式的类型,并替换存储在由左操作数指定的对象中的值。因此,即使没有显式强制转换,也可以获得转换。案例2是不同的,请注意printf的原型:
int printf(const char * restrict format, ...);
这是一个可变参数函数,你在可变参数中传递double,当你把一个参数传递给一个原型函数参数时,你会得到和简单赋值中完全一样的转换,但是对于没有作用域内原型的函数的参数,以及可变参数的函数,你会得到"默认参数提升"。2这些只包括将小于int的整型提升为int或unsigned int,以及将float提升为double。在printf()的特定情况下,当实际参数与相应的字段指令不匹配时,行为是未定义的。这就是您的情况。更一般地说,当任何变元函数试图将其变量参数之一解释为与该参数的(默认提升的)实际类型不兼容时,就会发生UB。
unsigned int
float
printf()
qvsjd97n6#
任何变量(int、char、float或double)都以位的形式存储在分配给它的内存位置。printf("%d", a);这行代码的意思是...将内存地址a处的位打印为%d(int)。例如,请考虑以下行:
printf("%d", a);
a
char ch = '0'; printf("%d", ch);
第一行char ch = 0;存储**'0'的二进制值,即在名为ch的地址处的0b110000**。第二行printf("%d", ch);将地址ch处的值打印为%d。基本上,它将值'0b110000'打印为int。转换为十进制的48,因此输出为48。“double”也是一样,存储在“double”变量地址的位将被打印为“int”,因此是垃圾值。“double”的小数部分也存储为位,但“int”类型不会将这些位视为小数部分。您可以阅读更多关于“float”和“doubles”如何存储的信息。
char ch = 0;
ch
printf("%d", ch);
6条答案
按热度按时间0sgqnhkj1#
请注意,第一种情况相当于
printf("%d", (int)a);
从
double
到int
的强制转换是一种特殊情况,它们将浮点数 * 转换 * 为整数。(In实际上,大多数其他强制转换,例如在诸如
unsigned long
和short
的各种整数类型之间的强制转换,“保留”内部表示的大多数位;在这方面,从整型到浮点型或从浮点型到整型的强制转换非常特殊,因为这涉及到一些“附加”处理)第二种情况
printf("%d", a)
实际上是undefined behavior(UB)。您使用的printf
的参数类型与其控制格式字符串不兼容。UB实际上可以是worse。另请参见n1570的§6.5.4
50pmv0ei2#
代码片段1和代码片段2存在差异,即:
igsr9ssn3#
在第一种情况下,有一个隐式类型转换。
相当于
(The只要类型转换值在
int
类型范围内,上述类型转换即可。)而在第二种情况下,你的程序没有使用正确的
double
数据类型的格式规范,从而调用了未定义的行为。你得到的垃圾值是未定义行为的一个可能结果。2w3rbyxf4#
当
printf("%d", a)
被调用时,下面的代码被压入到调用栈中。然后调用到
printf
函数本身的跳转。当printf启动时,它有第一个参数,格式字符串(
"%d"
),在一个已知的堆栈偏移量,但它不知道有多少参数也被推到堆栈上,它依赖于格式代码告诉它如何解释堆栈上的后续字节。它解析格式字符串并读取
%d
,然后当它看到%d
时,它假设下一个被压入堆栈的值是一个4字节的整数,然后它从堆栈中读取4个字节,并做任何需要的处理将内存打印成整数,但实际上,它真正打印的是组成浮点值的一半字节。仅看一个Intel示例,假设以下代码。
打印输出:
51eb851f
英特尔处理器存储在内存中的3.14的IEEE浮点表示如下:双精度浮点数的前4个字节与打印的相同(除了字节顺序相反,因为Intel是“little endian”)。
uz75evzq5#
现在,在这两种情况下,我都没有做显式类型转换,但如果我正确地工作...为什么?(int存储双精度值,这不应该隐式工作)
案例1涉及到使用简单的赋值运算符。操作数满足此运算符的约束,特别是使用案例
左操作数具有原子、限定或非限定算术类型,右操作数具有算术类型
请注意,这两个参数的类型不必相同。它们都是算术类型就足够了。在这种情况下,指定行为的主要方面是
右操作数的值被转换为赋值表达式的类型,并替换存储在由左操作数指定的对象中的值。
因此,即使没有显式强制转换,也可以获得转换。
案例2是不同的,请注意
printf
的原型:这是一个可变参数函数,你在可变参数中传递
double
,当你把一个参数传递给一个原型函数参数时,你会得到和简单赋值中完全一样的转换,但是对于没有作用域内原型的函数的参数,以及可变参数的函数,你会得到"默认参数提升"。2这些只包括将小于int
的整型提升为int
或unsigned int
,以及将float
提升为double
。在
printf()
的特定情况下,当实际参数与相应的字段指令不匹配时,行为是未定义的。这就是您的情况。更一般地说,当任何变元函数试图将其变量参数之一解释为与该参数的(默认提升的)实际类型不兼容时,就会发生UB。qvsjd97n6#
任何变量(int、char、float或double)都以位的形式存储在分配给它的内存位置。
printf("%d", a);
这行代码的意思是...将内存地址a
处的位打印为%d
(int)。例如,请考虑以下行:
第一行
char ch = 0;
存储**'0'的二进制值,即在名为ch
的地址处的0b110000**。第二行printf("%d", ch);
将地址ch
处的值打印为%d
。基本上,它将值'0b110000'打印为int。转换为十进制的48,因此输出为48。“double”也是一样,存储在“double”变量地址的位将被打印为“int”,因此是垃圾值。“double”的小数部分也存储为位,但“int”类型不会将这些位视为小数部分。您可以阅读更多关于“float”和“doubles”如何存储的信息。