我知道有多种方法可以在程序集中返回值,

  1. rax个登记册中
  2. xmm0xmm1个登记册
  3. 在堆栈中(类似于c++中的返回优化),有时会使用rdi寄存器来保存调用者的堆栈地址,然后直接返回rdi指向的地址中的值

我在汇编中有以下代码,该代码应该计算一个3x 3矩阵乘以一个3D载体,然后返回一个浮点数形式的3维载体(每个4个字节,总共12个字节).

一切都很容易理解,直到最后一段代码返回值,这太奇怪了,我不明白它的目的.

假设3D值最终在xmm6xmm7xmm8中,代码如下:

注:该函数的开头和结尾没有sub rsp, xxxadd rsp, xxx,实际上在矩阵和相乘的计算过程中根本没有使用堆栈.

movss  [rsp-28h],xmm6
movss  [rsp-24h],xmm7
movss  [rsp-20h],xmm8

mov    eax,[rsp-24h]
shl    eax,20h
mov    ebx,[rsp-28h]
or     rax,rbx
mov    [rsp-18h],rax
movd   rcx,xmm8
mov    [rsp-10h],rcx

mov    ebx,eax
shr    rax,20h
mov    [rsp-28h],eax
mov    [rsp-24h],ebx
mov    [rsp-20h],ecx

movss  xmm1,[rsp-10h]
movsd  xmm0,[rsp-28h]

我try 在带有asm()的C程序中编译和运行这段代码,发现实际上它只是在3个地方复制了xmm6xmm7xmm8,一个在[rsp-28h],一个在[rsp-18h],一个在xmm1xmm0,都是相同的浮点值.

我知道xmm0xmm1最有可能是返回值,但[rsp-28h][rsp-18h]对于返回值有什么用处吗?为什么程序使用一种非常奇怪的方法(复制到堆栈,然后复制到rax个等寄存器,然后再次将它们复制回堆栈)来复制结果?

我问这个是因为来电代码不使用xmm0xmm1.我不确定我是否应该认为调用者只是放弃返回值,或者返回值实际上在调用者堆栈中的某个地方.

谢谢.

推荐答案

Caveat:这可能不完全是你所说的,但是.

如果函数返回struct by value,则在返回过程中使用rsp/rbp

  1. 调用者必须在其堆栈帧中为返回值保留一个[mat]区域.
  2. 被调用函数的第一个参数是指向该区域的隐式/隐藏指针.
  3. 被调用的函数将将返回值复制到该指针指向的区域
  4. 然后,调用者将从临时区域复制到最终变量.

考虑以下来源:

#include <stdio.h>

struct obj {
    int x[10];
};

struct obj
fill(int arg)
{
    struct obj obj = {
        .x = { 4, 5, 6 }
    };

    printf("fill: arg=%d\n",arg);

    return obj;
}

void
objprint(const struct obj *obj)
{

    for (int i = 0;  i < 10;  ++i)
        printf(" %d",obj->x[i]);
    printf("\n");
}

struct obj obj = {
    .x = { 1, 2, 3 }
};

int
main(void)
{

    objprint(&obj);
    obj = fill(37);
    objprint(&obj);

    return 0;
}

为了清楚起见,我们用-m32来编译,但x86_64的原理相同.我们用:

cc \
-fno-inline-small-functions \
-fno-inline-functions-called-once \
-fno-inline-functions \
-fomit-frame-pointer \
-S -fverbose-asm -O2 -m32 retval2.c

这是[编辑]汇编器输出.堆栈框架之间有相当多的移动.

    .file   "retval2.c"
    .text
    .section    .rodata.str1.1,"aMS",@progbits,1
.LC0:
    .string "fill: arg=%d\n"
    .text
    .p2align 4,,15
    .globl  fill
    .type   fill, @function
fill:
.LFB11:
    .cfi_startproc
    pushl   %ebx    #
    .cfi_def_cfa_offset 8
    .cfi_offset 3, -8
    subl    $16, %esp   #,
    .cfi_def_cfa_offset 24
# retval2.c:9: {
    movl    24(%esp), %ebx  # .result_ptr, .result_ptr
# retval2.c:14:     printf("fill: arg=%d\n",arg);
    pushl   28(%esp)    # arg
    .cfi_def_cfa_offset 28
    pushl   $.LC0   #
    .cfi_def_cfa_offset 32
    call    printf  #
# retval2.c:16:     return obj;
    movl    $4, (%ebx)  #, MEM[(struct obj *)&<retval>]
# retval2.c:17: }
    movl    %ebx, %eax  # .result_ptr,
# retval2.c:16:     return obj;
    movl    $5, 4(%ebx) #, MEM[(struct obj *)&<retval> + 4B]
    movl    $6, 8(%ebx) #, MEM[(struct obj *)&<retval> + 8B]
    movl    $0, 12(%ebx)    #, MEM[(struct obj *)&<retval> + 12B]
    movl    $0, 16(%ebx)    #, MEM[(struct obj *)&<retval> + 16B]
    movl    $0, 20(%ebx)    #, MEM[(struct obj *)&<retval> + 20B]
    movl    $0, 24(%ebx)    #, MEM[(struct obj *)&<retval> + 24B]
    movl    $0, 28(%ebx)    #, MEM[(struct obj *)&<retval> + 28B]
    movl    $0, 32(%ebx)    #, MEM[(struct obj *)&<retval> + 32B]
    movl    $0, 36(%ebx)    #, MEM[(struct obj *)&<retval> + 36B]
# retval2.c:17: }
    addl    $24, %esp   #,
    .cfi_def_cfa_offset 8
    popl    %ebx    #
    .cfi_restore 3
    .cfi_def_cfa_offset 4
    ret $4      #
    .cfi_endproc
.LFE11:
    .size   fill, .-fill
    .section    .rodata.str1.1
.LC1:
    .string " %d"
    .text
    .p2align 4,,15
    .globl  objprint
    .type   objprint, @function
objprint:
.LFB12:
    .cfi_startproc
    pushl   %esi    #
    .cfi_def_cfa_offset 8
    .cfi_offset 6, -8
    pushl   %ebx    #
    .cfi_def_cfa_offset 12
    .cfi_offset 3, -12
    subl    $4, %esp    #,
    .cfi_def_cfa_offset 16
# retval2.c:21: {
    movl    16(%esp), %ebx  # obj, ivtmp.15
    leal    40(%ebx), %esi  #, _16
    .p2align 4,,10
    .p2align 3
.L5:
# retval2.c:24:         printf(" %d",obj->x[i]);
    subl    $8, %esp    #,
    .cfi_def_cfa_offset 24
    pushl   (%ebx)  # MEM[base: _14, offset: 0B]
    .cfi_def_cfa_offset 28
    addl    $4, %ebx    #, ivtmp.15
    pushl   $.LC1   #
    .cfi_def_cfa_offset 32
    call    printf  #
# retval2.c:23:     for (int i = 0;  i < 10;  ++i)
    addl    $16, %esp   #,
    .cfi_def_cfa_offset 16
    cmpl    %esi, %ebx  # _16, ivtmp.15
    jne .L5 #,
# retval2.c:25:     printf("\n");
    movl    $10, 16(%esp)   #,
# retval2.c:26: }
    addl    $4, %esp    #,
    .cfi_def_cfa_offset 12
    popl    %ebx    #
    .cfi_restore 3
    .cfi_def_cfa_offset 8
    popl    %esi    #
    .cfi_restore 6
    .cfi_def_cfa_offset 4
# retval2.c:25:     printf("\n");
    jmp putchar #
    .cfi_endproc
.LFE12:
    .size   objprint, .-objprint
    .section    .text.startup,"ax",@progbits
    .p2align 4,,15
    .globl  main
    .type   main, @function
main:
.LFB13:
    .cfi_startproc
    leal    4(%esp), %ecx   #,
    .cfi_def_cfa 1, 0
    andl    $-16, %esp  #,
    pushl   -4(%ecx)    #
    pushl   %ebp    #
    .cfi_escape 0x10,0x5,0x2,0x75,0
    movl    %esp, %ebp  #,
    pushl   %ecx    #
    .cfi_escape 0xf,0x3,0x75,0x7c,0x6
    subl    $64, %esp   #,
# retval2.c:36:     objprint(&obj);
    pushl   $obj    #
    call    objprint    #
# retval2.c:37:     obj = fill(37);
    leal    -56(%ebp), %eax #, tmp88
    popl    %edx    #
    popl    %ecx    #
    pushl   $37 #
    pushl   %eax    # tmp88
    call    fill    #
    movl    -56(%ebp), %eax #, tmp91
# retval2.c:38:     objprint(&obj);
    pushl   $obj    #
# retval2.c:37:     obj = fill(37);
    movl    %eax, obj   # tmp91, obj
    movl    -52(%ebp), %eax #, tmp93
    movl    %eax, obj+4 # tmp93, obj
    movl    -48(%ebp), %eax #, tmp95
    movl    %eax, obj+8 # tmp95, obj
    movl    -44(%ebp), %eax #, tmp97
    movl    %eax, obj+12    # tmp97, obj
    movl    -40(%ebp), %eax #, tmp99
    movl    %eax, obj+16    # tmp99, obj
    movl    -36(%ebp), %eax #, tmp101
    movl    %eax, obj+20    # tmp101, obj
    movl    -32(%ebp), %eax #, tmp103
    movl    %eax, obj+24    # tmp103, obj
    movl    -28(%ebp), %eax #, tmp105
    movl    %eax, obj+28    # tmp105, obj
    movl    -24(%ebp), %eax #, tmp107
    movl    %eax, obj+32    # tmp107, obj
    movl    -20(%ebp), %eax #, tmp109
    movl    %eax, obj+36    # tmp109, obj
# retval2.c:38:     objprint(&obj);
    call    objprint    #
# retval2.c:41: }
    movl    -4(%ebp), %ecx  #,
    .cfi_def_cfa 1, 0
    addl    $16, %esp   #,
    xorl    %eax, %eax  #
    leave
    .cfi_restore 5
    leal    -4(%ecx), %esp  #,
    .cfi_def_cfa 4, 4
    ret
    .cfi_endproc
.LFE13:
    .size   main, .-main
    .globl  obj
    .data
    .align 32
    .type   obj, @object
    .size   obj, 40
obj:
# x:
    .long   1
    .long   2
    .long   3
    .zero   28
    .ident  "GCC: (GNU) 8.3.1 20190223 (Red Hat 8.3.1-2)"
    .section    .note.GNU-stack,"",@progbits

请注意:

尽管main做到了:obj = fill(37);,但它传递了指向其堆栈帧上temp区域的指针,而不是传递指向lvalue obj的指针.

呼叫者将临时区域手动复制到左值.

C++相关问答推荐

%p与char* 等组合缺少的GCC Wform警告

有效地计算由一组点构成的等边三角形和等腰三角形的数量

从组播组地址了解收到的数据包长度

堆栈在作用域阻塞后会被释放吗?

创建一个fork导致fget无限地重新读取文件

在列表中插入Int指针(C)

以下声明和定义之间的区别

如何捕捉只有换行符或空格字符缓冲区的边缘大小写

实现简单字典时C语言中的段错误

Flose()在Docker容器中抛出段错误

如何在VS 2022中正确安装额外的C头文件

pthread_create的用法

如何使这个While循环在新行上结束

生产者消费者计数器意外输出的C代码

在下面的C程序中,.Ap0是如何解释的?

为什么一个在线编译器拒绝这个VLA代码,而本地的Apple clang却不拒绝;t?

C struct 中的冒泡排序

中位数和众数不正确

使用共享变量同步多线程 C 中的函数

为什么创建局部变量的指针需要过程在堆栈上分配空间?