尽管我很喜欢C和c++,但我还是忍不住对空结尾字符串的选择抓耳挠脑:

Length prefixed (i.e. Pascal) strings existed before C Length prefixed strings make several algorithms faster by allowing constant time length lookup. Length prefixed strings make it more difficult to cause buffer overrun errors. Even on a 32 bit machine, if you allow the string to be the size of available memory, a length prefixed string is only three bytes wider than a null terminated string. On 16 bit machines this is a single byte. On 64 bit machines, 4GB is a reasonable string length limit, but even if you want to expand it to the size of the machine word, 64 bit machines usually have ample memory making the extra seven bytes sort of a null argument. I know the original C standard was written for insanely poor machines (in terms of memory), but the efficiency argument doesn't sell me here. Pretty much every other language (i.e. Perl, Pascal, Python, Java, C#, etc) use length prefixed strings. These languages usually beat C in string manipulation benchmarks because they are more efficient with strings. C++ rectified this a bit with the std::basic_string template, but plain character arrays expecting null terminated strings are still pervasive. This is also imperfect because it requires heap allocation. Null terminated strings have to reserve a character (namely, null), which cannot exist in the string, while length prefixed strings can contain embedded nulls.

其中一些东西比C语言出现得更晚,所以C语言不知道它们是有道理的。然而,在C语言出现之前,有些语言就已经很简单了。为什么会选择空终止字符串,而不是明显更好的长度前缀?

编辑:因为有些人问了关于我上面提到的效率点的事实(他们不喜欢我已经提供的事实),他们源于以下几点:

使用空结尾字符串的Concat需要O(n + m)时间复杂度。长度前缀通常只需要O(m)。 使用空结尾字符串的长度需要O(n)时间复杂度。长度前缀为O(1)。 Length和concat是迄今为止最常见的字符串操作。在一些情况下,以空结尾的字符串可能更有效,但这种情况发生的频率要低得多。

从下面的答案,这些是一些情况下,空终止字符串更有效:

When you need to cut off the start of a string and need to pass it to some method. You can't really do this in constant time with length prefixing even if you are allowed to destroy the original string, because the length prefix probably needs to follow alignment rules. In some cases where you're just looping through the string character by character you might be able to save a CPU register. Note that this works only in the case that you haven't dynamically allocated the string (Because then you'd have to free it, necessitating using that CPU register you saved to hold the pointer you originally got from malloc and friends).

上面这些词都没有length和concat常见。

下面的答案中还有一个断言:

你需要把绳子的一端剪掉

但这个是不正确的——对于以null结尾的字符串和有长度前缀的字符串,它的时间是相同的。(以Null结尾的字符串只是在你想要的新结尾的地方插入一个Null,长度前缀只是从前缀中减去。)


当前回答

这个问题是作为长度前缀字符串(LPS)与零终止字符串(SZ)的问题提出的,但主要暴露了长度前缀字符串的好处。这似乎有些势不可挡,但老实说,我们也应该考虑到LPS的缺点和SZ的优点。

在我看来,这个问题甚至可以被理解为一种带有偏见的提问方式:“零终止字符串的优势是什么?”

零终止字符串的优点(我看到了):

very simple, no need to introduce new concepts in language, char arrays/char pointers can do. the core language just include minimal syntaxic sugar to convert something between double quotes to a bunch of chars (really a bunch of bytes). In some cases it can be used to initialize things completely unrelated with text. For instance xpm image file format is a valid C source that contains image data encoded as a string. by the way, you can put a zero in a string literal, the compiler will just also add another one at the end of the literal: "this\0is\0valid\0C". Is it a string ? or four strings ? Or a bunch of bytes... flat implementation, no hidden indirection, no hidden integer. no hidden memory allocation involved (well, some infamous non standard functions like strdup perform allocation, but that's mostly a source of problem). no specific issue for small or large hardware (imagine the burden to manage 32 bits prefix length on 8 bits microcontrollers, or the restrictions of limiting string size to less than 256 bytes, that was a problem I actually had with Turbo Pascal eons ago). implementation of string manipulation is just a handful of very simple library function efficient for the main use of strings : constant text read sequentially from a known start (mostly messages to the user). the terminating zero is not even mandatory, all necessary tools to manipulate chars like a bunch of bytes are available. When performing array initialisation in C, you can even avoid the NUL terminator. Just set the right size. char a[3] = "foo"; is valid C (not C++) and won't put a final zero in a. coherent with the unix point of view "everything is file", including "files" that have no intrinsic length like stdin, stdout. You should remember that open read and write primitives are implemented at a very low level. They are not library calls, but system calls. And the same API is used for binary or text files. File reading primitives get a buffer address and a size and return the new size. And you can use strings as the buffer to write. Using another kind of string representation would imply you can't easily use a literal string as the buffer to output, or you would have to make it have a very strange behavior when casting it to char*. Namely not to return the address of the string, but instead to return the actual data. very easy to manipulate text data read from a file in-place, without useless copy of buffer, just insert zeroes at the right places (well, not really with modern C as double quoted strings are const char arrays nowaday usually kept in non modifiable data segment). prepending some int values of whatever size would implies alignment issues. The initial length should be aligned, but there is no reason to do that for the characters datas (and again, forcing alignment of strings would imply problems when treating them as a bunch of bytes). length is known at compile time for constant literal strings (sizeof). So why would anyone want to store it in memory prepending it to actual data ? in a way C is doing as (nearly) everyone else, strings are viewed as arrays of char. As array length is not managed by C, it is logical length is not managed either for strings. The only surprising thing is that 0 item added at the end, but that's just at core language level when typing a string between double quotes. Users can perfectly call string manipulation functions passing length, or even use plain memcopy instead. SZ are just a facility. In most other languages array length is managed, it's logical that is the same for strings. in modern times anyway 1 byte character sets are not enough and you often have to deal with encoded unicode strings where the number of characters is very different of the number of bytes. It implies that users will probably want more than "just the size", but also other informations. Keeping length give use nothing (particularly no natural place to store them) regarding these other useful pieces of information.

也就是说,在标准C字符串确实效率低下的罕见情况下,没有必要抱怨。图书馆是可用的。如果我遵循这个趋势,我应该抱怨标准C不包括任何正则表达式支持函数……但实际上每个人都知道这不是一个真正的问题,因为有库可以用于此目的。因此,当字符串操作效率是需要的,为什么不使用像bstring库?或者甚至是c++字符串?

编辑:我最近看了看D弦。有趣的是,所选择的解决方案既不是大小前缀,也不是零终止。与C语言一样,双引号括起来的字面值字符串只是不可变字符数组的简写,并且该语言也有一个字符串关键字表示(不可变字符数组)。

但是D数组比C数组丰富得多。在静态数组的情况下,长度在运行时是已知的,因此不需要存储长度。编译器在编译时拥有它。在动态数组的情况下,长度是可用的,但D文档没有说明它保存在哪里。就我们所知,编译器可以选择将它保存在某个寄存器中,或者存储在远离字符数据的某个变量中。

正常char数组或非字符串没有最终为零,因此程序员必须把它本身如果他想叫一些C函数从D .字符串字面量的具体情况,然而D编译器仍然把零在每个字符串(允许容易把C字符串容易调用C函数?),但这零不是字符串的一部分(D不计算字符串大小)。

The only thing that disappointed me somewhat is that strings are supposed to be utf-8, but length apparently still returns a number of bytes (at least it's true on my compiler gdc) even when using multi-byte chars. It is unclear to me if it's a compiler bug or by purpose. (OK, I probably have found out what happened. To say to D compiler your source use utf-8 you have to put some stupid byte order mark at beginning. I write stupid because I know of not editor doing that, especially for UTF-8 that is supposed to be ASCII compatible).

其他回答

根据Joel Spolsky在这篇博文中的说法,

这是因为发明了UNIX和C编程语言的PDP-7微处理器有一个ascii字符串类型。ASCIZ的意思是“以Z(零)结尾的ASCII”。

在看到这里所有其他的答案后,我相信即使这是真的,这也只是C具有以空结束的“字符串”的部分原因。这篇文章很有启发性,因为像字符串这样简单的东西实际上是相当困难的。

来自马的口

None of BCPL, B, or C supports character data strongly in the language; each treats strings much like vectors of integers and supplements general rules by a few conventions. In both BCPL and B a string literal denotes the address of a static area initialized with the characters of the string, packed into cells. In BCPL, the first packed byte contains the number of characters in the string; in B, there is no count and strings are terminated by a special character, which B spelled *e. This change was made partially to avoid the limitation on the length of a string caused by holding the count in an 8- or 9-bit slot, and partly because maintaining the count seemed, in our experience, less convenient than using a terminator.

Dennis M Ritchie, C语言的开发

假设C以Pascal的方式实现字符串,通过前缀长度:7字符长字符串与3字符字符串的数据类型相同吗?如果答案是肯定的,那么当我将前者分配给后者时,编译器应该生成什么样的代码?字符串应该被截断,还是自动调整大小?如果调整大小,该操作是否应该被锁保护以使其线程安全?不管你喜不喜欢,C语言的方法回避了所有这些问题。

与长度前缀相比,null终止的一个优点是字符串比较的简单性,这一点我没有看到任何人提到过。考虑比较标准,它返回小于、等于或大于的有符号结果。对于长度前缀,算法必须遵循以下几行:

Compare the two lengths; record the smaller, and note if they are equal (this last step might be deferred to step 3). Scan the two character sequences, subtracting characters at matching indices (or use a dual pointer scan). Stop either when the difference is nonzero, returning the difference, or when the number of characters scanned is equal to the smaller length. When the smaller length is reached, one string is a prefix of the other. Return negative or positive value according to which is shorter, or zero if of equal length.

将其与null终止算法进行对比:

扫描两个字符序列,在匹配的索引处减去字符[注意,移动指针处理得更好]。当差值非零时停止,返回差值。注意:如果一个字符串是另一个字符串的PROPER前缀,减法中的一个字符将为NUL,即零,比较将自然地停止在那里。 如果差值为零,-only then-检查是否有字符为NUL。如果是,则返回0,否则继续到下一个字符。

以null结尾的情况更简单,并且非常容易用双指针扫描高效地实现。带长度前缀的大小写至少做同样多的工作,几乎总是更多。如果你的算法必须做大量的字符串比较[e。编译器!],以null结尾的情况胜出。现在,这可能不那么重要了,但在过去,是的。

这个问题是作为长度前缀字符串(LPS)与零终止字符串(SZ)的问题提出的,但主要暴露了长度前缀字符串的好处。这似乎有些势不可挡,但老实说,我们也应该考虑到LPS的缺点和SZ的优点。

在我看来,这个问题甚至可以被理解为一种带有偏见的提问方式:“零终止字符串的优势是什么?”

零终止字符串的优点(我看到了):

very simple, no need to introduce new concepts in language, char arrays/char pointers can do. the core language just include minimal syntaxic sugar to convert something between double quotes to a bunch of chars (really a bunch of bytes). In some cases it can be used to initialize things completely unrelated with text. For instance xpm image file format is a valid C source that contains image data encoded as a string. by the way, you can put a zero in a string literal, the compiler will just also add another one at the end of the literal: "this\0is\0valid\0C". Is it a string ? or four strings ? Or a bunch of bytes... flat implementation, no hidden indirection, no hidden integer. no hidden memory allocation involved (well, some infamous non standard functions like strdup perform allocation, but that's mostly a source of problem). no specific issue for small or large hardware (imagine the burden to manage 32 bits prefix length on 8 bits microcontrollers, or the restrictions of limiting string size to less than 256 bytes, that was a problem I actually had with Turbo Pascal eons ago). implementation of string manipulation is just a handful of very simple library function efficient for the main use of strings : constant text read sequentially from a known start (mostly messages to the user). the terminating zero is not even mandatory, all necessary tools to manipulate chars like a bunch of bytes are available. When performing array initialisation in C, you can even avoid the NUL terminator. Just set the right size. char a[3] = "foo"; is valid C (not C++) and won't put a final zero in a. coherent with the unix point of view "everything is file", including "files" that have no intrinsic length like stdin, stdout. You should remember that open read and write primitives are implemented at a very low level. They are not library calls, but system calls. And the same API is used for binary or text files. File reading primitives get a buffer address and a size and return the new size. And you can use strings as the buffer to write. Using another kind of string representation would imply you can't easily use a literal string as the buffer to output, or you would have to make it have a very strange behavior when casting it to char*. Namely not to return the address of the string, but instead to return the actual data. very easy to manipulate text data read from a file in-place, without useless copy of buffer, just insert zeroes at the right places (well, not really with modern C as double quoted strings are const char arrays nowaday usually kept in non modifiable data segment). prepending some int values of whatever size would implies alignment issues. The initial length should be aligned, but there is no reason to do that for the characters datas (and again, forcing alignment of strings would imply problems when treating them as a bunch of bytes). length is known at compile time for constant literal strings (sizeof). So why would anyone want to store it in memory prepending it to actual data ? in a way C is doing as (nearly) everyone else, strings are viewed as arrays of char. As array length is not managed by C, it is logical length is not managed either for strings. The only surprising thing is that 0 item added at the end, but that's just at core language level when typing a string between double quotes. Users can perfectly call string manipulation functions passing length, or even use plain memcopy instead. SZ are just a facility. In most other languages array length is managed, it's logical that is the same for strings. in modern times anyway 1 byte character sets are not enough and you often have to deal with encoded unicode strings where the number of characters is very different of the number of bytes. It implies that users will probably want more than "just the size", but also other informations. Keeping length give use nothing (particularly no natural place to store them) regarding these other useful pieces of information.

也就是说,在标准C字符串确实效率低下的罕见情况下,没有必要抱怨。图书馆是可用的。如果我遵循这个趋势,我应该抱怨标准C不包括任何正则表达式支持函数……但实际上每个人都知道这不是一个真正的问题,因为有库可以用于此目的。因此,当字符串操作效率是需要的,为什么不使用像bstring库?或者甚至是c++字符串?

编辑:我最近看了看D弦。有趣的是,所选择的解决方案既不是大小前缀,也不是零终止。与C语言一样,双引号括起来的字面值字符串只是不可变字符数组的简写,并且该语言也有一个字符串关键字表示(不可变字符数组)。

但是D数组比C数组丰富得多。在静态数组的情况下,长度在运行时是已知的,因此不需要存储长度。编译器在编译时拥有它。在动态数组的情况下,长度是可用的,但D文档没有说明它保存在哪里。就我们所知,编译器可以选择将它保存在某个寄存器中,或者存储在远离字符数据的某个变量中。

正常char数组或非字符串没有最终为零,因此程序员必须把它本身如果他想叫一些C函数从D .字符串字面量的具体情况,然而D编译器仍然把零在每个字符串(允许容易把C字符串容易调用C函数?),但这零不是字符串的一部分(D不计算字符串大小)。

The only thing that disappointed me somewhat is that strings are supposed to be utf-8, but length apparently still returns a number of bytes (at least it's true on my compiler gdc) even when using multi-byte chars. It is unclear to me if it's a compiler bug or by purpose. (OK, I probably have found out what happened. To say to D compiler your source use utf-8 you have to put some stupid byte order mark at beginning. I write stupid because I know of not editor doing that, especially for UTF-8 that is supposed to be ASCII compatible).