为啥字符串长度应该加一它在 C 中的容量?

Posted

技术标签:

【中文标题】为啥字符串长度应该加一它在 C 中的容量?【英文标题】:Why should string length be plus one its capacity in C?为什么字符串长度应该加一它在 C 中的容量? 【发布时间】:2017-09-11 11:27:41 【问题描述】:

您的字符串长度应该比您希望它能够容纳的最大字符数大一。足够合乎逻辑:字符串以NULL 字符结尾。

这是大多数新手都会得到的非常普遍的建议。然而,随着我在编程方面的成长,现在看来它并不那么正确。

任何类型数组的索引,无论是int还是char,都从0开始。因此,大多数数组的最大索引值比其数值小一。字符串也一样,但由于末尾有一个额外的字符,它会加一。因此,字符串长度与其中的字符数相同。


要查看我是否正确,请参阅此 sn-p:

char str[9];
scanf("%s", str);
printf("%d", strlen(str));

把它变成一个成熟的程序,然后运行它。输入123456789,保证9 个字符的长文本,然后查看结果。它可以容纳字符串,果然,字符串长度为9


我什至目睹了许多专业程序员说字符串大小应该是其容量的加一。这个建议在很大程度上是一个神话,还是我在某个地方出错了?

编辑

假设我想创建一个整数数组Arr,它可以容纳x 个元素。 Arr 的最后一个元素的索引值将比x 小一,因为索引值从0 开始,而不是1。所以,它的长度是x-1

那你会如何声明呢?我会这样做:int Arr[x-1];。我不认为这有什么问题。

现在,如果 Arrchar 类型的数组(即字符串),则 Arr 的长度将比其对应的 int 的长度大一,因为它的末尾有一个额外的 NULL 字符。最终结果为:(x-1)+1=x

Code to demonstrate this

那么为什么这次的声明必须是char Arr[x+1] 而不是简单的char Arr[x]

【问题讨论】:

您能否将链接分享给所谓的专家,他们说不应保留“\0”的空间。 您展示的程序也调用了未定义的行为。为什么它有效或无效不是一个有效的问题。 参见 e。 G。 here:scanf 自动添加一个终止字符 0。因此,如果您按照问题中的描述这样做,那么您实际上是在超出数组的范围 -> UB! C 没有字符串类型。并且不清楚为什么数组长度(与它的大小不同!)比什么少一?数组的“数值”是什么?? 你为什么不提出一个明确的问题并展示一些研究而不是陈述混乱的事情? 【参考方案1】:

您对索引是正确的。然而:

char str[9];

当您以这种方式声明字符串时,数字9 就是数组长度。减去 NULL,只能有 8 个字符,而不是 9 个。数组的长度是数组中元素的数量,而不是您想的 最大索引值。您混淆了这些术语。

许多其他答案甚至 cmets 已经解释了为什么您的程序有效。

【讨论】:

【参考方案2】:

根据C标准相对于转换说明符s的描述(7.21.6.2 fscanf函数)

s 匹配一系列非空白字符。279) 如果没有 l 长度 修饰符存在,对应的参数应该是一个指针 字符数组的初始元素,大到足以接受 序列和一个终止空字符,将被添加 自动

所以如果要输入123456789的字符序列那么就会尝试写下面的字符`

 '1', '2', '3', '4', '5', '6', '7', '8', '9', '\0' `

在数组中声明为

char str[9];

正如所见,序列包含 10 个字符,而数组只能容纳 9 个字符。因此,数组之外的内存将被覆盖,从而导致程序具有未定义的行为。

在与 C++ 相对的 C 中,您可以通过以下方式初始化字符数组

char str[3] = "Bye";

在这种情况下,终止零将不会用作数组的初始值设定项。那就是数组不包含字符串而只包含字符

 'B', 'y', 'e' 

但是,您可能不会将标准 C 函数 strlen 应用于此数组,因为该函数会计算字符,直到遇到终止零并且数组没有这样的字符。

您应该区分sizeof 运算符的返回值和标准C 函数strlen 的返回值。

例如,如果您有这样的声明

char str[10] = "Hello";

那么 sizeof 运算符 sizeof( str ) 返回 10,即数组有 10 个大小等于 1 的元素(sizeof( char) 始终等于 1)。

但是,如果您将应用标准 C 函数 strlen,则返回值将等于 5,因为该函数会计算终止零之前的所有字符。

你可以写例子

str[8] = 'A';

尽管如此,如果您应用函数strlen,您将再次获得值 5,因为在具有值 'A' 的元素 str[8] 之前有一个终止零。

【讨论】:

【参考方案3】:

任何类型的数组的索引,无论是 int 还是 char,都从 0 开始。

是的,没错。

因此,所有数组大小都比它们的数值小一。

没有。用于索引的第一个值仅影响索引,而不影响大小。例如,一个大小为 1 的数组只有一个索引,即 0。它是最大索引值,它比大小小 1,而不是相反。

在声明 char str[9]; 中,值 9 是数组大小,而不是最大索引值。

您的示例 似乎 起作用的原因是,未定义的行为没有导致崩溃或错误消息。

【讨论】:

措辞重要吗?可以编辑问题。一个术语的错误用法并不是世界末日。 我认为您正确使用了这些术语?你指的是什么? 它将是 maximum index value 而不是 array size。很明显我的意思是!为什么每个人都对此如此挑剔?【参考方案4】:

您是对的,数组索引从 0 开始,但 char str[9] 的长度为 9,因此最高索引为 8。您的示例似乎有效,但很容易产生错误。您也可以在代码中输入 1234567890,它会输出 10,因为程序无法知道数组的长度。

当您定义该 char 数组时,您会在堆栈上为它创建一个 9 字节的空间,但是当您将它传递给 scanf 时,char[] 会转换为 char* 指向数组中第一个元素的指针。所以 scanf 无法知道数组的长度并将输入写入内存,从 str 指向的位置开始。它将 \0 字符写入为数组保留的空间之外!但是再次将它传递给 strlen 时,它看不到数组的大小并继续扫描内存以查找 \0,它在 10 个字节后找到它,因此它假定长度为 10。

就像@Ajay Brahmakshatriya 在他的回答中表明的那样,这可能会导致错误,因为字符串之外的空间可以用于另一个变量,例如另一个字符串,然后可以将不同的数据写入 \0 所在的字节。

【讨论】:

【参考方案5】:

看到这个 -> Ideone

int main(void) 
    char a[16];
    char b[16];
    scanf("%s",a);
    b[0]='a';
    b[1]='\0';
    printf("%s %d %p %p", a, strlen(a), a, b);
    return 0;  

这几乎是您展示的代码的复制品。对于长度为 16 的给定输入(数组大小也是 16),打印的长度为 17。

既然我们已经确定你所说的不正确,我们将看看为什么它为你打印了 9 而不是在我发布的示例中。

您创建了一个大小为 9 的数组(分配了 9 个字节)。 然后你在其中存储了 9 个字节的数据,并由'\0' 终止,它写在第十个字节上。由于该空间没有被 任何东西 使用(幸运的是)重要的,数据适合。

然后当你打电话给strlen时,它给了你9。

现在我创建了一个 16 字节的数组,并在其后放置了另一个数组。现在,当它读取 16 个字节并以 '\0' 终止它时,它写入 b。

我通过写信给 b 再次覆盖了它。 scanf写的'\0'就这样消失了。

然后strlen在计算长度时溢出到b中,当它在b[1]看到'\0'时停止。

所有这些当然是未定义的行为

【讨论】:

【参考方案6】:

...所以,字符串长度与其中的字符数相同。

如果我们看到终止的 null 不是字符,则此语句是正确的。但是,保存 string 所需的存储空间比其中的字符数多一。 (强调'string'是因为string作为数据类型需要额外的终止null,需要存储。)

【讨论】:

我说的是索引,而不是大小。【参考方案7】:

试图证明我的观点:

代码

#include <stdio.h>
#include <string.h>

int main()

    char str[23];
    scanf("%s", str);
    printf("String length = %d\n", strlen(str));
    printf("String element  ---  Index number");
    int index=0;

    while (str[i]!='\0')
    
        printf("\n%c  ---  %d", str[i], index);
        i++;
    

    printf("\nNULL  ===  %d", index);

    return 0;

示例输入

graphing

样本输出

String length = 8
String element  ---  Index number
g  ---  0
r  ---  1
a  ---  2
p  ---  3
h  ---  4
i  ---  5
n  ---  6
g  ---  7
NULL  ===  8

【讨论】:

你的观点是什么?一个 8 字母字符串 + NULL 终止符一共是 9 个字节。保存该字符串的数组定义为 char str[9],有效数组索引为 0..8。 int 的数组不是 C 字符串(不需要 NULL 终止符),因此 8 个 int 的数组将是 int arr[8] 并且有效索引是 0..7。所有其他发布的答案都是正确的。

以上是关于为啥字符串长度应该加一它在 C 中的容量?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 C 中的文字字符串分配不可能用于指定长度的数组? [复制]

C语言怎样定义变长数组

excel 函数最多能输入多少 为啥会提示公式太长

c语言如果用字符串类型输出字符数组,字符数组最后一个是0,那么为啥会出现乱码

C++ - 字符串容量模式

为啥要限制密码的长度?