我知道函数参数被填充到目标字大小,但是用什么?
特别是在x86 Linux GNU工具链的上下文中,这些函数返回了什么?
int iMysteryMeat(short x)
{
return *((int *)&x);
}
unsigned uMysteryMeat(unsigned short x)
{
return *((unsigned *)&x);
}
问题在于,当在汇编中对函数进行手动编码时,是否有必要通过屏蔽或对其进行符号扩展来消除“小”参数,然后才能在“大”上下文中使用它们andl
{{ 1}})。
我也对这种情况是否有更多通用或跨平台标准感兴趣。
答案 0 :(得分:2)
这取决于ABI。 ABI需要指定调用者或被调用者(以及如何)扩展小参数的选择。不幸的是,ABI的这一部分经常被指定,导致不同编译器的不同选择。因此,为了防止使用不同的遗留编译器编译的代码之间的不兼容性,大多数现代编译器(我特别知道gcc
上的i386
)在谨慎方面是错误的,并且两者兼而有之。
int a(short x) {
return x;
}
int b(int x);
int c(short x) {
b(x);
}
gcc -m32 -O3 -S tmp.c -o tmp.s
_a:
pushl %ebp
movl %esp, %ebp
movswl 8(%ebp),%eax
leave
ret
_c:
pushl %ebp
movl %esp, %ebp
movswl 8(%ebp),%eax
movl %eax, 8(%ebp)
leave
jmp _b
请注意,a
不会对其参数采用任何扩展规则,而是对其进行扩展。类似地,c
确保在将其传递给b
之前扩展其参数(通过尾调用)。
答案 1 :(得分:0)
int iMysteryMeat(short x)
{
return *((int *)&x);
}
这是C中未定义的行为,这违反了别名规则,也可能违反了对齐要求。在short
中不要这样做。
答案 2 :(得分:0)
虽然基思的回答与我的问题精神一致,但根据亚历克斯的要求,我以为我会为自己试一试。
有趣的是,在这种情况下,我的例子的更直接的答案是"垃圾"。
#include <stdio.h>
int iMysteryMeat(short x)
{
return *((int *)&x);
}
unsigned uMysteryMeat(unsigned short x)
{
return *((unsigned *)&x);
}
int main()
{
printf("iMeat: 0x%08x\n", iMysteryMeat(-23));
printf("uMeat: 0x%08x\n", uMysteryMeat(-23));
return 0;
}
gcc -m32 -S meat.c
iMysteryMeat:
pushl %ebp
movl %esp, %ebp
subl $4, %esp
movl 8(%ebp), %eax
movw %ax, -4(%ebp)
leal -4(%ebp), %eax
movl (%eax), %eax
leave
ret
uMysteryMeat:
pushl %ebp
movl %esp, %ebp
subl $4, %esp
movl 8(%ebp), %eax
movw %ax, -4(%ebp)
leal -4(%ebp), %eax
movl (%eax), %eax
leave
ret
./a.out
iMeat: 0x0804ffe9
uMeat: 0x0043ffe9
正如您所看到的,不仅通常的符号扩展协议被覆盖(即与Keith&#39; s a()
比较),它实际上将x移动到未初始化的堆栈空间movw
,渲染无论main()
给出什么,返回值垃圾的上半部分。
所以,再次,正如ouah所说,永远不会在C中进行此操作,在汇编中(或者一般来说,真的如此),始终对您的输入进行消毒。