关于struct中的空数组的c:strlen()不为0

strlen() of an empty array within a struct is not 0

我是C语言的新手,我不了解这种行为。 在打印此空数组的长度后,我得到3而不是0。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct entry entry;

struct entry{
   char arr[16];
};

int main(){
  entry a;
  printf("%d
"
,strlen(a.arr));
  return 0;
}

我在这里不明白什么?


语句entry a;不会初始化该结构,因此其值可能是垃圾。因此,不能保证其任何成员上的strlen都会返回明智的内容。实际上,它甚至可能使程序崩溃,甚至更糟。


在C中没有"空数组"之类的东西。您的char[16];数组始终包含16个字节-未初始化为局部变量,每个char具有未指定的值。此外,如果这些未指定的值都没有恰好为0,则strlen将在数组外部读取,并且您的代码将具有未定义的行为。

另外,strlen返回size_t并使用%d进行打印也具有未定义的行为。您必须使用%zu,其中z表示相应的参数是size_t

(如果碰巧您正在使用MSVC ++" C"编译器,请注意它可能不支持%zu。请改为使用真正的C编译器和C标准库。)


这是strlen()的源代码:

1
2
3
4
5
6
size_t strlen(const char *str)
{
    const char *s;
    for (s = str; *s; ++s);
    return(s - str);
}

等待,您的意思是有源代码要strlen()?为什么是。 C语言中的所有标准函数本身都是用C语言编写的。

此功能从str指定的内存地址开始。然后,它使用for函数从该地址开始,然后逐字节前进,直到达到零为止。函数如何做到这一点?首先,它将s分配给str。然后,它检查s指向的值。如果为零(即* s返回零),则for循环完成。如果该值不为零,则将s指针递增,并一遍又一遍地执行零检查,直到找到零为止。

最后,s指针移动的距离减去您传入的原始指针是strlen()的结果。

换句话说,strlen()只是遍历内存,直到找到下一个零字符,然后它将从该点返回到原始指针的字符数。

但是,如果找不到零怎么办?会停止吗?不。它将一直反复不断,直到找到零或程序崩溃为止。

这就是为什么strlen()如此令人困惑,也是为什么它是现代软件中许多严重错误的根源。这并不意味着您不能使用它,而是意味着您必须非常非常小心,以确保传递的任何内容都是以空字符结尾的字符串(即,后面跟着一组零个或多个非零字符)零字符)。

还请记住,在C中,分配或将其保留时基本上不知道包含什么内存。如果您希望它全为零,那么您需要确保自己用零填充!

无论如何,您问题的答案涉及memset()函数的使用。您必须将指向数组开头的指针memset(),该数组的长度以及填充它的值传递给您(当然,您的情况是零!)


What am I not understanding here?

可能的误解是auto变量,例如:

1
entry a;

从进程的堆栈中分配了内存。为了您的利益,该堆栈存储器的现有内容不是zeroed-out。因此,a元素的值(也将位于进程堆栈上)最初不会为您带来利益zeroed-out。相反,a及其元素(包括.arr)的全部内容将包含奇异的,甚至可能是意外的值。

C程序员学习通过将变量清零或使用所需的值对其进行初始化来初始化变量。

例如,问题代码可能这样做如下:

1
2
3
4
5
6
7
8
int main(){
  entry a =
    {
    .arr[0] = 0
    };

...
}

要么:

1
2
3
4
5
6
7
int main(){
  entry a;

  memset(&a, 0, sizeof(a));

...
}

没有初始化a,这将导致未定义的行为。

C"字符串"是'\0'终止的char数组。因此,strlen()将从给定地址浏览整个内存,直到找到'\0'或导致分段错误。