【发布时间】:2017-11-16 00:15:01
【问题描述】:
给定变量:
var1 db "abcdefg", NULL
如何执行循环来导航每个字母?在 C++ 中,您会在循环内执行类似 var[x] 的操作,然后每次递增 x。有什么想法吗?
【问题讨论】:
标签: arrays string assembly x86 c-strings
给定变量:
var1 db "abcdefg", NULL
如何执行循环来导航每个字母?在 C++ 中,您会在循环内执行类似 var[x] 的操作,然后每次递增 x。有什么想法吗?
【问题讨论】:
标签: arrays string assembly x86 c-strings
在 C 和 C++ 中,字符串以 NUL 结尾。这意味着将一个 ASCII NUL 字符 (0) 添加到字符串的末尾,以便代码可以判断字符串的结束位置。 strlen 函数遍历字符串,从头开始,一直循环,直到遇到这个 NUL 字符。当它找到 NUL 时,它知道这是字符串的结尾,并将从开头到 NUL 的字符数作为字符串的长度返回。
字符串字面量(双引号中的内容)由 C/C++ 编译器自动以 NUL 结尾,因此:
"abcdefg"
等价于下面的数组:
{'a', 'b', 'c', 'd', 'e', 'f', 'g', 0}
我之所以提到这一点,是因为 Peter Rader 在他的回答中提出了这一建议,而您并没有真正理解他在说什么。但是,您似乎已经知道这一点,因为您在程序集声明中将 NUL 字符附加到您的字符串:
var1 db "abcdefg", NULL
现在,我们通常不会为此使用标识符 NULL。尤其是在 C 中,NULL 被定义为空指针。我们只使用字面量 0,因此定义将是:
var1 db "abcdefg", 0
但假设 NULL 在某处定义为 0,您的代码可能有效。
所以你的设置是正确的。现在你需要做的就是编写你的循环:
mov edx, OFFSET var1 ; get starting address of string
Loop:
mov al, BYTE PTR [edx] ; get next character
inc edx ; increment pointer
test al, al ; test value in AL and set flags
jz Finished ; AL == 0, so exit the loop
; Otherwise, AL != 0, so we fell through.
; Here, you can do do something with the character in AL.
; ...
jmp Loop ; keep looping
Finished:
您说您熟悉CMP 指令。在上面的代码中,我使用了TEST 而不是CMP。你也可以这样写:
cmp al, 0
但是
test al, al
效率稍高一些,因为它是一条较小的指令,所以我只是习惯于在将寄存器的值与 0 进行比较的特殊情况下这样编写它。编译器也会生成此代码,所以熟悉一下就好了。
Bonus chatter:表示字符串的另一种方法是将其长度(以字符为单位)与字符串本身一起存储。这就是 Pascal 语言传统上所做的。这样,您就不需要在字符串末尾使用特殊的 NUL 标记字符。相反,声明应该是这样的:
var1 db 7, "abcdefg"
每个字符串的第一个字节是它的长度。 This has various advantages over the C style,即您不必遍历整个字符串来确定其长度。当然,主要缺点是字符串的长度限制为 255 个字符,因为这就是 BYTE 的全部内容。
无论如何,在预先知道长度的情况下,您不再检查 NUL 字符,您只需迭代与字符串中的字符相同的次数:
mov edx, OFFSET var1 ; get starting address of string
mov cl, BYTE PTR [edx] ; get length of string
Loop:
inc edx ; increment pointer
dec cl ; decrement length
mov al, BYTE PTR [edx] ; get next character
jz Finished ; CL == 0, so exit the loop
; Do something with the character in AL.
; ...
jmp Loop ; keep looping
Finished:
(在上面的代码中,我假设所有字符串的长度最小为 1 个字符。这可能是一个安全的假设,并且避免了在循环。)
或者,您可以执行您提到的数组索引,但如果您想通过字符串迭代 forwards,则必须小心:
mov edx, OFFSET var1 ; get starting address of string
movzx ecx, BYTE PTR [edx] ; get length of string
lea edx, [ecx+1] ; increment pointer by 1 + number of chars
neg ecx ; negate the length counter
Loop:
mov al, BYTE PTR [edx+ecx] ; get next character
; Do something with the character in AL.
; ...
inc ecx
jnz Loop ; CL != 0, so keep looping
基本上,我们将EDX设置为指向字符串的end,我们将计数器(ECX)设置为负的长度字符串,然后我们通过索引[EDX+ECX] 来读取字符(因为我们否定了ECX,所以它等同于[EDX-ECX])。
几乎可以肯定有一种比我在这里想出的更好(更聪明)的方法,但你应该明白这一点。
【讨论】:
mov al, [var1 + edx] 的简单解决方案应该是合适的。
mov al, BYTE PTR [var1 + index](其中 index 是立即值或包含该值的寄存器)自然会起作用。不知道我应该让答案更长。 :-)
rep scasb 可用于通过搜索终止 NUL 字符来查找字符串的长度。这是用汇编语言实现strlen 的一种(极其)低效的方法(除非您正在为 8088 编程)。