【问题标题】:Declaring variable length strings in c - apparently dynamic?在 c 中声明可变长度字符串 - 显然是动态的?
【发布时间】:2016-10-26 20:23:25
【问题描述】:

我正在尝试了解有关如何在 c 中定义“字符串”的基本知识。

char s[2];
scanf("%s", s);
printf("%s", s);
printf("sizeof s %d", sizeof(s));

我不是 c 程序员。我知道使用 scanf 获取用户输入存在问题,我什至没有检查它的返回值等。这只是为了了解有关声明字符串的一些基本知识。

鉴于上面的代码,如果我输入say 'helloworld' 会打印'helloworld'。好的。但我想通过说 char s[2] 我说的是像 's 是一个长度为 2 的数组,其中每个元素的类型都是 char'。

因此,我期待看到“他”被打印出来。不是“你好世界”。因为我的 s 数组只有 2 个字符的空间。

sizeof 仍然返回 2。但看起来我的数组已经增长到用户输入的大小。

发生了什么?

【问题讨论】:

  • 它没有增长。您已经为 s 分配了 2 个字符,这就是在堆栈上为它预留了多少内存。然而,scanf() 只是接收到一个指向内存的指针。它无法知道分配了多少内存,所以它只是读取和存储,写入可能为其他东西设置的内存,直到完成。内存管理部分由您决定。这些类型的错误是许多“什么...?”的根源。调试时刻,因为它通常看起来像 else 被破坏了。
  • 为什么他的scanf中没有&s?
  • 您正在覆盖数组的边界,从而导致未定义的行为。你是正确的,你的数组只有 2 个chars。你很幸运,写超过 2 chars 并没有破坏其他东西。在另一台机器上或使用不同的编译器选项时,您可能不会那么幸运。此外,c 字符串以 NULL (\0) 结尾,因此您需要在数组中添加一个额外的字符。 “helloworld” 的长度为 10 chars,这意味着您需要一个 char 数组长度为 11 chars 才能正确存储它。
  • 不,阵列没有增长。通过输入长文本,您已经覆盖了数组外部的部分堆栈(或分配数组的任何内存)。当堆栈内存发生这种情况时,通常会导致崩溃或执行不需要的代码,尤其是当代码是远程访问服务的一部分时。
  • 你暴露了scanf 中的缺陷,那就是它不是内存安全的。如果你在这里使用fgets,你会得到h作为你的回报(不要忘记空终止符)。

标签: c


【解决方案1】:

这充分说明了为什么scanf 如此危险。您已经覆盖了其他不打算用于此目的的内存。读取恒定长度字符串的一种更安全的方法是执行类似的操作

char a[2];
fgets(a, sizeof(a), stdin);
printf("%s\n", a);

如果你这样做并且你输入了helloworld,你只会得到h到stdout(因为字符串有sizeof(a) - 1字符由于空终止符。这意味着数组a实际上是{'h', '\0'})。 fgetsscanf 读取恒定大小的字符串更安全。

【讨论】:

    【解决方案2】:

    两个问题:

    • C 不对数组访问进行任何边界检查;
    • 在大多数情况下,数组类型的表达式会被转换为指针类型的表达式,表达式的值将是数组第一个元素的地址。这包括作为参数传递给函数的数组表达式。

    %s 转换规范告诉printf 打印从指定地址开始的字符的序列,直到它看到字符串末尾的0 终止符。同样,它告诉scanf 存储从指定地址开始的字符序列,直到它看到空白字符或EOF。

    当您将表达式 s 作为参数传递给scanfprintf 时,表达式将从“char 的2 元素数组”类型转换为“指向char",表达式的值是数组第一个元素的地址(相当于传递表达式&s[0])。

    所有scanf 接收到的是一个指针值——它不知道从该地址开始的数组有多大。所以它不知道s 仅大到可以包含两个字符。相反,它会愉快地将那些额外的字符写入数组末尾。同样,printf 不知道数组只有 2 个字符宽 - 它只是继续打印,直到看到 0 终止符。

    您可以指定一个字段宽度作为转换的一部分:

    scanf( "%1s", s ); 
    

    这将从标准输入最多读取 1 个字符并将其存储到 s。请记住,string 是一个字符序列,后跟一个 0 终止符,因此要存储 N 个字符的字符串,需要预留一个 N+1 个元素的数组来存储它。

    【讨论】:

    • 不错的答案。详细信息:“C 不对数组访问进行任何边界检查;” --> 就是简单的UB,一个编译器可以做边界检查。边界检查当然不是指定的 C 行为。
    【解决方案3】:

    我认为还没有人真正回答过这个问题。这里有两个问题...

    首先,数组“s”只有 2 个字节长,所以 scanf 对任何超过 1 个字符的字符串都会产生不希望的结果。

    char s[255]; // was char s[2];
    

    另外,如前所述,scanf 是一种输入字符串的可怕方式。请改用 fgets

    fgets(s, sizeof(s), stdin); // was scanf("%s", s);
    

    其次,不能使用sizeof来获取字符串的长度。它将返回包含字符串的数组的长度(不是你想要的,相信我)。相反,请使用 strlen

    printf("strlen s %d", strlen(s)); // was printf("sizeof s %d", sizeof(s));
    

    【讨论】:

    • 详细信息:代码可以使用sizeof 来获取字符串的大小。 strlen(p) 使用 pointer p,它指向一个 string 并返回字符串的长度。在 C 中,string 是一个数组。
    猜你喜欢
    • 2022-11-14
    • 1970-01-01
    • 2013-10-09
    • 1970-01-01
    • 2012-05-28
    • 1970-01-01
    • 2020-08-04
    • 1970-01-01
    • 2015-11-26
    相关资源
    最近更新 更多