尽管我很喜欢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,长度前缀只是从前缀中减去。)


当前回答

GCC接受以下代码:

Char s[4] = "abcd";

如果我们把is当作字符数组,而不是字符串数组,这是可以的。也就是说,我们可以使用s[0], s[1], s[2]和s[3],甚至使用memcpy(dest, s, 4)访问它。但是当我们尝试使用put (s)时,我们会得到混乱的字符,或者更糟糕的是使用strcpy(dest, s)。

其他回答

即使在32位机器上,如果允许字符串的大小与可用内存相同,带前缀的长度字符串也只比以空结尾的字符串宽3个字节。

首先,对于短字符串来说,额外的3个字节可能是相当大的开销。具体来说,零长度字符串现在占用的内存是原来的4倍。我们中的一些人正在使用64位机器,因此我们要么需要8个字节来存储零长度的字符串,要么字符串格式无法处理平台支持的最长字符串。

可能还需要处理对齐问题。假设我有一个包含7个字符串的内存块,比如“solo\0second\0\0four\0five\0\0seventh”。第二个字符串从偏移量5开始。硬件可能要求32位整数以4的倍数的地址对齐,因此您必须添加填充,从而进一步增加开销。相比之下,C表示非常节省内存。(内存效率很好;例如,它有助于缓存性能。)

Obviously for performance and safety, you'll want to keep the length of a string while you're working with it rather than repeatedly performing strlen or the equivalent on it. However, storing the length in a fixed location just before the string contents is an incredibly bad design. As Jörgen pointed out in the comments on Sanjit's answer, it precludes treating the tail of a string as a string, which for example makes a lot of common operations like path_to_filename or filename_to_extension impossible without allocating new memory (and incurring the possibility of failure and error handling). And then of course there's the issue that nobody can agree how many bytes the string length field should occupy (plenty of bad "Pascal string" languages used 16-bit fields or even 24-bit fields which preclude processing of long strings).

C语言让程序员选择是否/在哪里/如何存储长度的设计更加灵活和强大。当然,程序员必须聪明。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结尾的情况胜出。现在,这可能不那么重要了,但在过去,是的。

我觉得更好的问题是你为什么觉得C欠你什么?C语言的设计是为了满足你的需要,仅此而已。你需要摆脱那种认为语言必须为你提供一切的心态。或者只是继续使用你的高级语言,这将给你奢侈的字符串,日历,容器;而在Java中,你会得到一种千变万化的东西。多个类型字符串,多个类型的unordered_map(s)。

这对你来说太糟糕了,这不是C的目的。C并不是被设计成一种从大头针到锚的臃肿语言。相反,您必须依赖第三方库或您自己的库。没有什么比创建一个包含字符串及其大小的简单结构体更容易的了。

struct String
{
 const char *s;
 size_t len;
};

你知道问题出在哪里。它不标准。另一种语言可能决定将len组织在字符串之前。另一种语言可能决定使用指针来代替结束。另一个人可能决定使用六个指针来提高String的效率。然而,null结尾的字符串是字符串的最标准格式;你可以用它来与任何语言进行交互。甚至Java JNI也使用以空结尾的字符串。

Lastly, it is a common saying; the right data structure for the task. If you find that need to know the size of a string more than anything else; well use a string structure that allows you to do that optimally. But don't make claims that that operation is used more than anything else for everybody. Like, why is knowing the size of a string more important than reading its contents. I find that reading the contents of a string is what I mostly do, so I use null terminated strings instead of std::string; which saves me 5 pointers on a GCC compiler. If I can even save 2 pointers that is good.

不知怎的,我把这个问题理解为C中没有编译器支持以长度为前缀的字符串。下面的例子显示,至少你可以开始你自己的C字符串库,其中字符串长度在编译时计算,使用这样的构造:

#define PREFIX_STR(s) ((prefix_str_t){ sizeof(s)-1, (s) })

typedef struct { int n; char * p; } prefix_str_t;

int main() {
    prefix_str_t string1, string2;

    string1 = PREFIX_STR("Hello!");
    string2 = PREFIX_STR("Allows \0 chars (even if printf directly doesn't)");

    printf("%d %s\n", string1.n, string1.p); /* prints: "6 Hello!" */
    printf("%d %s\n", string2.n, string2.p); /* prints: "48 Allows " */

    return 0;
}

然而,这不会带来任何问题,因为你需要小心什么时候特别释放字符串指针,什么时候它是静态分配的(字面字符数组)。

编辑:作为对这个问题更直接的回答,我的观点是,这是C既可以支持可用的字符串长度(作为编译时间常数)的方式,如果你需要它,但如果你只想使用指针和零终止,仍然没有内存开销。

当然,使用以零结尾的字符串似乎是推荐的做法,因为标准库一般不接受字符串长度作为参数,而且提取长度的代码不像char * s = "abc"那样简单,正如我的示例所示。