现在,我只是想知道如何遍历一个字符串。如果代码没有意义,那是因为我将某些信息解释为错误的。最糟糕的是,我真的不知道自己在做什么。
strlen:
pushq %rbx
movq %rsi, %rbx
loop:
cmp $0x00, (%rdi, %rbx)
je end
inc %rbx
jmp loop
end:
movq %rbx, %rax
popq %rbx
ret
PS:有一个原因使我的标题看起来像是一个老头,第二次在他的计算机上尝试搜索“如何去google.com”。Superrrrnoob在这里试图学习一些汇编语言。我正在尝试为自己实现strlen函数。
只需inc %rbx
就可以增加指针值。 (%rbx)
使用其值作为存储器地址来取消引用该寄存器。在x86上,每个字节都有其自己的地址(此属性称为“可字节寻址的字节”),并且地址只是适合寄存器的整数。
ASCII字符串中的字符全为1字节宽,因此将指针加1将移至ASCII字符串中的下一个字符。 (在UTF-8的字符位于1..127代码点范围之外的一般情况下,这是不正确的,但是ASCII是UTF-8的子集。)
术语:ASCII码0
被称为NUL(一个L),而不是NULL。在C中,NULL是指针概念。 C样式的隐式长度字符串可以描述为0终止或NUL终止,但是“ null终止”正在滥用该术语。
您应该选择一个不同的寄存器(称为调用寄存器),因此您无需在函数中推送/弹出该寄存器。您的代码不会make任何函数调用,因此不需要将归纳变量保留在保留调用的寄存器中。
我没有在其他SO Q&A中找到一个good简单示例。它们或者像我在注释中链接的那样,在循环内有2个分支(包括一个无条件的jmp),或者它们浪费了增加指针和计数器的指令。在循环内使用索引寻址模式并不可怕,但是在某些CPU上效率较低,因此我仍然建议进行指针递增->在循环后减去end-start。
这就是我写一个最小的strlen的方法,它一次只检查1个字节(缓慢而简单)。我将循环本身保持很小,这是IMO总体上编写循环的一种好方法的合理示例。通常,保持代码紧凑会更容易理解asm中的函数。 (给它一个不同于strlen
的名称,这样您就可以测试它而无需gcc -fno-builtin-strlen
或其他名称。)
.globl simple_strlen
simple_strlen:
lea -1(%rdi), %rax # p = start-1 to counteract the first inc
.Lloop: # do {
inc %rax # ++p
cmpb $0, (%rax)
jne .Lloop # }while(*p != 0);
# RAX points at the terminating 0 byte = one-past-end of the real data
sub %rdi, %rax # return length = end - start
ret
strlen
的返回值是0
字节的数组索引=数据长度[[not包括终止符。
可以通过剥离第一个迭代或使用jmp
以cmp / jne进入循环的方式来避免在第一次加载之前偏移LEA / INC指令(在第一次cmp之前花费2个周期的等待时间),后公司Why are loops always compiled into "do...while" style (tail jump)?。
在LEMP之间增加cmp / jcc之间的LEA指针(例如cmp
; lea 1(%rax), %rax
; jne
)可能会更糟,因为它会破坏cmp / jcc的宏融合成单个uop。 (实际上,cmp $imm, (%reg)
/ jcc的宏融合在像Skylake这样的Intel CPU上都不会发生。cmp
微融合内存操作数。也许AMD融合了cmp / jcc。)此外,您将离开RAX 1比您想要的高的循环。
因此,(在Intel Sandybridge系列上)加载movzx
(又名movzbl
)并将字节零扩展到%ecx
和test %ecx, %ecx
/ jnz
就像循环条件一样有效。但是更大的代码大小。
大字符串每次检查1个字节的速度比SSE2慢16倍。如果您不打算最小化代码大小和简化性,请参阅Why is this code 6.5x slower with optimizations enabled?以获取使用SSE2的简单strlen XMM寄存器。 SSE2是x86-64的基准,因此,在提速时,应始终使用它,因为值得在asm中手工编写的内容。
Re:您更新的问题
,其中包含Why does rax and rdi work the same in this situation?中实施的错误端口RDI和RBX都保存指针。将它们加在一起不会成为有效地址!在您尝试移植的代码中,RCX(索引)在循环之前被初始化为零。但是您没有执行xor %ebx, %ebx
,而是执行了mov %rdi, %rbx
。单步执行代码时,使用调试器检查寄存器值。