C语言:打印大数

13

接下来看这个:

#include <stdio.h>

main() {
    unsigned long long verybig = 285212672;

    printf("Without variable : %llu\n", 285212672);
    printf("With variable    : %llu", verybig);
}

这是上述程序的输出:

Without variable : 18035667472744448
With variable    : 285212672
正如您从上面可以看到的那样,当把数字作为常量传递给printf时,它会输出一些错误的巨大数字,但是当该值首先存储在变量中时,printf会打印正确的数字。
这背后的原因是什么?
4个回答

26

尝试使用285212672ULL;如果你不加后缀写它,编译器会将它视为普通整数。在变量中它可行是因为在赋值时将整数强制转换为unsigned long long ,这样传递给printf()的值就是正确的类型。

在你问之前,不,编译器可能不能"%llu"的printf()格式字符串中推断出来。那是一个不同层面的抽象。编译器负责语言语法,printf()语义不是语法的一部分,它是运行时库函数(与你自己的函数没有什么区别,只是它被包含在标准库中)。

考虑以下32位int和64位unsigned long long系统的代码:

#include <stdio.h>

int main (void) {
    printf ("%llu\n",1,2);
    printf ("%llu\n",1ULL,2);
    return 0;
}

输出结果为:

8589934593
1
在第一种情况下,两个32位整数1和2被推送到堆栈上,printf()把它解释为一个64位的无符号长整型值,即2 x 232 + 1。无意中将2参数包括在ULL值中。
在第二种情况下,实际上您推送了64位的1值和一个多余的32位整数2,该值将被忽略。
请注意,格式字符串和实际参数之间的"失步"是不好的做法。例如:
printf ("%llu %s %d\n", 0, "hello", 0);

由于32位的指针"hello"将被%llu使用,而%s会尝试对最后一个0参数进行解引用,因此很可能会崩溃。下面的“图示”说明了这一点(假设单元格为32位,并且“hello”字符串存储在0xbf000000)。

What you pass     Stack frames     What printf() uses
                 +------------+
0                | 0          | \
                 +------------+  > 64-bit value for %llu.
"hello"          | 0xbf000000 | /
                 +------------+
0                | 0          |    value for %s (likely core dump here).
                 +------------+
                 | ?          |    value for %d (could be anything).
                 +------------+

1
但我认为编译器足够聪明,可以在printf格式规范中找到%u,请尝试printf(“%d%u”,〜0,〜0)..两者都将按预期打印值.. - sud03r
不是的 - 这些数据类型大小相同 - 是printf()在计算 - 尝试使用'a'和%d。 - paxdiablo
Pax:这也没问题,字符字面量是整数常量。 - caf
1
没错,@caf,但编译器仍然没有查看printf参数。而且它不应该这样做,因为那是不同的抽象层次(语言与运行时库)。我已经更新了答案并提供了更多信息。 - paxdiablo
在代码中你使用了1和2,但在文本中你使用了0和1 :) - Johannes Schaub - litb
谢谢,@litb,已修复。我最初使用了0/1,但行为似乎有点模糊,这就是为什么我改变了[部分]它的原因。 - paxdiablo

5
值得指出的是,一些编译器对于这种情况会给出有用的警告 - 例如,GCC对于你的代码就是这样说的:
x.c: In function ‘main’:
x.c:6: warning: format ‘%llu’ expects type ‘long long unsigned int’, but argument 2 has type ‘int’

2
另一个不应忽略编译警告的原因。 - reuben

3

285212672 是一个 int 类型的值。 printf 函数需要一个 unsigned long long 类型的值,但你传递了一个 int 类型的值。因此,它会从栈上取出比实际传递的值更多的字节,并打印垃圾数据。将其放入一个 unsigned long long 类型的变量中,然后再将其传递给函数,在赋值行中它将被提升为 unsigned long long 类型,并将该值传递给 printf 函数,这样就可以正常工作。


0

数据类型仅是解释内存位置内容的一种方式。
在第一种情况下,常量值作为int存储在只读内存位置中,printf尝试将此地址解释为8字节位置,因为它被指示存储的值是long long,在此过程中打印了垃圾值。
在第二种情况下,printf尝试将long long值解释为8个字节,并打印预期结果。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接