带有lld、ld和d类型标识符的int变量的c-printf



正如我所认为的,printf中的%d将从堆栈中读取sizeof(int(,%ld将从堆栈读取sizeof(long(,以此类推。我写了这个代码片段:

##############printf1.c
#include <stdio.h>
int main()
{
int a = 1, b = 2;
long la = 1, lb = 2;
long long lla = 1, llb = 2;
printf("a=%d, b=%dn", a, b); 
printf("a=%ld, b=%ldn", a, b); 
printf("a=%lld, b=%lldn", a, b); 
printf("la=%d, lb=%dn", la, lb);
printf("la=%ld, lb=%ldn", la, lb);
printf("la=%lld, lb=%lldn", la, lb);
printf("lla=%d, llb=%dn", lla, llb);
printf("lla=%ld, llb=%ldn", lla, llb);
printf("lla=%lld, llb=%lldn", lla, llb);
return 0;
}

我机器的输出是:

$ ./printf1
a=1, b=2
a=1, b=2
a=1, b=2
la=1, lb=2
la=1, lb=2
la=1, lb=2
lla=1, llb=2
lla=1, llb=2
lla=1, llb=2

这是怎么发生的?我认为如果变量类型和类型标识符不匹配,应该会有一些垃圾输出。我的机器:

$ uname -a
Linux cu01 3.10.0-693.el7.x86_64 #1 SMP Tue Aug 22 21:09:27 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux

我的gcc:

$ gcc --version
gcc (GCC) 4.8.5 20150623 (Red Hat 4.8.5-16)

ps:我读过这篇文章:用lld、ld和d类型标识符打印一个size_t变量。但作者的问题并没有发生在我的测试中。至于作者的代码:

##############printf2.c
#include <stdio.h>
int main()
{
size_t temp;
temp = 100;
printf("lld=%lld, ld=%ld, u=%un", temp, temp, temp);
printf("ld=%ld, u=%u, lld=%lldn", temp, temp, temp);
return 0;
}

我的机器上的输出结果总是"正确"的:

$ ./printf2
lld=100, ld=100, u=100
ld=100, u=100, lld=100

有人能解释一下吗?非常感谢你的帮助!

之所以会发生这种情况,是因为这些值是如何传递给printf的,以及如何读取这些值。因此,首先,当使用d说明符将其传递给printf时,得到正确结果的原因是,它只读取8字节整数中的前4个字节,恰好数字1只占用一个字节,因此无论它读取多少字节,都会打印1。这里有一个简单的可视化方法。

以下是二进制中的长(和长-长(数字

00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
|-----------------------------------|

上面显示了使用d作为说明符传递时读取的位,如果将ldlld作为说明符传递,它将读取剩余的4个字节,但由于它们都是前导零,这显然不会影响打印结果。同样的概念也适用于数字2。尝试用LONG_MAX替换lalla的值,您会注意到当与d说明符一起使用时,它会打印-1。请注意,这种行为是未定义的,因为它取决于系统的端序,并且不会在所有系统上都发生,这正是系统中最有可能发生的情况。

现在,为什么这不破坏堆栈?如果我们对传递到堆栈的东西的大小不诚实,显然应该这样做,对吧?既然你在Linux系统上,你的机器使用的是system V abi,它在调用约定中指定,前几个参数将使用寄存器传递,所以在这种情况下,实际上没有任何东西被推到堆栈上,而是全部从寄存器中读取。如果你想了解更多信息,这里有更多信息https://wiki.osdev.org/Calling_Conventions

需要注意的是,这会在不同的机器上产生不同的结果,这显然就是为什么它被称为未定义行为,以及为什么你根本不应该依赖它或在代码中使用它,尤其是在这种情况下很容易不使用它。

最新更新