Return 特定位置的指针 - 程序集

Return a pointer at a specific position - Assembly

我是 Assembly 的初学者,我有一个简单的问题。 这是我的代码:

BITS 64                     ; 64−bit mode
global strchr               ; Export 'strchr'

SECTION .text           ; Code section
strchr:
    mov rcx, -1
.loop:
    inc rcx
    cmp byte [rdi+rcx], 0
    je exit_null
    cmp byte [rdi+rcx], sil
    jne .loop
    mov rax, [rdi+rcx]
    ret

exit_null:
    mov rax, 0
    ret

此编译但不起作用。如您所见,我想重现函数 strchr。当我用 printf 测试我的函数时它崩溃了(问题不是测试)。 我知道我可以 INC rdi 直接移动到 rdi 参数和 return 它在我想要的位置。 但我只想知道是否有办法 return rdi 在 rcx 位置修复我的代码并可能改进它。

您的函数 strchr 似乎需要两个参数:

  1. 指向 RDI
  2. 中的字符串的指针
  3. 指向 RSI 中字符的指针。

Register rcx 用作字符串内部的索引?在这种情况下,您应该使用 al 而不是 cl。请注意,您不限制搜索大小。当RSI引用的字符在字符串中找不到时,很可能会触发异常。也许你应该测试从 [rdi+rcx] 加载的 al 并在 al=0.

时停止进一步搜索

如果你想让它return指向字符第一次出现的指针 在字符串中,只是
mov rax,[rdi+rcx] 替换为 lea rax,[rdi+rcx].

您的代码(来自编辑版本 2)执行以下操作:

char* strchr ( char *p, char x ) {
   int i = -1;
   do {
      if ( p[i] == '[=10=]' ) return null;
      i++;
   } while ( p[i] != x );
   return * (long long*) &(p[i]);
}

正如@vitsoft 所说,您的意图是 return 一个指针,但在第一个 return (在汇编中)是 returning 从地址加载的单个四字找到的字符,8 个字符而不是地址。


在循环中间递增是不正常的。从-1 开始索引也很奇怪。在第一次迭代中,循环继续条件查看 p[-1],这不是一个好主意,因为它不是要求您搜索的字符串的一部分。如果该字节恰好是 nul 字符,它将立即停止搜索。

如果您等到两个测试都执行完毕才递增,那么您就不会引用 p[-1],并且您也可以从 0 开始索引,这更常见。


你可以考虑将字符捕获到一个寄存器中,而不是使用复杂的寻址方式三次。

此外,您可以在 rdi 中推进指针并完全放弃索引变量。

这是 C:

char* strchr ( char *p, char x ) {
    for(;;) {
        char c = *p;
        if ( c == '[=11=]' )
            break;
        if ( c == x )
            return p;
        p++;
   }
   return null;
}

感谢你的帮助,我终于做到了! 感谢 Erik 的回答,我修正了一个愚蠢的错误。我正在将 str[-1] 与 NULL 进行比较,所以它出错了。 在 vitsoft 的回答下,我将 mov 切换为 lea 并且它起作用了! 这是我的代码:

strchr:
    mov rcx, -1
.loop:
    inc rcx
    cmp byte [rdi+rcx], 0
    je exit_null
    cmp byte [rdi+rcx], sil
    jne .loop
    lea rax, [rdi+rcx]
    ret

exit_null:
    mov rax, 0
    ret

当前版本中剩下的唯一错误是加载 8 个字节的 char 数据作为 return 值,而不是仅仅进行指针数学运算,使用 mov 而不是 lea。 (经过各种编辑删除并添加了不同的错误,反映在谈论不同代码的不同答案中)。

但这是 over-complicated 并且效率低下(两次加载,索引寻址模式,当然还有设置 RCX 的额外指令)。
只需增加指针,因为这就是您想要的return。

如果您要一次循环 1 个字节而不是使用 SSE2 一次检查 16 个字节,strchr 可以像这样简单:

;; BITS 64 is useless unless you're writing a kernel with a mix of 32 and 64-bit code
;; otherwise it only lets you shoot yourself in the foot by putting 64-bit machine code in a 32-bit object file by accident.

global mystrchr
mystrchr:
 .loop:                     ; do {
    movzx  ecx, byte [rdi]   ; c = *p;
    cmp    cl, sil           ; if (c == needle) return p;
    je     .found
    inc    rdi               ; p++
    test   cl, cl
    jnz    .loop            ; }while(c != 0)

      ;; fell out of the loop on hitting the 0 terminator without finding a match
    xor    edi, edi         ; p = NULL
    ; optionally an extra ret here, or just fall through

 .found:
    mov    rax, rdi         ; return p
    ret

我在 end-of-string 之前检查了一个匹配项,所以我仍然有 un-incremented 指针,而不必在“找到的”return 路径中递减它。如果我使用 inc 开始循环,我可以使用 [rdi - 1] 寻址模式,仍然避免使用单独的计数器。这就是为什么我调换了循环底部的分支顺序与问题中的代码的顺序。

由于我们要将字符与 SIL 和零进行两次比较,因此我将其加载到寄存器中。这可能不会 运行 在现代 x86-64 上更快,它可以 运行 每个时钟 2 个负载以及 2 个分支(只要最多采用其中一个)。

一些 Intel CPU 可以 cmp reg,mem / jcc into a single load+compare-and-branch uop for the front-end, at least when the memory addressing mode is simple, not indexed. But not cmp [mem], imm/jcc, so we're not costing any extra uops for the front-end on Intel CPUs by separately loading into a register. (With movzx to mov cl, [rdi])


请注意,如果您的调用程序也是用汇编语言编写的,则很容易 return 多个值,例如状态和指针(在 not-found 的情况下,终止 0 可能会有用)。 Many C standard library string functions are badly designed,特别是 strcpy 帮助调用者避免重做 length-finding 工作。

特别是在带有 SIMD 的现代 CPU 上,显式长度非常有用:real-world strchr 实现将检查对齐,或检查给定指针是否在 16 个字节的范围内一页的结尾。但是 memchr 不必这样做,如果大小 >= 16:它可以只执行 movdqu 加载和 pcmpeqb.

请参阅 for details and a link to glibc strlen's hand-written asm. Also 了解 real-world 实现,例如 glibc 使用 pcmpeqb / pmovmskb。 (也许 pminub 用于 0 终止符检查以展开多个向量。)

SSE2 比 non-tiny 字符串的答案中的代码快大约 16 倍。对于非常 的字符串,您可能会遇到内存瓶颈并且“仅”快 8 倍。