【问题标题】:allocate memory for string in C [duplicate]在C中为字符串分配内存[重复]
【发布时间】:2017-02-21 19:19:00
【问题描述】:

我刚开始 C 编程,我有一个初学者问题:

int main(int argc, char *argv[])
{ 
char *a=malloc(1*sizeof(char));
a[0]='a';
a[1]='b';
a[2]='c';
printf("%c\n",a[0]);
printf("%c\n",a[1]);
printf("%c\n",a[2]);
printf("%s\n",a);
return 0;
}

所以我想通过一个一个地输入字符来创建一个由未知长度的单词组成的字符串。因为我不知道单词的长度,所以我只使用 malloc。我打算先为一个字符分配内存,然后在输入新字符时使用 reallocate 为下一个字符添加新空间。但是,在我 malloc (1*sizeof(char)) 应该只有一个字符的大小之后,我发现我已经可以在字符串中添加多个字符,为什么会这样?正确的方法是什么?

感谢大家花时间阅读我的长问题:)

【问题讨论】:

  • “为什么会这样?” - 因为未定义的行为很神奇
  • 您的代码有一个错误,因此它不会像您预期的那样运行。只是修复错误。要准确理解错误代码的复杂故障模式,还有很长的路要走。所以暂时避免错误。
  • 经常 - 你不能依赖它并且你完全不应该 - 内存管理器将分配比你请求的更大的内存块。因此,您可以在未向您报告为可用的内存区域中写入,然而,它是可用的。当然,该区域可能会在以后使用,因此您在那里写的内容可能会丢失。或者彻底改变。或者您可能正在覆盖内存管理器本身使用的一些重要数据。简而言之,仅仅因为你可以做某事,并不意味着你应该
  • sizeof(char) 是多余的和不必要的。
  • 感谢大家的回答!我不明白未定义的行为,我现在明白了!

标签: c string char malloc


【解决方案1】:

你的意思是“为什么会这样”,

  • 为什么允许
  • 为什么没有“惩罚”(即立即崩溃)?

允许是因为C直接访问内存;这是它的力量的一部分。在让您执行之前,很少会检查您正在尝试执行的操作。这就是为什么你需要小心。

为什么它不以崩溃“惩罚”,而不是立即,也许永远不会?因为并不总是禁止在该区域写入(内存保护是面向页面的)。假设当您分配一个内存区域时,它被划分为 1000 个字节的页面。那么如果你分配 50 个字节,底层硬件将解锁 1000 个字节。它无法解锁较小的区域。因此,您“可以”写入所有这 1000 个字节而不会导致保护错误。

现在内存管理器必须跟踪数据在哪里,所以它有自己的结构,而且它也经常“分页”内存。因此,当您请求 50 个字节时,软件内存管理器实际上可能会分配 256 个。然后,如果您将这 50 个字节重新分配到 100,您将看到指针没有改变。如果你 realloc() 那些到 257 字节,指针 确实 改变 - 内存管理器不能将该块扩大到 257 字节,所以它将它标记为空闲,并从硬件在其他地方分配一个 512 块。如果你随后 alloc() 42 字节,你可能会发现它的指针与之前指向你的 100 字节缓冲区的地址相同。

有时,一些调试库不仅会分配一个区域,还会用金丝雀“保护”它。您要求 50 个字节,库分配 66 个字节并在这 66 个字节内返回一个指针 8 个字节。它用已知值填充前 8 个字节和后 8 个字节。它会不时检查该值是否仍然存在;如果不是,则会引发软崩溃以警告您溢出(或下溢)缓冲区。

在您的示例中,没有此类保护,您可以在分配的额外区域中写入。但很可能该区域稍后会被使用并被覆盖:也许,如果你这样做

foo = malloc(20);
strcpy(foo, "string ... 30 bytes long");
bar = malloc(20); //     ^20th byte
strcpy(bar, "hello world");

然后打印 foo,你会得到“string ... 3hello world”。或“字符串 ...[GARBAGE]hello world”。通过在 foo 之后写入 bar,您覆盖了存储数据的区域。

再说一次,如果你从来没有在 bar 中写过任何东西,那么程序可能会工作并且不会抱怨。

然后你在不同的平台或不同的库上编译,一个运行了多年的程序突然崩溃了。欢迎来到未定义行为的世界。

有几个库和工具可用于解决此类问题 - 一个非常好的工具是 valgrind

“删除”字符串和/或释放其内存

// I initialize the pointer to NULL. If I just declared the pointer,
// its initial value might be anything. This way, I reduce the random
// element in my program. Makes no difference... except that one time
// when it does, and will save your bacon.
char *pwd = NULL;

// Every malloc and realloc MUST check that it did not return NULL,
// meaning an error occurred. Even for small memory blocks.
if (NULL === (pwd = malloc(200))) {
     // Handle out of memory error
}
strcpy(pwd, "Squeamish Ossifrage"):

// ... do something with pwd

// ...we're done. If we just freed this area, its contents would remain
// available *and* the pointer would still point to it. so this works:
/*
      free(pwd);
      printf("The secret word is %s\n", pwd);
      ...but might explode at any moment.
*/
// pwd contains sensitive data, so we first zero it, and this requires
// remembering the actual size of the allocated block. Here, 200.
memset(pwd, 0, 200);

// Now we free the area pointed to by the pointer. Then we also
// erase the pointer.
free(pwd); pwd = NULL;

通过在同一行写free和NULL,我可以运行

grep 'free\\s*(' | grep -v "NULL;"

并找到所有 free() 没有 NULL 赋值的行,并将这些行标记为可能需要改进。

现在,如果我在释放 pwd 后使用它,它将永远无法工作,这会进一步消除执行中的随机性。

【讨论】:

  • 很好的答案!这真的很有帮助,谢谢!
  • 嗨,所以如果我想“清空”字符串并将其他未知长度的字符放入字符串中,然后再将字符串的内存重新分配为“1 char”“2char”,依此类推等等,我应该调用 memset 将它们全部设置为 0 还是只释放它们?
  • 你首先用新的长度重新分配,然后重写。要擦除字符串(例如密码),您首先要 memset,然后再释放。我稍后会更新我的答案。
  • 好的,这回答了我的问题,谢谢!
【解决方案2】:

发生这种情况是因为您可以这样做,但这是未定义的行为。这称为缓冲区溢出,这是一种危险的编程情况。您应该分配超过 1 个字节的内存,并跟踪字符串的长度。一旦达到分配空间的末尾,您可以调用realloc 将内存块重新分配到更大的大小。

【讨论】:

    【解决方案3】:
    int main(/*dont write what you dont         use*/)
    { 
    char *a=malloc(4/*at least*/*sizeof(char));
    a[0]='a';
    a[1]='b';
    a[2]='c';
    a[3]='\0';
    //or strcpy(a, "abc");
    printf("%c\n",a[0]);
    printf("%c\n",a[1]);
    printf("%c\n",a[2]);
    printf("%s\n",a);
    free(a);
    return 0;
    }
    

    记住:当你写“?”编译器得到

    {?, '\0'}

    '\0' 是字符串结尾字符。

    【讨论】:

    • 嗨,所以如果我想“清空”字符串并将其他未知长度的字符放入字符串中,然后再将字符串的内存重新分配为“1 char”“2char”,依此类推等等,我应该调用 memset 将它们全部设置为 0 还是只释放它们?
    • 当你释放它们时,它们将永远消失。你之前不需要做任何事情。
    • 建议a=NULL;
    • 免费后.......
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-03-18
    • 2021-07-06
    • 2022-01-01
    • 1970-01-01
    相关资源
    最近更新 更多