我正在尝试通过阅读C Programming Language, 2nd Edition 来学习C.我有一些编程经验,但没有C。
我目前在第1章。我有以下代码:
float f;
for (f = 0.0; f <= 3; f += 1.1)
printf("A: %3f B: %6.2f\n", f, f + 0.15);
打印输出:
A: 0.000000 B: 0.15
A: 1.100000 B: 1.25
A: 2.200000 B: 2.35
看起来很好。
现在我按如下方式更改printf:
printf("A: %3d B: %6.2f\n", f, f + 0.15);
新输出
A: 0 B: 0.00
A: -1610612736 B: 0.00
A: -1610612736 B: -625777476808257557292155887552002761191109083510753486844893290688350183831589633800863219712.00
这里发生了什么?我希望浮点数转换为int,因为我使用了%d,但事实并非如此。另外,为什么价值B也出错?这里发生了什么事?
答案 0 :(得分:16)
当你致电:
printf("A: %3d B: %6.2f\n", f, f + 0.15);
C会自动将float
值转换为double
(这是在调用带有可变参数的函数时进行的标准转换,例如int printf(const char *fmt, ...);
)。为了论证,我们假设sizeof(int)
是4而sizeof(double)
是8(有例外,但它们很少而且很远)。
f
的8字节双精度,以及f + 0.15
的另一个8字节双精度。在处理格式字符串时,%d
告诉printf()
您在格式字符串后将4字节int
压入堆栈。由于这不是你所做的,你已经调用了未定义的行为;根据C标准,接下来发生的一切都没问题。
然而,最可能的实现轻快地读取4个字节并将它们打印为好像它们是int
(它相信你说实话)。然后它遇到%6.2f
格式;它将从堆栈中读取8字节为double
。有一个外部的可能性会导致内存错误导致访问错位(这需要64位机器,要求double
在8字节边界上对齐,例如SPARC),否则它将会从f
读取4个字节,从f + 0.15
读取4个字节,将它们放在一起以创建一些非常意外的double
值 - 如示例所示。
答案 1 :(得分:8)
Printf会将您指向的内存视为您告诉它的内存。没有转换。它将表示float的内存视为int。因为两者的存储方式不同,所以你得到的本质上是一个随机数。
如果要将float输出为整数,则应首先将其转换为:
printf("A: %3d B: %6.2f\n", (int)f, f + 0.15);
答案 2 :(得分:1)
对于大多数函数,如果您传递float
但函数期望使用int
,则编译器知道会自动将float
转换为int
。但是printf
是特殊的(非常特殊)。 %d
期望有一个int
,您的工作就是将其传递给int
。在printf
的情况下,编译器中没有自动机制可以为您执行转换。
尽管如此,好的编译器会检测并警告此问题。如果没有,那您需要一个更好的。
更长的解释是,对于大多数函数,函数原型给出所有参数的数量和类型,这是使编译器了解可能需要引入转换的机制。但是printf
的原型是
extern int printf(const char *, ...);
这三个点...
的字面意思是:“这里的参数个数可变,不知道有多少个或什么类型”。到printf
实际运行时,它会在格式字符串中找到%d
,告诉它期待已通过的int
,如果您通过了转换,则为时已晚,无法进行任何转换除了int
以外。
答案 3 :(得分:0)
不管浮点参数如何,都可以打印任何想要的整数值:
printf("A: %d B: %6.2f\n", f, f + 0.15);
以下是在英特尔架构上打印任意整数的方法:
int print_it(int, int /* nameless but printed */, float f)
{
printf("A: %d B: %6.2f\n", f, f + 0.15);
}
int main()
{
print_it(0, 12 /* will be printed */, 0.0);
print_it(0, 123 /* printed */, 1.1);
print_it(0, 1234 /* printed */ , 2.2);
}
此输出:
A: 12 B: 0.00
A: 123 B: 1.10
A: 1234 B: 2.20
说明:显然,格式字符串和参数不匹配会导致未定义的行为。不过,有时可以预见。在英特尔架构上,前几个参数由寄存器传递。浮点值在不同的寄存器中传递。
尽管与问题中的printf
指令相同,但输出却不同。发生的情况是12、123、1234通过负责第二个非浮点参数的通用寄存器。由于printf
仅具有一个非浮点参数,因此第二个非fp参数的寄存器不变。该寄存器保留从print_it(0, int_value, fp_value)
的第二个参数获得的值。
但是原始文件会带来垃圾:
for (f = 0.0; f <= 3; f += 1.1) printf("A: %3f B: %6.2f\n", f, f + 0.15);
由于printf
在内部调用其他函数,因此产生了不同的垃圾。这些功能破坏了printf("... %d ...", ...)
读取的通用寄存器。
显然,此行为仅在通过一组单独的寄存器传递浮点参数的系统上发生。显然,只有在编译器优化未以某种方式修改代码的情况下,这种情况才会发生,因为在发生未定义的行为时,它可以做一些野蛮的事情。