为什么在数组中的值的情况下比较工作不同

Why does compare works differently in the case of value from the array

我正在使用内联 C 程序集,但我不明白为什么 cmp 命令对这两种情况不起作用。

我有 C 函数:

int array_max(const int input_array[], const int array_size);

在主体中我创建了数组并将其传递给函数

int input_array[] = {50,10,3,70,5};

printf("%d\n", array_max(input_array, 5));

函数定义是用汇编写的

__asm__
(
"array_max:;"
"   xor     %rdx, %rdx;"
"   movq    , %rax;"
"   movq    , %rbx;" 

"   cmp     %rbx, %rax;"
"   jge     gr_eq;"
"   movq    %rbx, %rax;"
    
"gr_eq:;"
"   ret"
);

在这种情况下是 return 值 70

__asm__
(
"array_max:;"
"   xor     %rdx, %rdx;"
"   movq    , %rax;"
"   movq    (%rdi,%rdx,4), %rbx;" // input_array[0] == 50

"   cmp     %rbx, %rax;"
"   jge     gr_eq;"
"   movq    %rbx, %rax;"
    
"gr_eq:;"
"   ret"
);

但这种情况下 50...

sameone 可以帮助我如何将传递的数组值与寄存器中的值进行比较吗?并解释一下为什么这个实现不起作用?

问题是你有一个 32 位有符号数组 int 并且你正在从数组中读取 64 位值(当每个元素的大小只有 4 个字节时)并比较为 64位有符号整数。如果您使用像 GDB 这样的调试器单步执行代码,您应该会看到发生了什么。预先警告 RBXAMD64 System V ABI 中的 non-volatile 寄存器,因此它的值必须保存在函数中。您可以通过不使用任何 RBXRBPR12-R15 寄存器来避免这种情况。

最简单的更改是这样做:

__asm__
    (
    "array_max:;"
    "   xor     %rdx, %rdx;"
    "   mov     , %eax;"
    "   mov     (%rdi,%rdx,4), %ecx;"
    "   cmp     %ecx, %eax;"
    "   jge     gr_eq;"
    "   mov     %ecx, %eax;"

    "gr_eq:;"
    "   ret"
    );

当您继续前进时,您应该意识到您正在将 arr_size 作为 const int 传递,这是一个 32 位有符号数。作为将通过 RSI 传递的第二个参数。传递 32 位值时,寄存器的高 32 位(如 RSI)未定义。您必须确保 。更好的方法是将 const int 更改为 const size_t,这在 64 位代码中将是一个 64 位无符号值。这使得调用者有责任将值放入完整寄存器。

继续使用的代码版本const int arr_size;确保数组大小大于 0;从最后一个元素到第一个元素遍历数组可以这样完成:

__asm__
    (
    "array_max:\n\t"
    "   movsx %esi, %rsi\n\t"          # Sign extend arr_size(ESI) 32-bit value to 64-bit
    "   mov   [=11=]x80000000, %eax\n\t"   # EAX starts as lowest signed integer
    "   jmp   2f\n"
    "1:\n\t"
    "   mov   (%rdi,%rsi,4), %ecx\n\t" # Read current 32-bit signed value from array
    "   cmp   %eax, %ecx\n\t"
    "   jl    2f\n\t"                  # Is current element(ECX) < max value(EAX)?
    "   mov   %ecx, %eax\n"            # If not, update max value with current element
    "2:\n\t"
    "   dec   %rsi\n\t"                # Work backwards through the array
    "   jge   1b\n\t"                  # Until we have finished processing 1st element
    "   ret"                           # Return max array value in EAX  
    );

其工作方式是将EAX中的初始最大值设置为最小的负值(0x80000000),并从数组末尾开始比较当前元素到最大值。如果当前元素 (ECX) 大于看到的最大值 (EAX) 则将最大值设置为当前元素。这一直持续到处理完数组的开头。


备注

  • 您可以进行一些改进,例如使用 CMOVcc 指令之一在没有分支的情况下使用当前元素设置最大值。它看起来像:

    "   cmp     %eax, %ecx\n\t"
    "   cmovg   %ecx, %eax\n"          # Set max value to current element if ECX>EAX
    

    而不是:

    "   cmp   %eax, %ecx\n\t"
    "   jl    2f\n\t"                  # Is current element(ECX) < max val(EAX)?
    "   mov   %ecx, %eax\n"            # If not, update max value with current element
    
  • 如果有人使用 GCC/CLANG 的 -S 选项查看生成的内容,我会使用 "\n\t"(换行符后跟制表符)来改进格式装配说明。您可以继续使用 ;

  • 我使用数字标签来确保所有标签都是唯一的,并且不会与 C 编译器可能生成的任何其他标签冲突。有关此功能的更多信息,请参见 here

    Numeric Labels

    A numeric label consists of a single digit in the range zero (0) through nine (9) followed by a colon (:). Numeric labels are used only for local reference and are not included in the object file's symbol table. Numeric labels have limited scope and can be redefined repeatedly.

    When a numeric label is used as a reference (as an instruction operand, for example), the suffixes b (“backward”) or f (“forward”) should be added to the numeric label. For numeric label N, the reference Nb refers to the nearest label N defined before the reference, and the reference Nf refers to the nearest label N defined after the reference.