这可以归结为 C 字符串、胖指针以及字符串文字在可执行文件中存储方式之间的差异。
C 弦乐
您可能已经知道,C 将字符串表示为char *
。由于无法知道何时停止从内存中读取字符串,因此在末尾添加了一个空终止符(值为 0 的字节)。
So what strlen
它只是计算字节数,直到找到值为 0 的字节。printf
做类似的事情,只不过它将找到的内容输出到标准输出。
// This string occupies 5 bytes of memory due to the implicit null terminator
char *string_literal = "test";
// ['t', 'e', 's', 't', 0]
脂肪指针
然而,C String 方法可能存在问题。如果要获取子字符串,则需要修改原始字符串以添加新的空终止符或将所需部分复制到内存的新部分。解决这个问题的方法是用指针存储字符串的长度
// This isn't technically correct, but it is easier to think of this way
pub struct string {
ptr: *const i8,
length: usize,
}
你可以看到 C++ 中使用的胖指针std::string
和 Rust 的切片。由于 Rust 决定使用胖指针作为默认值,因此编译器将选择在可能的情况下不包含空终止符以节省空间。
Memory
在 Linux 可执行文件(ELF 格式)中,代码中使用的所有字符串文字和常量均由编译器自行决定添加到二进制文件的文本部分。
在不知道太多的情况下,我将猜测第一个代码示例的文本部分是什么样的:
This uses C's standard lib printf\0\nUseless thing againLength of : \0
我通过将所有字符串文字按照代码中给出的顺序放在一起并删除将在编译时删除的部分(例如{}
在 Rust 的打印语句中。通过这种天真的估计,我们实际上看到空终止符之前正好有 31 个字符与第一个代码示例的输出相匹配。您可以使用自己验证这一点objdump -sj .text executable_file
(假设我正确地执行了该命令)。
例外情况
我想指出的一件事是 a 的长度特点不是固定的。例如,Unicode 字符的长度可以是 4 个字节。因此,如果您打算将字符串传递给 c,建议您使用二进制字符串来更明确地了解数据类型,并且如果您不确定是否会传递它,请直接添加 null 终止符。
// The b converts the string to a [u8; N] and \0 is the null terminator.
let example = b"test 123\0";