【问题标题】:I allocated memory from the heap to store a character but it is holding a string [duplicate]我从堆中分配内存来存储一个字符,但它包含一个字符串 [重复]
【发布时间】:2018-03-12 01:34:48
【问题描述】:
#include<stdio.h>
#include<stdlib.h>
int main()
{
    char* name;
    name=(char* )malloc(sizeof(char)); 
    printf("Enter a name: ");
    scanf("%s",name);
    printf("%s",name);
    return 0;
}

当我只是为单个字符分配内存时,上面的代码完美地存储了整个字符串。这怎么可能?

【问题讨论】:

  • "只是为单个字符分配内存。这怎么可能?" scanf() 只是扫描超出代码分配的内存末尾的其余部分。这样做会调用臭名昭著的未定义行为。

标签: c string character heap-memory allocation


【解决方案1】:

您只为 一个 字符分配了内存,这足以存储终止的空字节。你需要分配更多。

例如:

char *name = malloc(128); /* allocates 128 bytes */
printf("Enter a name: ");
if (fgets(name, 128, stdin) == NULL) {
    /* input failure */
}

请注意scanf() is notorious for reading input。更喜欢fgets()。另请注意,fgets() 将在换行符中读取,如果有您可能想要删除的空间。

另见:Why does some code carefully cast the values returned by malloc to the pointer type being allocated?

【讨论】:

  • fgets() 将始终在换行符中读取。如果缓冲区长度太短而无法读取完整的行,则将通过连续调用fgets() 部分读取该行。换行符将位于读取的最后一部分的末尾。
  • 不一定通过“fgets() 的连续调用”,而是通过下一个输入操作。
  • 没错,但您的示例显示了一个循环调用fgets(),而不是一个循环调用fgets() 与另一种输入样式混合。混合输入样式(例如 fgets() 后跟 scanf() 或反之亦然)无论如何都是一个非常糟糕的主意,因为可能会出现不需要的交互 - 例如一种方法留下 '\n' 待处理,而另一种方法读取它并停止在阅读预期的输入之前。
  • @Peter "但是你的例子显示了一个调用 fgets() 的循环" - 我的例子中没有循环。
  • 我没有混合输入样式 - 是的 - 我知道这很糟糕。但是我还是不明白你的意思。
【解决方案2】:

你有undefined behavior。非常scared。阅读Lattner's blog on UB

printf("%s",name);

name 是一个真正的字符串(即一些合法且有效的内存区域以零字节结尾)时,此方法有效。在您的程序中并非如此,您正在访问至少超过malloc-ed 区域的一个字节。你有一个buffer overflow

单字符区域是有效字符串的唯一情况是当单个char 包含零字节,因此字符串为空。

上面的代码完美地存储了一个完整的字符串

你只是运气不好(顺便说一句,这不是“完美”)。它可能会更糟(例如宇宙的崩溃),也可能会更好(例如一些segmentation violationnasal demons,....)。这就是为什么你应该害怕 UB。

(要解释在您的特定计算机上发生了什么,您需要深入了解实现细节,例如研究您的操作系统、编译器、指令集、编译器生成的汇编代码等... ; 你不想为此花费数年的时间,即使你做了它仍然是 UB)

您应该使用所有警告和调试信息 (gcc -Wall -Wextra -g) 进行编译,并使用调试器 gdbvalgrind 并确保您的程序处理的每个内存位置都是有效的。

顺便说一句,malloc 可能会失败,您应该对此进行测试,sizeof(char) 的定义是 1。您应该尝试

char* name = malloc(80);
if (!name) { perror("malloc"); exit(EXIT_FAILURE); };

还请阅读您正在使用的每个功能的文档。以printfmallocscanf 开头。请注意,您使用scanf 是危险的。最好用\n 结束您的printf 控制字符串或适当地使用fflush(因为stdout 通常是行缓冲的)。同时下载并研究C11编程语言的规范n1570

在某些系统上,您拥有的超出 C11 标准所保证的范围。例如,POSIX 有getline,你可以像here 一样使用它。如果您没有 POSIX 系统,还可以考虑 fgets(在 C11 标准中)(那么您将需要复杂的技巧来读取任意长的行,或者指定和 document 您的程序只能如果使用 malloc(80),则处理最多 79 个字节的行。

在您的代码中avoid arbitrary limits 是一种礼貌;当然,您的计算机仍然有局限性(您可能无法处理一千亿字节的行)。

注意character encoding(另请参阅this)。现在使用,在 2017 年,UTF-8 everywhere(例如与 libunistringglibhelp 一起使用)但请记住 Unicode 字符可以跨越 几个字节(即 @ 987654358@-s)。

【讨论】:

    【解决方案3】:

    您只能安全地将 一个 字符串存储到该数组中,并且该字符串的长度为零。

    也许,幸运的是,这段代码似乎可以在你的余生中工作,而且你永远不会听到任何关于它的抱怨......但这真的不太可能。

    键入更多的字符;用几 KB 捣碎键盘,我敢打赌你会开始看到分段错误。很高兴我向您展示了这一点,因为我可能为您节省了许多小时的调试时间!

    话虽如此,我不能提供 100% 的保证;这只是一个有根据的猜测。也许您的系统没有段错误。相反,也许您的系统让黑客绕过了安全措施。谁知道?行为是未定义

    相反,由于编码不安全,我本可以为您免去一场官司,但我们不能给未定义的行为下一个定义,只能给出标准编程语言给出的定义,即: 非便携不稳定

    现在您应该清楚避免这种情况的原因了。

    【讨论】:

      猜你喜欢
      • 2011-08-31
      • 1970-01-01
      • 2012-12-06
      • 1970-01-01
      • 2021-05-01
      • 2014-06-03
      • 1970-01-01
      • 2012-11-21
      • 2011-08-28
      相关资源
      最近更新 更多