这是 glibc printf 中的错误吗?

Is this a bug in glibc printf?

使用 glibc 中的 stdint.h(gcc SUSE Linux 版本 9.2.1,英特尔酷睿 I7 处理器)我在直接打印 INT32_MIN 时遇到了一个最奇怪的行为:

#include <stdio.h>
#include <stdint.h>

void main(void)
{
    printf("%d\n", INT16_MIN);
    int a = INT16_MIN;
    printf("%d\n", a);

    printf("%ld\n", INT32_MIN);
    long b = INT32_MIN;
    printf("%ld\n", b);

    printf("%ld\n", INT64_MIN);
    long c = INT64_MIN;
    printf("%ld\n", c);
}

输出:

-32768
-32768
2147483648
-2147483648
-9223372036854775808
-9223372036854775808

此外,如果我尝试

printf("%ld\n", -INT32_MIN);

我得到了相同的结果,但是使用了编译器 warning: integer overflow in expression '-2147483648' of type 'int' results in '-2147483648' [-Woverflow]

并不是说这对任何现有程序来说都非常糟糕,实际上它看起来非常无害,但这是旧 printf 中的错误吗?

让我们观察这个特定的片段

    long a = INT32_MIN;
    printf("%ld\n", a);          // #1
    printf("%ld\n", INT32_MIN);  // #2

它打印以下内容(根据您的示例,我自己还没有尝试过,它取决于实现,我稍后会指出):

-2147483648
2147483648

现在,在 #1 中,我们将 long 传递给 printf。该函数使用可变参数列表并根据指定的格式,我们的参数值将被解释为 signed long,因此我们最终得到值 -2147483648.

对于#2,我们的情况有所不同。如果您查看 INT32_MIN 的定义方式,您会发现它是一个普通的旧 C 宏,例如:

# define INT32_MIN      (-2147483647-1)

所以,我们的线路

printf("%ld\n", INT32_MIN);

实际上是

printf("%ld\n", (-2147483647-1));

其中参数类型不是长整数,而是整数(参见 this for details)!与此比较

    int b = INT32_MIN;
    printf("%ld\n", b);

它打印出与您观察到的相同的值(正值)。

现在归结为您的问题实际上应该是什么:

What happens when printf receives a value of a type that isn't in the format specifier?

!

Is this a bug in glibc printf?

没有

printf("%ld\n", INT32_MIN);2147483648

有一种简单的方法可以做到这一点。函数的第二个 integer/pointer 参数应该在 64 位寄存器 RCX 中传递。 INT32_MIN 是一个 32 位 int,位模式为 0x80000000,因为这是 −2,147,483,648 的二进制补码模式。在 64 位寄存器中传递 32 位值的规则是它在低 32 位中传递,高位不被调用的例程使用。这次调用,0x80000000被放到了低32位,高位刚好被置零。

然后 printf 检查格式字符串并需要 64 位 long。因此它会在 RCX 中查找 64 位整数。当然,传递一个64位整数的规则是要使用整个寄存器,所以printf取所有64位,0x0000000080000000。这是 +2,147,483,468 的位模式,所以 printf 打印 2147483648.

当然,C 标准没有定义行为,因此可能会发生其他事情,但对于您观察到的实例中确实发生的情况,这很可能是这种情况。

printf("%d\n", INT16_MIN);-32768

由于 int 在您的 C 实现中是 32 位,int16_tINT16_MIN 会自动提升为 int 用于函数调用,因此这传递了一个 int,而 %d 需要一个 int,所以没有不匹配,并且打印了正确的值。

类似地,问题中的其他 printf 调用具有与转换规范匹配的参数(考虑到 int16_t 的特定定义等在您的 C 实现中;它们可能在其他方面不匹配),所以它们的值被正确打印。