The Unix programmer often works with C-style text strings, which consist of the text followed by a NUL, i.e., a byte equal to 0.
The C library provides a strlen() function to find the length of the string. The function is usually written in C and, therefore, does not take advantage of the string manipulation routines built into the Intel microprocessor since its lowly 8086/8088 origins.
In assembly language finding the length of a C-style string is a snap. The x86 family of microprocessors come with with the scasb instruction which searches for the first occurence of a byte whose value is equal to that of the AL register. The address of the start of the string itself has to be in the EDI register. Technically, it is supposed to be in the extra segment, but we do not need to worry about that in the flat 32-bit memory mode anymore. When used along with the repne prefix, the scasb instruction goes up (or down, depending on the direction flag) the memory, looking for the match.
As it scans the bytes, it decreases the value of the ECX register and increases the value of EDI. It quits when it either finds a match, or ECX becomes equal to 0.
To find the length of a string, we need to initialize ECX to the highest value possible, which is 4,294,967,295 in the 32-bit mode. When viewed as a signed value, it is the same as -1. The easiest way to achieve that is by first setting ECX to 0, then reversing all its bits, e.g.:
sub ecx, ecx ; ECX = 0 not ecx ; ECX = -1, or 4,294,967,295 |
We need to initialize AL to NUL, or 0:
sub al, al |
Assuming the start of our string is pointed at by EDI, we can now search for NUL at the end of the string:
cld repne scasb |
Now that we have found it, we can figure out the length of the string. We could take several approaches:
The most obvious would be by subtraction of the position of the NUL from the start of the string. This is how the typical C library does it.
But in assembly language we have a faster way. Note that while we initialized ECX to 4,294,967,295, it was the same as -1. But the microprocessor decreased its value with every scan, including when it found the NUL. Thus, ECX = - strlen - 2. We could negate it, so we would get ECX = strlen + 2. We could then subtract the 2:
neg ecx dec ecx dec ecx |
But this is still not the most efficient way. We know from digital electronics that reversing all bits of a negative number results in its absolute value - 1. We can, therefore, replace the first two lines of the above with not ecx:
not ecx dec ecx |
ECX now contains the length of the string, not counting the terminating NUL.
To put it all together, to find the length of the string whose starting address is in EDI, we only need a few lines of assembly language code:
sub ecx, ecx sub al, al not ecx cld repne scasb not ecx dec ecx |
TIP: In assembly language you are not limited to NUL-terminated strings. You can find the length of a string ending with any terminator by simply placing it in AL in the above code.
If we were to write a C library, naturally, we would write the strlen() function in assembly language for greater speed.
The typical C compiler expects any function to preserve the value of EDI. It expects to find the return value in EAX. And it does not care what we do with ECX. It also passes its arguments on the stack.
Here, then, is our improved version of the C strlen() function:
global strlen ; int strlen(const char *string); strlen: push edi sub ecx, ecx mov edi, [esp+8] not ecx sub al, al cld repne scasb not ecx pop edi lea eax, [ecx-1] ret |
N.B.: While on the ELF-based systems, the C function names are used unchanged in assembly language, on some systems they are prepended with an underline. On such systems, substitute _strlen for strlen. Of course, you could use both:
global strlen, _strlen _strlen: strlen: push edi [etc...] |
Copyright © 2000 G. Adam Stanislav.
All rights reserved.