1

我知道函数参数被填充到目标字长,但是用什么呢?

特别是在 x86 Linux GNU 工具链的上下文中,这些函数返回什么?

int iMysteryMeat(short x)
{
    return *((int *)&x);
}
unsigned uMysteryMeat(unsigned short x)
{
    return *((unsigned *)&x);
}

问题是,在汇编中手动编码函数时,是否有必要在“大”上下文中使用它们之前通过屏蔽或符号扩展来消除“小”参数(andl, imull)。

我也对这种情况是否有更通用或跨平台的标准感兴趣。

4

3 回答 3

2

这取决于 ABI。ABI 需要指定小参数是由调用者扩展还是由被调用者扩展(以及如何扩展)的选择。不幸的是,这部分 ABI 经常没有明确说明,导致不同的编译器做出不同的选择。因此,为了防止使用不同的旧版编译器编译的代码之间不兼容,大多数现代编译器(我特别了解gccon 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(通过尾调用)之前扩展其参数。

于 2012-09-05T22:26:29.540 回答
0
int iMysteryMeat(short x)
{
    return *((int *)&x);
}

这是 C 中未定义的行为,这违反了别名规则,也可能违反对齐要求。short不要这样做。

于 2012-09-05T22:10:20.190 回答
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 的 比较a()),它实际上将 x 移动到未初始化的堆栈空间中movw,无论main()给出什么,都会呈现返回值的上半部分垃圾。

所以,再次,正如 ouah 所说,永远不要在 C 中这样做,在汇编中(或者一般来说,真的),总是对你的输入进行消毒

于 2012-09-05T23:08:17.557 回答