为什么以null结尾的字符串?或者:以空值终止与字符+长度存储

时间:2009-08-10 05:59:54

标签: c performance algorithm string null-terminated

我正在用C编写语言解释器,我的string类型包含length属性,如下所示:

struct String
{
    char* characters;
    size_t length;
};

因此,我必须花费大量时间在我的解释器中手动处理这种字符串,因为C不包含对它的内置支持。我考虑过切换到简单的以null结尾的字符串只是为了符合底层C,但似乎有很多理由不这样做:

如果使用“长度”而不是寻找空值,则内置边界检查。

您必须遍历整个字符串才能找到它的长度。

你必须做额外的事情来处理以null结尾的字符串中间的空字符。

以空值终止的字符串与Unicode处理不佳。

非空终止字符串可以实习更多,即“Hello,world”和“Hello”的字符可以存储在同一个地方,只是长度不同。使用以null结尾的字符串无法做到这一点。

字符串切片(注意:字符串在我的语言中是不可变的)。显然第二个更慢(并且更容易出错:考虑将beginend的错误检查添加到两个函数中。

struct String slice(struct String in, size_t begin, size_t end)
{
    struct String out;
    out.characters = in.characters + begin;
    out.length = end - begin;

    return out;
}

char* slice(char* in, size_t begin, size_t end)
{
    char* out = malloc(end - begin + 1);

    for(int i = 0; i < end - begin; i++)
        out[i] = in[i + begin];

    out[end - begin] = '\0';

    return out;
}

毕竟,我的想法不再是我是否应该使用以null结尾的字符串:我在考虑为什么C使用它们!

所以我的问题是:我错过了无效的空终止有什么好处吗?

10 个答案:

答案 0 :(得分:29)

来自Joel的Back to Basics

  

为什么C字符串以这种方式工作?这是因为发明了UNIX和C编程语言的PDP-7微处理器具有ASCIZ字符串类型。 ASCIZ的意思是“最后用Z(零)的ASCII。”

     

这是存储字符串的唯一方法吗?不,实际上,这是存储字符串的最糟糕方式之一。对于非平凡的程序,API,操作系统,类库,你应该避免像瘟疫这样的ASCIZ字符串。

答案 1 :(得分:16)

通常的解决方案是同时执行这两项操作 - 保持长度并保持空终止符。这不是额外的工作,意味着你总是准备将字符串传递给任何函数。

以空值终止的字符串通常会消耗性能,原因很明显,发现长度所需的时间取决于长度。从好的方面来说,它们是用C表示字符串的标准方式,所以如果你想使用大多数C库,你别无选择,只能支持它们。

答案 2 :(得分:7)

一个好处是,对于null-termination,以null结尾的字符串的任何尾部也是以null结尾的字符串。如果你需要将一个以第N个字符开头的子字符串(前提是没有缓冲区溢出)传递给某个字符串处理函数 - 没问题,只需在那里传递offrested地址。当以其他方式存储大小时,您需要构造一个新的字符串。

答案 3 :(得分:6)

以空字符串结尾的字符串的一个优点是,如果您逐个字符地遍历字符串,则只需要保留一个指针来处理字符串:

while (*s)
{
    *s = toupper(*s);
    s++;
}

对于没有标记的字符串,你需要保持两位状态:指针和索引:

while (i < s.length)
{
    s.data[i] = toupper(s.data[i]);
    i++;
}

...或当前指针和限制:

s_end = s + length;
while (s < s_end)
{
    *s = toupper(*s);
    s++;
}

当CPU寄存器是稀缺资源(并且编译器在分配它们时更糟糕)时,这很重要。现在,不是那么多。

答案 4 :(得分:5)

长度也存在问题。

  • 长度需要额外的存储空间(现在不是这样的问题,但是30年前的一个重要因素)。

  • 每次更改字符串时都需要更新字符串,因此全面降低了性能。

  • 使用以NUL结尾的字符串,您仍然可以使用长度或存储指向最后一个字符的指针,因此如果您正在进行大量的字符串操作,则仍然可以使字符串与长度相等。

  • NUL终止的字符串要简单得多 - NUL终止符只是strcat等方法用来确定字符串结尾的约定。因此,您可以将它们存储在常规char数组中,而不必使用结构。

答案 5 :(得分:5)

略微偏离主题,但是有一种比你描述的方式更有效的方式来做长度前缀的字符串。创建这样的结构(在C99及以上版本中有效):

struct String 
{
  size_t length;
  char characters[0];
}

这会创建一个开头长度的结构,'characters'元素可以用作char *,就像使用当前结构一样。但是,不同之处在于,您只能在堆上为每个字符串分配一个项目,而不是两个。像这样分配你的字符串:

mystr = malloc(sizeof(String) + strlen(cstring))

例如 - 结构的长度(只是size_t)加上足够的空间来放置实际的字符串。

如果您不想使用C99,您也可以使用“char characters [1]”并从要分配的字符串长度中减去1。

答案 6 :(得分:4)

抛出一些假设:

  • 无法获得空终止字符串的“错误”实现。然而,标准化的结构可能具有特定于供应商的实现。
  • 不需要结构。 Null终止的字符串是“内置的”可以这么说,因为它是一个特殊的char *。

答案 7 :(得分:1)

虽然在大多数情况下我更喜欢array + len方法,但是使用null终止是有正当理由的。

采用32位系统。

存储7字节字符串
char * + size_t + 8个字节= 19个字节

存储7字节的空名字符串
char * + 8 = 16个字节。

null-term数组不需要像字符串那样是不可变的。我可以通过简单地放置一个空字符来愉快地截断c字符串。如果你编码,你需要创建一个新的字符串,包括分配内存。

根据字符串的用法,你的字符串永远不能与c字符串相比,而不是你的字符串。

答案 8 :(得分:1)

对于部分操作的类型检查和性能而言,0终止是一种方法,这是绝对正确的。这个页面上的答案已经总结了它的起源和用途。

我喜欢Delphi存储字符串的方式。我相信它在(可变长度)字符串之前保持长度/最大长度。这样,为了兼容性,字符串可以以空值终止。

我对你机制的关注: - 附加指针 - 你语言核心部分的不变性;通常字符串类型不是一成不变的,所以如果你重新考虑,那就太难了。您需要实现“创建副本更改”机制 - 使用malloc(几乎没有效率,但可能只是为了方便而包含在这里?)

祝你好运;编写自己的翻译可以非常有助于理解编程语言的语法和语法! (至少,它适合我)

答案 9 :(得分:0)

我认为主要原因是标准没有说明除了char以外的任何类型的大小。但是sizeof(char)= 1,这对于字符串大小来说肯定是不够的。