使用%f打印整数变量
以下c程序的输出是:0.000000输出后面是否有逻辑或者是编译器依赖的答案还是我只是得到垃圾值?
#include int main() { int x=10; printf("%f", x); return 0; }
PS: – 我知道尝试使用%f打印整数值是愚蠢的。 我只是从理论的角度来问这个问题。
从最新的C11草案 :
§7.16.1.1/ 2
...if type is not compatible with the type of the actual next argument (as promoted according to the default argument promotions), the behavior is undefined, except for the following cases: — one type is a signed integer type, the other type is the corresponding unsigned integer type, and the value is representable in both types; — one type is pointer to void and the other is a pointer to a character type.
要记住的最重要的事情是,正如克里斯指出的那样,行为是未定义的。 如果这是一个真正的程序,唯一明智的做法是修复代码。
另一方面,查看行为未被语言标准定义的代码的行为可能是有益的(只要您小心不要过多地概括行为)。
printf
的"%f"
格式需要一个double
类型的参数,并以十进制forms打印,不带指数。 非常小的值将打印为0.000000
。
当你这样做:
int x=10; printf("%f", x);
我们可以解释一下有关您所使用的平台的一些假设的可见行为:
-
int
是4个字节 -
double
是8个字节 -
int
和double
参数使用相同的机制传递给printf
,可能在堆栈上
因此,调用将(合理地)将int
值10
作为4字节数量推送到堆栈上, printf
将从堆栈中获取8字节数据并将其视为double
的表示。 4个字节将表示为10
(hex, 0x0000000a
); 其他4个字节将是垃圾,很可能为零。 垃圾可以是8字节数量的高阶或低阶4字节。 (或其他任何事情;记住行为未定义。)
这是我刚刚拼凑的演示程序。 它不是滥用printf
,而是使用memcpy()
将int
对象的表示复制到double
对象中。
#include #include void print_hex(char *name, void *addr, size_t size) { unsigned char *buf = addr; printf("%s = ", name); for (int i = 0; i < size; i ++) { printf("%02x", buf[i]); } putchar('\n'); } int main(void) { int i = 10; double x = 0.0; print_hex("i (set to 10)", &i, sizeof i); print_hex("x (set to 0.0)", &x, sizeof x); memcpy(&x, &i, sizeof (int)); print_hex("x (copied from i)", &x, sizeof x); printf("x (%%f format) = %f\n", x); printf("x (%%g format) = %g\n", x); return 0; }
我的x86系统的输出是:
i (set to 10) = 0a000000 x (set to 0.0) = 0000000000000000 x (copied from i) = 0a00000000000000 x (%f format) = 0.000000 x (%g format) = 4.94066e-323
正如您所看到的, double
的值非常小(您可以参考有关详细信息的IEEE浮点格式的参考),接近零, "%f"
打印为0.000000
。
让我再次强调, 行为是未定义的 ,这意味着语言标准“对程序的行为没有任何要求”。 字节顺序,浮点表示和参数传递约定的变化可以显着改变结果。 即使编译器优化也会影响它; 允许编译器假定程序的行为定义良好,并基于该假设执行转换。
所以请随意忽略我在这里写的所有内容(除了第一段和最后一段)。
因为二进制的整数10如下所示:
00000000 00000000 00000000 00001010
所有printf都采用内存中表示并尝试将其表示为IEEE 754浮点数。
浮点数有三个部分(从MSB到LSB):
标志:1位
指数:8位
尾数:23位
由于整数10在尾数位中仅为1010,因此它的数量非常小,远小于printf浮点格式的默认精度。
结果未定义。
我只是从理论的角度来问这个问题。
完整克里斯的优秀答案 :
你的printf中发生的事情是未定义的,但它可能与下面的代码非常相似 (它取决于varargs的实际实现,IIRC)。
免责声明:以下是对“平台上未定义的行为案例中可能发生的事情的解释”,而不是始终在所有平台上发生的真实/有效描述。
定义“未定义”?
想象一下以下代码:
int main() { int i = 10 ; void * pi = &i ; double * pf = (double *) pi ; /* oranges are apples ! */ double f = *pf ; /* what is the value inside f ? */ return 0; }
在这里,当你的指针指向double(即pf
)指向一个托管整数值的地址(即i
)时,你得到的是未定义的,最有可能是垃圾。
我想看看那个记忆里面有什么!
如果你真的想看看那些垃圾可能背后的东西(在某些平台上进行调试时),请尝试以下代码,我们将使用union来模拟一块内存,我们将编写double或int数据:
typedef union { char c[8] ; /* char is expected to be 1-byte wide */ double f ; /* double is expected to be 8-bytes wide */ int i ; /* int is expected to be 4-byte wide */ } MyUnion ;
f
和i
字段用于设置值, c
字段用于逐字节查看(或修改)内存。
void printMyUnion(MyUnion * p) { printf("[%i %i %i %i %i %i %i %i]\n" , p->c[0], p->c[1], p->c[2], p->c[3], p->c[4], p->c[5], p->c[6], p->c[7]) ; }
上面的函数将逐字节打印内存布局。
下面的函数将打印不同类型值的内存布局:
int main() { /* this will zero all the fields in the union */ memset(myUnion.c, 0, 8 * sizeof(char)) ; printMyUnion(&myUnion) ; /* this should print only zeroes */ /* eg. [0 0 0 0 0 0 0 0] */ memset(myUnion.c, 0, 8 * sizeof(char)) ; myUnion.i = 10 ; printMyUnion(&myUnion) ; /* the representation of the int 10 in the union */ /* eg. [10 0 0 0 0 0 0 0] */ memset(myUnion.c, 0, 8 * sizeof(char)) ; myUnion.f = 10 ; printMyUnion(&myUnion) ; /* the representation of the double 10 in the union */ /* eg. [0 0 0 0 0 0 36 64] */ memset(myUnion.c, 0, 8 * sizeof(char)) ; myUnion.f = 3.1415 ; printMyUnion(&myUnion) ; /* the representation of the double 3.1415 in the union */ /* eg. [111 18 -125 -64 -54 33 9 64] */ return 0 ; }
注意:此代码在Visual C ++ 2010上进行了测试。
这并不意味着它会在您的平台上以这种方式(或者根本)工作,但通常情况下,您应该得到类似于上面发生的结果。
最后,垃圾只是你所看到的内存中的hex数据集,但被视为某种类型。
由于大多数类型具有不同的数据存储器表示,因此查看除原始类型之外的任何其他类型的数据必然会产生垃圾(或不那么垃圾)结果。
你的printf可能表现得很好,因此,当它最初设置为int时,尝试将原始内存解释为double。
PS:请注意,由于int和double的字节大小不同,垃圾变得更加复杂,但它主要是我上面所描述的。
但我想打印一个int作为双!
真的吗?
Helios提出了一个解决方案 。
int main() { int x=10; printf("%f",(double)(x)); return 0; }
让我们看看伪代码,看看是什么输入到printf:
/* printf("...", [[10 0 0 0]]) ; */ printf("%i",x); /* printf("...", [[10 0 0 0 ?? ?? ?? ??]]) ; */ printf("%f",x); /* printf("...", [[0 0 0 0 0 0 36 64]]) ; */ printf("%f",(double)(x));
演员表提供不同的内存布局,有效地将整数“10”数据更改为双“10.0”数据。
因此,当使用“%i”时,它会期望类似[[?? ?? ?? ??]],并且对于第一个printf,接收[[10 0 0 0]]并将其正确解释为整数。
当使用“%f”时,它会像[[?? ?? ?? ?? ?? ?? ?? ??]],并在第二个printf上接收类似[[10 0 0 0]]的东西,缺少4个字节。 所以最后4个字节是随机数据(可能是[[10 0 0 0]]之后的字节,也就是[[10 0 0 0 ?? ?? ?? ?? ??]]
在最后一个printf中,强制转换改变了类型,因此将内存表示改为[[0 0 0 0 0 0 36 64]],printf将其正确解释为double。
基本上它是垃圾。 小整数看起来像不应该存在的非标准化浮点数。
您可以像这样强制转换int变量:
int i = 3; printf("%f",(float)(i));