动态内存分配问题

11

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

char *buffer_heap = new char[15];

它将在内存中表示为:

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

为什么末尾没有空字符,而是以ýýýý««««««««þþþ作为结尾?


3
首先,谁说这是一个字符串?对编译器来说,你只是想要15个原始字节的内存。如果你想要一个字符串,请使用std::string。那么这些数据是什么呢?它只是那里恰好存在的任何内容。大多数编译器实际上会用调试数据或其他信息填充这些数据,因此当你使用未初始化的数据时,它可能具有一致的模式。 - GManNickG
8
我不知道为什么人们要对这个进行负投票,这是一个完全合理的问题。仅仅因为楼主误解了某些事情,并不意味着我们应该惩罚他。 - GManNickG
2
@dmckee:这里还有一个相关的问题:https://dev59.com/ZXRC5IYBdhLWcg3wOeWB。在回答之前,我可能应该考虑通过Google搜索“site:stackoverflow.com 0xCD 0xFD”,但仍然不完全相同。 - Steve Jessop
2
@GMan: +1,很高兴看到有人支持新手;缺乏知识并不是罪过,但不愿意去了解并为此感到自豪就有些问题了 :) - legends2k
@Steve:0xcd 0xfd是与编译器有关的,我从未使用过那个... - dmckee --- ex-moderator kitten
显示剩余2条评论
7个回答

22

在Windows中,Í代表字节0xCD,Windows调试分配器会将其写入你的15个字节内存中以表示未初始化的堆内存。未初始化的栈则是0xCC。这样做的想法是,如果你读取内存并意外地得到这个值,你可以想一想,“嗯,我可能忘记初始化了”。此外,如果你将其作为指针并进行取消引用操作,Windows会崩溃你的进程,而如果未初始化的缓冲区被填充了随机或任意值,那么有时你可能会得到一个有效的指针,你的代码可能会造成各种麻烦。C++不会说明未初始化内存保存什么值,非调试分配器不会浪费时间为每个分配填充内存以特殊的值,因此你绝不能依赖于那个值。

接下来是4字节的ý(字节0xFD),Windows调试分配器用它来表示缓冲区末尾越界的区域。这样做的想法是,如果你在调试器中发现自己正在写入一个看起来像这样的区域,你可以想到“嗯,我可能越界了我的缓冲区”。此外,如果当缓冲区被释放时该值已更改,内存分配器可以警告你的代码有误。

«代表字节0xAB,þ则是0xFE。这些也可能被用作眼镜蛇(它们不是合理的指针或偏移量,因此它们不构成堆结构的一部分)。我不知道它们的意义,可能是像0xFD一样的更多保护数据。

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

如果没有提到你在Windows上工作,仅仅将问题描述为“C++”,这可能会让人误以为这就是C++的行为。实际上这不是C++的行为,而是特定编译器选项和/或链接的动态链接库导致的行为。C++不允许你读取超出缓冲区末尾的内容,Microsoft只是对你好,让你没有崩溃或更严重的后果。


3
好的,我会尽力进行翻译。对于每个十六进制代码进行详细解释很好,特别是关于调试器技巧的解释,以及解释标记规范,这些都值得加分。 - legends2k
我添加了Visual C++标签,因为你是正确的,这个问题需要它。OP可能不知道这是特定于实现的行为。 - Omnifarious

6

您没有初始化该内存。您只能看到已经存在的内容...



4

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

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

1

因为char是一种本地类型,所以它未初始化。这就是C++的工作方式(这是C的遗留问题)。

接受这一点并自己进行0终止:

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

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

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

1

虽然每个C风格的字符串都表示为char序列,但并不是每个char序列都是字符串。

\0通常在直接赋值字符串字面量时出现,或者您自己添加它。只有在使用考虑到\0的函数处理该数组作为字符串时,它才有意义。

如果您只分配内存而不初始化它,则其中充满了随机内容。可能会有一个0,也可能没有-您将不得不在随后的步骤中放置一些有意义的内容。由您决定是否将其设置为字符串。


为什么末尾总是会出现ýýýý««««««««þþþ? - cpx
3
@Dave17: 那里面的数据并不总是相同的。可以编写一个循环来进行100次新的char[15]分配并查看结果。如果数据始终相同,那么这可能是编译器使用的调试模式。 - Zan Lynx
我正在使用VS-2005,尝试了1000个新字符,但仍然一样。 - cpx
1
@Dave: 那你只是看到了调试数据,或者其他在释放内存时放置在那里的内存跟踪信息。这不是你可以依靠的东西,只是垃圾。 - GManNickG

1

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

另一方面,更好的答案是你根本不应该这样做。忘记 new[] 的存在,并且不要再回头看它。


仅适用于高级用户:请记住new[]的存在,花一些时间弄清楚如何覆盖放置和数组的新建和删除,然后无论如何都使用向量。 - Steve Jessop
@Steve:或者按照有关优化的老话来说:规则 #1:不要这样做。规则 #2(仅适用于高级程序员):现在不要这样做。 - Jerry Coffin
规则#3(适用于超级高级程序员):停止折腾,发布该死的东西;-) - Steve Jessop

0
在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;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接