动态内存分配问题

Posted

技术标签:

【中文标题】动态内存分配问题【英文标题】:Dynamic memory allocation question 【发布时间】:2011-01-19 00:03:02 【问题描述】:

当你使用指针在堆上分配动态内存时,

char *buffer_heap = new char[15];

它将在内存中表示为:

 ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍýýýý««««««««þþþ

为什么末尾没有 NULL 终止字符,而不是 ýýýý««««««««þþþ?

【问题讨论】:

首先,谁说这甚至是一个字符串?对于编译器,您只需要 15 个原始字节的内存。如果你想要一个字符串,使用std::string。那么这个数据是什么?这只是发生在那里的任何事情。大多数编译器实际上会用调试数据或其他信息填充这些数据,因此当您使用未初始化的数据时,它可能会得到一致的模式。 我不知道为什么人们不赞成这个,这是一个完全有效的问题。仅仅因为 OP 误解了某事并不意味着我们应该为此惩罚他。 相关:***.com/questions/2029651/… ***.com/questions/958549/dynamically-allocated-char 一辈子都找不到完全相同的副本,但我发誓有一个... @dmckee:这里是另一个相关的:***.com/questions/370195/…。在我回答之前,我可能应该想到谷歌“site:***.com 0xCD 0xFD”,但这仍然不是一个完全正确的骗局。 @GMan:+1,很高兴看到有人支持 n00bs;缺乏知识并不是一种罪过,而不必费心去了解它并为此感到骄傲是:) 【参考方案1】:

你还没有初始化那个内存。你只是看到已经存在的东西......

【讨论】:

【参考方案2】:

虽然每个 C 样式字符串都表示为一个字符序列,但并非每个字符序列都是一个字符串。

\0 通常在您直接分配字符串文字或您自己添加时出现。只有当您将该数组视为具有将 \0 考虑在内的函数的字符串时,它才有意义。

如果你只是分配内存而不初始化它,它就会充满随机的东西。那里可能有一个 0 也可能没有 - 你将不得不在后续步骤中放一些有意义的东西。是否将其设为字符串取决于您。

【讨论】:

为什么总有 ýýýý««««««««þþþ 附加到结尾? @Dave17:那里没有总是相同的数据。进行循环以进行 100 个新的 char[15] 分配并查看。如果它总是相同的,那么它可能是你的编译器使用的调试模式。 我正在使用 VS-2005,我尝试了 1000 个新字符,但还是一样。 @Dave:那么您看到的只是调试数据,或其他内存跟踪信息,这些信息在释放时放置在那里。这不是你可以指望的东西,它只是垃圾。【参考方案3】:

因为char 是本机类型,所以它是未初始化的。 C++ 就是这样(它是 C 的遗产)。

只需接受,然后 0 自行终止:

char *buffer_heap = new char[15];
*buffer_heap = '\0';

或者如果你想初始化整个缓冲区:

std::fill(buffer, buffer + 15, 0);

【讨论】:

【参考方案4】:

你需要初始化它。可以通过显式调用默认构造函数将内置类型初始化为零:

char *b = new char[15]();

【讨论】:

【参考方案5】:

只有当你分配一个已初始化的类型时,它才会被初始化。否则,如果你想要一些有意义的值,你必须自己写。

另一方面,更好的答案是您一开始就不应该这样做。忘记new[]的存在,不要回头。

【讨论】:

仅适用于高级用户:记住new[] 存在,花点时间弄清楚如何覆盖放置和数组new 和delete,然后还是使用向量。 @Steve:或者就像关于优化的老话所说:规则#1:不要这样做。规则 #2(仅适用于高级程序员):现在不要这样做。 第 3 条规则(适用于超级高级程序员):停止修补并交付该死的东西 ;-)【参考方案6】:

Í 是字节 0xCD,Windows 调试分配器将其写入您的 15 字节内存,以表明它是未初始化的堆内存。未初始化的堆栈将为 0xCC。这个想法是,如果你曾经读取内存并意外地得到这个值,你可以对自己说,“嗯,我可能忘记初始化这个了”。此外,如果您将其作为指针读取并取消引用,那么 Windows 将使您的进程崩溃,而如果未初始化的缓冲区填充了随机或任意值,那么有时您会侥幸获得一个有效的指针,而您的代码可能会导致所有各种麻烦。 C++ 没有说明未初始化的内存持有什么值,非调试分配器不会浪费时间为每次分配使用特殊值填充内存,因此您绝不能依赖该值存在。

后面是 4 个字节的 ý(字节 0xFD),Windows 调试分配器使用它来指示缓冲区末尾的越界区域。这个想法是,如果你发现自己在调试器中写入一个看起来像这样的区域,你可以想“嗯,我可能已经超出了我的缓冲区”。此外,如果在释放缓冲区时值发生了变化,内存分配器会警告您代码错误。

« 是字节 0xAB,而 þ 是 0xFE。大概这些也是为了吸引眼球(它们不是合理的指针或偏移量,因此它们不构成堆结构的一部分)。我不知道它们是什么意思,可能是 0xFD 之类的更多保护数据。

最后,我猜你找到了一个 0 字节,即 15 字节缓冲区末尾之外的第 16 个字节(即从它的开头算起的第 31 个字节)。

以“C++”的形式提出问题而没有提及您使用的是 Windows,这表明这就是 C++ 的行为方式。不是,它是 C++ 的一种实现的行为方式,具有特定的编译器选项和/或链接的 dll。 C++ 不允许您读取缓冲区的末尾,Microsoft 只是对您很好,让您侥幸逃脱,不会崩溃或更糟。

【讨论】:

+1 用于详细说明每个十六进制代码,特别是用于说明调试器的技巧;也用于解释标记学科 我添加了 visual-c++ 标签,因为你是对的,问题需要它。 OP 可能不知道这是特定于实现的行为。【参考方案7】:

在 Linux 上的 GNU C++ (g++) 中,这个程序很快就退出了:

#include <algorithm>
#include <iterator>
#include <vector>
#include <cstddef>
#include <cstdlib>
#include <iostream>

namespace 

class rand_functor 
 public:
   int operator ()() const  return ::std::rand(); 
;



int main()

   using ::std::cout;
   using ::std::vector;
   using ::std::ostream_iterator;
   using ::std::generate;
   using ::std::equal;
   using ::std::copy;

   char *tmp = new char[1000];
   // This just fills a bunch of memory with random stuff, then deallocates it
   // in the hopes of making a match more likely.
   generate(tmp, tmp+1000, rand_functor());
   delete[] tmp;
   vector<char *> smalls;
   smalls.push_back(new char[15]);
   do 
      smalls.push_back(new char[15]);
    while (equal(smalls[0], smalls[0]+15, smalls[smalls.size() - 1]));
   cout << "        In one allocation I got: [";
   copy(smalls[0], smalls[0]+15, ostream_iterator<char>(cout));
   cout << "]\nAnd in another allocation I got: [";
   copy(smalls[smalls.size() - 1], smalls[smalls.size() - 1]+15,
        ostream_iterator<char>(cout));
   cout << "]\n";
   cout << "It took " << smalls.size() << " allocations to find a non-matching one.\n";
   return 0;

【讨论】:

以上是关于动态内存分配问题的主要内容,如果未能解决你的问题,请参考以下文章

为啥我们不能在堆栈上分配动态内存?

C语言中的动态内存分配的用法举例

动态内存分配

连续内存分配:内存碎片与分区的动态分配

在 C++ 中的 2D 动态内存分配数组中释放分配的内存

为啥在使用 realloc() 进行动态内存分配之后再添加一块内存?