【问题标题】:Malloc array of characters dynamic vs static CMalloc 字符数组动态与静态 C
【发布时间】:2015-09-15 20:17:47
【问题描述】:

所以我基本上是在尝试输入 scanf 的字母(它们之间没有间距),将每个字母放入一个数组中,并使用动态分配的数组 (malloc) 将相应的字母吐出到数组中。

崩溃

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

int main () {

    char *userInput = malloc(sizeof(char)*3); /* dynamic */
    scanf("%s", &userInput);
    printf("user inputed %c", userInput[1]);
    free(userInput);

    return 0;
}

运行

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

int main () {

    char userInput [3];  /* array */
    scanf("%s", &userInput);
    printf("user inputed %c", userInput[1]);

    return 0;
}

输入: asd

输出: s

我对动态分配数组的理解是 char userInput [3]; 等同于 char *userInput = malloc(sizeof(char)*3); 但显然从这种情况来看这不是真的?有人愿意解释/帮助吗?

【问题讨论】:

  • 问题不在于数组声明。相反,问题在于对 scanf() 及其格式字符串的调用。强烈建议阅读 scanf() 的手册页。事实上,两个发布的代码集都表现出相同的未定义行为

标签: c char malloc


【解决方案1】:

欢迎来到堆栈溢出!巧合的是,您的代码的主要问题是它容易受到堆栈溢出的影响。 scanf 无法知道 userInput 有多大,因为你没有告诉它,并且很乐意在你的非常短的数组结束之后继续填充内存。

如果您想精确捕获三个字符(没有 nul 终止符),请改用 scanf("%3c", userInput)。请注意,如果没有 NUL,您不能期望将 userInput 视为字符串;例如,通过printf 打印它会导致随机数量的乱码,因为 C 不知道字符串在哪里结束。

现在,回答您关于“malloc 和静态数组有什么区别”的实际问题:区别在于范围。如果您只在创建函数returns 之前使用userInput,则没有实际区别,但是当您尝试执行以下操作时,您就会遇到麻烦:

int function1 {
     char my_string[3];

     scanf("%3c", my_string);

     return my_string; /* WRONG! DANGER! */
}

上例中的return 将愉快地将指向my_string 的指针返回给调用函数。但是,一旦function1 返回,堆栈就会回滚,占用的内存my_string 基本上已经消失(并且可能已经重新使用)。结果是不可预测的,但几乎普遍对您的程序非常不利。

但是,如果您改用malloc(),则可以安全地返回my_string 指针,并且内存将持续存在,直到后来有人调用free(my_string)(其中my_string 是指向原始my_string 的指针;它不必同名!)。

这突出了另一个区别:使用诸如char my_string[3]; 之类的堆栈变量,您无需担心(实际上也不能)free() 内存,而如果内存是malloc()'d,您必须free()它如果你想回收内存。

上面有一些细微差别,例如文件范围的变量和static 函数变量,我将其作为进一步阅读的主题。

【讨论】:

  • @M.M c 转换说明符不添加空字符,而 s 转换说明符可以。对于大小为 3 的缓冲区,%3c 会很好,假设要读取 恰好 3 个字符,并且不需要空字符。否则,我会说大小为 3 的缓冲区应该使用 %2s
【解决方案2】:

正如 Giorgi 的 answer 所指出的,主要问题是 address-of 运算符 &amp; 的错误使用。


但是,为什么它在一个案例中起作用以及为什么它在另一个案例中不起作用的原因是非常有趣

  1. char array[3]:当您声明该数组时,将为它分配内存空间,array 将成为该位置(内存地址)的标签。当您将array 传递给scanf(或在其他任何地方使用它而不下标[])时,您将一个地址 传递给该函数。因此,当您在标签 array 上使用 &amp; 运算符时,它会向您返回相同的地址,但类型不同T(*)[3]),您的编译器可能抱怨。但是,由于内存地址是有效的,它按预期工作。

  2. char *array = malloc():当你声明那个变量的时候,内存也是为它保留的,但是这次在不同的地方,保留的空间是sizeof T(*),所以它可以保存一个内存地址。该变量在内存中也有一个地址,您也可以使用&amp;array 获取该地址。然后你malloc 一些内存和malloc 返回给你一个内存块的地址,你现在可以使用它。您只需执行array 即可获得该内存地址。因此,当您使用&amp;array 调用scanf 时,您传递的是变量地址而不是块地址。这就是它崩溃的原因(我猜你没有只输入两个字符)。

检查此代码:

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

int main(void)
{
    char *array[3];
    char *ptr = malloc(3 * sizeof(char));

    printf ("array : %p\n", array);
    printf ("&array: %p\n\n", &array);
    printf ("ptr   : %p\n", ptr);
    printf ("&ptr  : %p\n", &ptr);

    scanf("%s", &ptr);

    printf ("ptr   : %p\n", ptr);

    return 0;
}

哪些输出:

$ ./draft
array : 0x7ffe2ad05ca0
&array: 0x7ffe2ad05ca0

ptr   : 0x19a4010
&ptr  : 0x7ffe2ad05c98
ABCD
ptr   : 0x44434241

scanf获得了指针的地址,所以当它保​​存从stdin读取的值时,它会覆盖我们从malloc获得的地址!我们拥有的内存块现在消失了,内存正在泄漏......现在,这很糟糕,因为我们正在覆盖堆栈上的东西,内存正在泄漏,它会崩溃。

观察最后一个输出:ptr(之前是分配块的地址)的值现在是 0x44434241(DCBAASCII Table)! 有多好?

【讨论】:

  • @M.M:因为格式说明符类型与传入的内容不匹配,对吧?
  • @Giorgi 在情况 1 中,如果我们输入的字节数比 array_size更多 个字节,则它是 UB(越界写入)。在情况 2 中,如果我们比architecture pointer size 写入 更多 个字节(32 位为 4 个字节,64 位为 8 个字节),它将是 UB(再次越界写入)。
  • @EnzoFerber:在此之前是 UB,查看 M.M 的评论(由于类型不匹配)
猜你喜欢
  • 2020-07-31
  • 2018-07-29
  • 2015-04-16
  • 2017-03-29
  • 2019-07-06
  • 1970-01-01
  • 2011-02-09
  • 1970-01-01
  • 2013-07-20
相关资源
最近更新 更多