我正在用C编写语言解释器,我的string
类型包含length
属性,如下所示:
struct String
{
char* characters;
size_t length;
};
因此,我必须花费大量时间在我的解释器中手动处理这种字符串,因为C不包含对它的内置支持。我考虑过切换到简单的以null结尾的字符串只是为了符合底层C,但似乎有很多理由不这样做:
如果使用“长度”而不是寻找空值,则内置边界检查。
您必须遍历整个字符串才能找到它的长度。
你必须做额外的事情来处理以null结尾的字符串中间的空字符。
以空值终止的字符串与Unicode处理不佳。
非空终止字符串可以实习更多,即“Hello,world”和“Hello”的字符可以存储在同一个地方,只是长度不同。使用以null结尾的字符串无法做到这一点。
字符串切片(注意:字符串在我的语言中是不可变的)。显然第二个更慢(并且更容易出错:考虑将begin
和end
的错误检查添加到两个函数中。
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使用它们!
所以我的问题是:我错过了无效的空终止有什么好处吗?
答案 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)
抛出一些假设:
答案 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,这对于字符串大小来说肯定是不够的。