Length of a string

A zero-terminated string has no stored character count. To find its length, read bytes until the first zero. This example does that twice: an ordinary loop leaves its answer in r8, and repne scasb leaves the same answer in r9. Both searches are limited to the bytes declared for text.

default rel
global _start

section .data
text:           db "assembly", 0
text_end:
TEXT_CAPACITY   equ text_end - text

section .text
_start:
    lea rsi, [text]             ; first byte
    xor rcx, rcx                ; offset, also the length so far
.next:
    cmp rcx, TEXT_CAPACITY
    jae .loop_missing           ; no byte remains to check
    cmp byte [rsi + rcx], 0
    je .loop_done               ; zero is not a character
    inc rcx
    jmp .next
.loop_done:
    mov r8, rcx
    jmp .scan_start
.loop_missing:
    mov r8, -1                  ; no zero within the capacity

.scan_start:
    lea rbx, [text]             ; save the starting address
    lea rdi, [text]             ; scasb reads from rdi
    mov rcx, TEXT_CAPACITY
    test rcx, rcx
    jz .scan_missing            ; no comparison for an empty range
    xor eax, eax                ; al = zero, the byte to find
    cld                         ; advance toward higher addresses
    repne scasb
    jnz .scan_missing           ; last comparison was not zero
    mov r9, rdi
    sub r9, rbx                 ; bytes examined, including the zero
    dec r9                      ; characters before the zero
    jmp .done
.scan_missing:
    mov r9, -1
.done:
    mov rax, 60
    xor rdi, rdi
    syscall

Run it and inspect r8 and r9 in the register panel: both are 8. The ordinary loop tests the capacity before reading [rsi + rcx]. At offsets 0 through 7 it sees the letters; at offset 8 it sees zero and copies rcx to r8. TEXT_CAPACITY is 9 because db writes eight letters and the explicit terminator. The text_end label marks the address just after those nine bytes; it stores no byte itself.

The byte in cmp byte [rsi + rcx], 0 is required. [rsi + rcx] gives an address and the immediate 0 has no width, so NASM cannot tell whether to compare one, two, four, or eight bytes without an explicit size.

For the second search, scasb compares al with the byte at rdi and then advances rdi by one because cld cleared the direction flag. repne repeats that comparison while rcx is nonzero and the last byte was unequal. It decrements rcx after every comparison. Here it examines nine bytes, including the zero: on the match, rdi = text + 9, rcx = 0, and the zero flag is set. rdi - text is therefore 9 bytes examined; subtracting one excludes the terminator and gives the length 8. The jnz checks the comparison result immediately after the scan. If the capacity runs out on a nonzero byte, it takes the missing-terminator path.

Try replacing the declaration with text: db "abcdefghi", leaving text_end in place. Those nine declared bytes contain no zero. Each search reads only those bytes and leaves -1 (the 64-bit pattern 0xFFFFFFFFFFFFFFFF) in its result register. Restore the original declaration afterward. The capacity is the number of accessible bytes supplied to the search; it does not promise that a zero is present. A program using this pattern must know that capacity before it starts reading.

Your turn

Complete the two ordinary loops below. For each string, check its capacity before reading a byte, count the nonzero bytes, and put -1 in the result register if no zero occurs within that capacity. first contains "Hi!" and a zero, so r10 should be 3. second has five nonzero bytes and no terminator, so r11 should have the -1 bit pattern 0xFFFFFFFFFFFFFFFF. Use Test to check both values, then inspect the registers. The second string is safe to scan because its five-byte capacity stops the loop before it reads beyond second_end.

default rel
global _start

section .data
first:          db "Hi!", 0
first_end:
FIRST_CAPACITY  equ first_end - first
second:         db "abcde"
second_end:
SECOND_CAPACITY equ second_end - second

section .text
_start:
    lea rsi, [first]
    xor rcx, rcx
.first_next:
    ; Check FIRST_CAPACITY before reading a byte.
    ; On zero, put rcx in r10. On exhaustion, put -1 in r10.

.second_start:
    lea rsi, [second]
    xor rcx, rcx
.second_next:
    ; Check SECOND_CAPACITY before reading a byte.
    ; On zero, put rcx in r11. On exhaustion, put -1 in r11.

.done:
    mov rax, 60
    xor rdi, rdi
    syscall
Show solution
default rel
global _start

section .data
first:          db "Hi!", 0
first_end:
FIRST_CAPACITY  equ first_end - first
second:         db "abcde"
second_end:
SECOND_CAPACITY equ second_end - second

section .text
_start:
    lea rsi, [first]
    xor rcx, rcx
.first_next:
    cmp rcx, FIRST_CAPACITY
    jae .first_missing
    cmp byte [rsi + rcx], 0
    je .first_found
    inc rcx
    jmp .first_next
.first_found:
    mov r10, rcx
    jmp .second_start
.first_missing:
    mov r10, -1

.second_start:
    lea rsi, [second]
    xor rcx, rcx
.second_next:
    cmp rcx, SECOND_CAPACITY
    jae .second_missing
    cmp byte [rsi + rcx], 0
    je .second_found
    inc rcx
    jmp .second_next
.second_found:
    mov r11, rcx
    jmp .done
.second_missing:
    mov r11, -1

.done:
    mov rax, 60
    xor rdi, rdi
    syscall