如何生成一个以非null结尾的c字符串

how to make a not null-terminated c string?

本文关键字:字符串 结尾 null 何生成 一个以      更新时间:2023-10-16

我想知道:char*cs=。。。。。;如果cs指向巨大但没有"\0"的内存块,strlen()和printf("%s",cs)会发生什么?我写这几行:

char s2[3] = {'a','a','a'};
printf("str is %s,length is %d",s2,strlen(s2));

我得到的结果是:"aaa","3",但我认为这个结果是因为一个"\0"(或一个0字节)恰好位于位置s2+3。如何制作一个以非null结尾的c字符串?strlen和其他c字符串函数严重依赖于"\0"字节,如果没有"\0",我只想更深入、更好地了解这个规则。

ps:我的好奇心是通过研究下面关于SO的帖子引起的。如何将const char*转换为std::string帖子中有这样的话:"这实际上比看起来更棘手,因为除非字符串实际上是nul终止的,否则你不能调用strlen。">

如果它不是以null结尾的,那么它就不是C字符串,并且不能使用像strlen这样的函数——它们会离开数组的末尾,导致未定义的行为。你需要用其他方式记录长度。

您仍然可以使用printf打印非终止字符数组,只要您给出长度:

printf("str is %.3s",s2);
printf("str is %.*s",s2_length,s2);

或者,如果您可以访问数组本身,而不是指针:

printf("str is %.*s", (int)(sizeof s2), s2);

您还标记了问题C++:在该语言中,您通常希望避免所有这些容易出错的胡言乱语,而使用std::string

根据定义,"C字符串"以null结尾。该名称来自于以null结尾的字符串的C约定。如果你想要其他东西,它不是一个C字符串。

因此,如果你有一个非null终止的字符串,你就不能对它使用C字符串操作例程。你不能使用strlenstrcpystrcat。基本上,任何采用char*但没有单独长度的函数都是不可用的。

那你能做什么?如果您有一个不是以null结尾的字符串,那么您将分别获得长度。(如果你不这样做,你就完蛋了。你需要一些方法来找到长度,要么用终止符,要么单独存储。)你可以做的是分配一个合适大小的缓冲区,复制字符串,然后附加一个null。或者,您可以编写自己的一组字符串操作函数,这些函数可以处理指针和长度。在C++中,您可以使用std::string的构造函数,该构造函数采用char*和长度;那个人不需要终结者。

您的假设是正确的:您的strlen返回了正确的值,这完全是因为运气不好,因为恰好在您不正确终止的字符串之后的堆栈上有一个零。字符串为3字节可能会有所帮助,编译器可能会将堆栈上的内容与4字节的边界对齐。

你不能依赖这个。C字符串的末尾需要NUL字符(零)才能正常工作。C字符串处理混乱,容易出错;有一些库和API可以帮助减少这种情况……但仍然很容易出错。:)

在这种特殊情况下,您的字符串可以初始化为以下之一:

  • A:char s2[4] = { 'a','a','a', 0 }; // good if string MUST be 3 chars long
  • B:char *s2 = "aaa"; // if you don't need to modify the string after creation
  • C:char s2[]="aaa"; // if you DO need to modify the string afterwards

还要注意,声明BC是"更安全的",因为如果有人稍后出现并以改变长度的方式更改字符串声明,BC仍然自动正确,而A取决于程序员记住更改数组大小并在末尾保留显式null终止符。

发生的情况是strlen继续读取内存值,直到它最终变为null。然后,它假设这是终止符,并返回可能非常大的长度。如果您在期望使用C字符串的环境中使用strlen,那么您可以将这个巨大的数据缓冲区复制到另一个不够大的缓冲区中,从而导致缓冲区溢出问题,或者最多可以将大量垃圾数据复制到缓冲区中。

将一个以非null结尾的C字符串复制到std:string中可以做到这一点。如果您决定知道这个字符串只有3个字符长,并丢弃其余字符,那么您仍然会有一个非常长的std:string,它包含前3个好字符,然后是大量浪费。这是低效的。

寓意是,如果您使用CRT函数对C字符串进行运算符,则它们必须以null结尾。这与任何其他API没有什么不同,您必须遵守API为正确使用而制定的规则。

当然,如果你总是使用特定的长度版本(例如strncpy),你没有理由不能使用CRT函数,但你必须将自己限制在这些版本,总是,并手动跟踪正确的长度。

约定指出,具有终止的char数组是以null结尾的字符串。这意味着所有str*()函数都希望在char数组的末尾找到一个null终止符。但仅此而已,这只是惯例。

按照惯例,字符串也应该包含可打印字符。

如果您像char arr[3] = {'a', 'a', 'a'};那样创建一个数组,那么您就创建了一个char数组。由于它不是由终止的,所以它在C中不被称为字符串,尽管它的内容可以打印到stdout。

C标准直到7-库函数部分才定义术语string。C11 7.1.1p1中的定义为:

  1. 字符串是由字符组成的连续序列,以第一个null字符结尾并包含该字符

(强调矿)

如果字符串的定义是以null字符结束的字符序列,则未以null字符终止的非null字符序列不是字符串、句点。

您所做的是未定义的行为

您正试图写入非您的内存位置。

将其更改为

char s2[] = {'a','a','a',''};