为什么 `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
示例中有 两个 效果:
- 正如我已经写过的,如果需要
char
或 short
参数,x86-32 编译器会将 32 位值传递给函数。
- 在参数数量可变的函数中(例如
printf
),char
或short
类型的“附加”参数必须转换为int
由编译器。
假设我们在 16 位 CPU 上工作,但数据类型 int
是 32 位长。我们有一些函数 void test(short x, ...);
.
如果我们在这种情况下调用 test(a, a);
,编译器会将第一个参数作为 16 位值传递(因为该参数的数据类型为 short
),将第二个参数作为 32-位值(因为它是一个附加参数)。
printf()
的附加参数也是如此 - 假设 int
是 32 位数据类型。
我正在学习汇编基础知识并且我经常使用 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
示例中有 两个 效果:
- 正如我已经写过的,如果需要
char
或short
参数,x86-32 编译器会将 32 位值传递给函数。 - 在参数数量可变的函数中(例如
printf
),char
或short
类型的“附加”参数必须转换为int
由编译器。
假设我们在 16 位 CPU 上工作,但数据类型 int
是 32 位长。我们有一些函数 void test(short x, ...);
.
如果我们在这种情况下调用 test(a, a);
,编译器会将第一个参数作为 16 位值传递(因为该参数的数据类型为 short
),将第二个参数作为 32-位值(因为它是一个附加参数)。
printf()
的附加参数也是如此 - 假设 int
是 32 位数据类型。