为什么 `printf` 和 `%hu%` 从堆栈中获取 4 个字节而不是 2 个字节?

Why does `printf` with `%hu%` takes 4 bytes from stack instead 2?

我正在学习汇编基础知识并且我经常使用 printf(我认为我从 C/C++ 经验中知道的足够多)。我遇到了 2 个字节(16 位)值的奇怪东西:

在 32 位模式下,当使用 printf("%hu", (unsigned short)123) 时,我认为它会从堆栈中取出 6 个字节(4 个用于格式字符串的地址,2 个用于值)。同时实际​​情况是,地址为 4,价值为 4。

这是为什么? %hu 等于 %u 吗?或者它只是剥离值的高 2 个字节?


一些代码(从C到ASM编译,-O0): https://godbolt.org/z/x167zdon1

这个有效:

        mov     eax, 123
        push    eax
        push    format ; "%hu"
        call    _printf
        add     esp, 4 + 4    ; works

但我认为这应该:

        mov     ax, 123
        push    eax
        push    format ; "%hu"
        call    _printf
        add     esp, 4 + 2    ; doesn't work: print gibberish, as it takes 2 bytes more to display the value....

doesn't work ...

当然:您执行了 push eax,将 4 个字节压入堆栈。为此,你必须再次从堆栈中删除 4 个字节。

I came across weird thing with 2 bytes (16 bits) values.

在 x86-32 代码中,堆栈通常应与 4 对齐。这意味着 esp 寄存器的值应为 4 的倍数。

出于这个原因,如果函数参数只是 16 字节甚至 8 字节的值,大多数调用约定都需要推送 32 位值。

Is %hu equal to just %u? Or is it just stripping high 2 bytes of the value?

这取决于调用约定和库实现:

当使用 C 编译器必须 push 32 位数字的调用约定时,如果参数是 16 位值,其高 16 位为 0,则库(printf 函数)可以%hu%u 相同的方式实现。

当使用调用约定,其中此类数字的高 16 位可能具有任何值时,printf 函数当然必须去除高 2 个字节。

编辑

实际上,您在 printf 示例中有 两个 效果:

  1. 正如我已经写过的,如果需要 charshort 参数,x86-32 编译器会将 32 位值传递给函数。
  2. 在参数数量可变的函数中(例如printf),charshort类型的“附加”参数必须转换为int 由编译器。

假设我们在 16 位 CPU 上工作,但数据类型 int 是 32 位长。我们有一些函数 void test(short x, ...);.

如果我们在这种情况下调用 test(a, a);,编译器会将第一个参数作为 16 位值传递(因为该参数的数据类型为 short),将第二个参数作为 32-位值(因为它是一个附加参数)。

printf() 的附加参数也是如此 - 假设 int 是 32 位数据类型。