为啥在数组中的值的情况下比较的工作方式不同
Posted
技术标签:
【中文标题】为啥在数组中的值的情况下比较的工作方式不同【英文标题】:Why does compare works differently in the case of value from the array为什么在数组中的值的情况下比较的工作方式不同 【发布时间】:2020-10-20 18:45:03 【问题描述】:我正在使用内联 C 程序集,但我不明白为什么 cmp 命令在这两种情况下不起作用。
我有 C 函数:
int array_max(const int input_array[], const int array_size);
在main中我创建了数组并将其传递给函数
int input_array[] = 50,10,3,70,5;
printf("%d\n", array_max(input_array, 5));
函数定义写在汇编中
__asm__
(
"array_max:;"
" xor %rdx, %rdx;"
" movq $70, %rax;"
" movq $50, %rbx;"
" cmp %rbx, %rax;"
" jge gr_eq;"
" movq %rbx, %rax;"
"gr_eq:;"
" ret"
);
在这种情况下是返回值 70
__asm__
(
"array_max:;"
" xor %rdx, %rdx;"
" movq $70, %rax;"
" movq (%rdi,%rdx,4), %rbx;" // input_array[0] == 50
" cmp %rbx, %rax;"
" jge gr_eq;"
" movq %rbx, %rax;"
"gr_eq:;"
" ret"
);
但这种情况下 50...
sameone 可以帮助我如何将传递的数组值与寄存器中的值进行比较?并解释一下为什么这个实现不起作用?
【问题讨论】:
非常感谢!当我将它存储到 ECX 或 EDX 并与 EAX 进行比较时,它就可以工作了! RBX 保留调用;选择一个不同的寄存器。 (以及固定负载的操作数大小)。例如cmpl $70, (%rdi, %rdx, 4)
。请注意,它的比较顺序与您所做的相反,加载结果作为右手操作数。
【参考方案1】:
问题是您有一个 32 位有符号 int
s 数组,并且您正在从数组中读取 64 位值(当每个元素的大小仅为 4 个字节时)并作为 64 位有符号整数进行比较.如果您使用 GDB 之类的调试器单步调试代码,您应该会看到发生了什么。请注意,RBX 是AMD64 System V ABI 中的非易失性寄存器,因此它的值必须保存在函数中。您可以通过不使用任何 RBX、RBP 或 R12-R15 寄存器来避免这种情况。
最简单的改变就是这样做:
__asm__
(
"array_max:;"
" xor %rdx, %rdx;"
" mov $70, %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)未定义。你必须确保你sign extend the 32-bit value to a 64-bit signed value。更好的是将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 $0x80000000, %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
数字标签
数字标签由零 (0) 范围内的单个数字组成 通过九 (9) 个冒号 (:)。仅使用数字标签 供本地参考,不包含在目标文件的符号中 桌子。数字标签的范围有限,可以重新定义 反复。
当数字标签用作参考时(作为指令 例如,操作数)、后缀 b(“向后”)或 f(“向前”) 应添加到数字标签。对于数字标签 N, 参考 Nb 指的是之前定义的最近的标签 N 参考,参考 Nf 指的是最近的标签 N 定义 在引用之后。
【讨论】:
什么是“1:\n\t”、“2:\n\t”和\n\t?我只学习了 3 天的内联汇编,很难找到有关它的资源,而且我在任何地方都没有看到这些“命令”。 @Wh1stlee:我用一些额外的细节更新了我的答案。\n\t
和 \n
可以结束类似 ;
的指令,但它改进了格式,如底部注释部分所述。在您看到\n\t
或\n
的任何地方,如果这对您更有意义,您可以将其替换为;
。
@Wh1stlee:有关链接,请参见 ***.com/tags/inline-assembly/info。但是,当您只是在全局范围内使用 asm 语句来定义整个函数(而不是简单地将其放在单独的文件中)时,inline asm 的大部分复杂性都不适用(约束告诉编译器如何围绕扩展 Asm 语句进行优化)。您所处理的只是普通的 GAS 语法,在这种情况下是 x86-64 和 AT&T 语法。 sourceware.org/binutils/docs/as/Symbol-Names.html GNU 中的文档 as
手册文档数字本地标签。以上是关于为啥在数组中的值的情况下比较的工作方式不同的主要内容,如果未能解决你的问题,请参考以下文章
Blazor DI 在 .razor 和商务类中的工作方式不同。为啥?
为啥此相关子查询在 Oracle 和 SQL Server 中的工作方式不同