以长度为前缀的字符串克服了以零结尾的字符串的哪些问题?

What are the problems of a zero-terminated string that length-prefixed strings overcome?

本文关键字:字符串 结尾 问题 克服 前缀      更新时间:2023-10-16

以长度为前缀的字符串克服了以零结尾的字符串的哪些问题?

我正在读《写伟大的代码》第一卷,我脑子里有这个问题。

一个问题是,对于以零结尾的字符串,您必须反复查找字符串的末尾。典型的低效示例是将数据连接到缓冲区:

char buf[1024] = "first";
strcat(buf, "second");
strcat(buf, "third");
strcat(buf, "fourth");

在每次调用strcat时,程序必须从字符串的开头开始并找到终止符以知道从哪里开始追加。这意味着随着字符串变长,函数花费越来越多的时间来寻找追加的位置。

对于长度前缀的字符串,相当于strcat函数将立即知道结束的位置,并在附加后更新长度。

每一种表示字符串的方式都有优点和缺点,它们是否会给你带来问题取决于你对字符串做什么,以及哪些操作需要高效。上面描述的问题可以通过在字符串增长时手动跟踪字符串的末尾来解决,因此通过更改代码可以避免性能成本。

一个问题是不能在以零结尾的字符串中存储空字符(值0)。这使得不可能存储一些字符编码以及加密数据。

长度前缀字符串不受此限制。

首先澄清:c++字符串(即std::string) 不是直到c++ 11才要求以零结尾。它们总是提供对以零结尾的C字符串的访问。

由于历史原因,c风格的字符串以0结尾。

您所指的问题主要与安全问题有关:零结束字符串需要具有零终止符。如果它们缺少它(无论出于什么原因),字符串的长度就会变得不可靠,并且可能导致缓冲区溢出问题(恶意攻击者可以通过在不应该写入数据的地方写入任意数据来利用这一点)。DEP有助于缓解这些问题,但这与主题无关)。

在paul - henning Kamp的《The Most Expensive One-byte error》一书中有最好的总结。

  1. 性能成本:在块中操作内存更便宜,如果你总是不得不寻找NULL字符,这是无法做到的。换句话说,如果你事先知道你有一个129个字符的字符串,那么在64、64和1字节的节中操作它可能会更有效,而不是一个字符一个字符地操作它。
  2. 保安:Marco A.已经打得很重了。超过或低于运行字符串缓冲区仍然是黑客攻击的主要途径。

  3. 编译器开发成本:大成本是与优化编译器为空终止字符串,这将更容易与地址和长度格式。

  4. 硬件开发成本:对于与空终止字符串相关的字符串特定指令,硬件开发成本也很大。

可以使用长度前缀字符串实现的更多附加功能:

  1. 可以有多种样式的长度前缀,通过字符串指针/引用标识的第一个字节的一个或多个位来识别。为了换取一点额外的时间来确定字符串长度,例如,可以为短字符串使用单字节前缀,为长字符串使用更长的前缀。如果使用大量1-3字节的字符串,与使用固定的4字节前缀相比,可以节省超过50%的总内存消耗;这种格式还可以容纳长度超过32位整数范围的字符串。

  2. 可以在边界检查的缓冲区中存储可变长度字符串,其代价仅为长度前缀中的1或2位。数字N与其他位组合将表示以下三种情况之一:

    1. n字节字符串

    2. (可选)一个n字节的缓冲区,包含一个零长度字符串

    3. n字节缓冲区,如果最后一个字节B小于248,则保存长度为N-B-1的字符串;如果是248或更多,前面的B-247字节将存储缓冲区大小和字符串长度之间的差异。请注意,如果字符串的长度恰好是N-1,则该字符串将后跟一个NUL字节,如果小于该长度,则该字符串后面的字节将不使用,可以设置为NUL。

    使用这种方法,需要在使用前初始化强缓冲区(以指示它们的长度),但这样就不再需要将字符串缓冲区的长度传递给将要在那里存储数据的例程。

  3. 可以使用某些前缀值来表示各种特殊的东西。例如,可以有一个前缀,表示后面不是字符串,而是一个字符串数据指针和两个给出缓冲区大小和当前长度的整数。

  4. 如果对字符串进行操作的方法调用了一个方法来获取数据指针、缓冲区大小和长度,那么只要字符串本身比方法调用的时间更长,就可以将对字符串一部分的引用便宜地传递给这样的方法。
  5. 可以用一个位扩展上述特征,以表明字符串数据位于malloc生成的区域中,如果需要可以调整大小;此外,可以安全地使用方法,有时返回分配在堆上的动态生成的字符串,有时返回不可变的静态字符串,并让接收方执行"如果不是静态的,则释放此字符串"。

我不知道是否有任何前缀字符串实现实现了所有这些额外的特性,但是它们都可以以很少的存储空间成本,相对较少的代码成本,以及比使用长度既不知道也不短的以null结尾的字符串所需的更少的时间成本来适应。

以长度为前缀的字符串克服了以零结尾的字符串的哪些问题?


一点儿也没有呢。这只是花瓶。

以长度为前缀的字符串在其结构中包含字符串长度的信息。如果您想对以零结尾的字符串执行相同的操作,您可以使用辅助变量;

lpstring = "foobar"; // saves '6' somewhere "inside" lpstring
ztstring = "foobar";
ztlength = 6;        // saves '6' in a helper variable

许多C库函数使用以零结尾的字符串,不能使用''字节以外的任何内容。这是函数本身的问题,与字符串结构无关。