【问题标题】:strlen() of an empty array within a struct is not 0结构中空数组的 strlen() 不为 0
【发布时间】:2016-04-17 06:27:39
【问题描述】:

我对 C 很陌生,我不理解这种行为。在打印这个空数组的长度时,我得到 3 而不是 0。

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

typedef struct entry entry;

struct entry{
   char arr[16];
};

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

我在这里不明白什么?

【问题讨论】:

  • 你还没有初始化变量,所以数组可以包含任何东西;它根本不需要空终止。此外,打印strlen(a.arr) 的正确格式是%zu,因为strlen() 返回size_t
  • 一个安全的初始化它的方法是a.arr[0] = 0;,这样你就有一个零长度的字符串。

标签: c arrays struct


【解决方案1】:

entry a; 语句确实没有初始化结构,所以它的值很可能是垃圾。因此,不能保证其任何成员上的strlen 会返回任何合理的值。事实上,它甚至可能会导致程序崩溃,或者worse

【讨论】:

    【解决方案2】:

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

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

    (如果您碰巧使用的是 MSVC++“C”编译器,请注意 it might not support %zu。请获取真正的 C 编译器和 C 标准库。)

    【讨论】:

    • size_t 不是未签名的吗?我认为你必须使用%zu。还有一个虚拟的、著名的、传播广泛的操作系统,其printf() 功能无法识别%z
    • @jdarthenay 添加了一条关于著名且广为传播的 vir..er 操作系统的通知。
    • 不是MSVC编译器的问题,是运行时的问题。您可以使用 MinGW-w64 解决它,因为可以选择用自定义 MinGW 函数替换 printf 家族函数,请参阅here
    • @jdarthenay 编译器在我们谈论 MSVC 不编译器时是一个很好的错字。但是现在有人声称 VS 2015 实际上会支持 %z?
    • 对此不确定,但我可以告诉您,如果您在 Windows 程序中使用 printf("%lld");,您的程序将在 Windows 7 中运行,但在 Windows XP 中无法运行,因为这是一个 Windows DLL 问题如果我记得很清楚的话,是从 Windows Vista 解决的。
    【解决方案3】:

    这是 strlen() 的源代码:

    size_t strlen(const char *str)
    {
        const char *s;
        for (s = str; *s; ++s);
        return(s - str);
    }
    

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

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

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

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

    但是,如果它找不到零怎么办?它会停止吗?没有。它会一直跋涉,直到找到零或程序崩溃。

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

    还请记住,在 C 中,当您分配或搁置内存时,您基本上不知道内存包含什么。如果你想让它全为零,那么你需要确保自己用零填充它!

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

    【讨论】:

      【解决方案4】:

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

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

      【讨论】:

      • 也许你的意思是\0-terminated
      • @AnttiHaapala 确实,已修复。
      【解决方案5】:

      这里有什么不明白的地方?

      也许误解是auto变量,如:

      entry a;
      

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

      C 程序员通过将auto 变量清零或使用所需值初始化它们来学习初始化它们。

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

      int main(){
        entry a = 
          {
          .arr[0] = 0
          };
      
      ...
      } 
      

      或者:

      int main(){
        entry a;
      
        memset(&a, 0, sizeof(a));
      
      ...
      } 
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-11-28
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-03-28
        相关资源
        最近更新 更多