x86 汇编语言中以 null 结尾的字符串和不以 null 结尾的字符串有什么区别

What is the different between a null terminated string and a string that is not terminated by null in x86 assembly language

我目前正在按照 Kip Irvine 的 "assembly language x86 programming" 书学习汇编编程。

在书中,作者指出

The most common type of string ends with a null byte (containing 0). Called a null-terminated string

在本书的后续章节中,作者有一个没有空字节的字符串示例

greeting1 \
BYTE "Welcome to the Encryption Demo program "

所以我只是想知道,在 x86 汇编语言中,以 null 结尾的字符串和不以 null 结尾的字符串有什么区别?它们可以互换吗?或者它们彼此不等价?

字符串如何终止与汇编无关。从历史上看,'$'、CRLF [10,13] 或 [0A,0D] 有时会像 Linux 下的 GEDIT 那样颠倒过来。约定取决于您的系统将如何与自身或其他系统交互。例如,我的应用程序严格以 ASCII 为导向,因此,如果我读取 UTF-8 或 16 格式的文件,我的应用程序就会失败。 NULL 或任何类型的终止都可以是可选的。

考虑这个例子

Title:  db  'Proto_Sys 1.00.0', 0, 0xc4, 0xdc, 0xdf, 'CPUID', 0, 'A20', 0
        db  'AXCXDXBXSPBPSIDIESDSCSSSFSGS'
Err00:  db  'Retry [Y/N]', 0

我已经实现了一个例程,其中如果 CX=0 则假设要显示一个以 NULL 结尾的字符串,否则只读取一个字符并重复 CX 次。这就是 0xc4 0xdc 0xdf 没有终止的原因。同样,'Retry [Y/N]' 之前没有终止符,因为我的算法设计方式不需要。

您唯一需要关心的是您的数据来源是什么,或者您的应用程序是否需要与其他东西兼容。然后你只需简单地实现你需要的任何东西来让它工作。

这里没有特定于 asm 的内容;这与 C 中的问题相同。这都是关于如何将字符串存储在内存中并跟踪它们结束的位置。

what is the different between a null terminated string and a string that is not terminated by null?

一个以 null 结尾的字符串后面有一个 0 字节,因此您可以找到 strlen 的结尾。 (例如,使用缓慢的 repne scasb)。这使得可以用作隐式长度字符串,就像 C 使用的那样。

explains the NASM syntax for creating one in static storage with db. db usage in nasm, try to store and print string 显示当您忘记 0 终止符时会发生什么。

Are they interchangeable?

如果您知道以 null 结尾的字符串的长度,则可以将指针+长度传递给需要显式长度字符串的函数。该函数永远不会查看 0 字节,因为您将传递不包含 0 字节的长度。它不是字符串数据的一部分。

但是如果您有一个没有终止符的字符串,则不能将它传递给需要以 null 终止的字符串的函数或系统调用。 (如果内存是可写的,您可以在字符串后存储一个 0 以使其成为一个以 null 结尾的字符串。)


在Linux中,许多系统调用将字符串作为C 风格的隐式长度空终止字符串。 (即只是 char* 而没有传递长度)。

例如,open(2) 接受一个字符串作为路径:int open(const char *pathname, int flags); 您必须将一个以 null 结尾的字符串传递给系统调用。在 Linux 中创建名称包含 '[=20=]' 的文件是不可能的(与大多数其他 Unix 系统相同),因为所有处理文件的系统调用都使用以空字符结尾的字符串。

OTOH,write(2) 采用不一定是字符串的内存缓冲区。它具有签名 ssize_t write(int fd, const void *buf, size_t count);。它不关心 buf+count 是否有 0 因为它只查看从 bufbuf+count-1.

的字节

可以将字符串传递给write()。它不在乎。它基本上只是进入内核页面缓存(或进入管道缓冲区或任何非常规文件)的 memcpy。但正如我所说,您不能将任意未终止缓冲区作为路径参数传递给 open().

Or they are not equivalent of each other?

隐式长度和显式长度是在内存中跟踪字符串 data/constants 并传递它们的两种主要方式。他们解决了同样的问题,但方式相反。

如果您有时需要在遍历它们之前找到它们的长度,那么长的隐式长度字符串是一个糟糕的选择。循环遍历一个字符串比只读取一个整数要慢很多。求一个隐式长度的字符串的长度是O(n),但是一个显式长度的字符串当然是O(1)次求长度。 (它已经知道了!)。至少以字节为单位的长度是已知的,但如果它采用像 UTF-8 或 UTF-16 这样的可变长度编码,则可能不知道以 Unicode 字符表示的长度。