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
似乎需要两个参数:
- 指向
RDI
和 中的字符串的指针
- 指向
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 倍。
我是 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
似乎需要两个参数:
- 指向
RDI
和 中的字符串的指针
- 指向
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
.
请参阅 strlen
's hand-written asm. Also pcmpeqb
/ pmovmskb
。 (也许 pminub
用于 0 终止符检查以展开多个向量。)
SSE2 比 non-tiny 字符串的答案中的代码快大约 16 倍。对于非常 大 的字符串,您可能会遇到内存瓶颈并且“仅”快 8 倍。