2010-08-17 43 views
2

我想知道与C和C++相关的常见内存管理问题。我们如何调试这些错误。C++中的内存管理问题

这里有一些我知道

1)未初始化的变量使用

2)删除指针两次

3)书写阵列出未能解除分配内存边界

4)的

5)竞赛条件

1)malloc传回一个NULL指针。你需要将这个指针转换为任何你想要的。

2)对于字符串,需要为结尾字符分配一个额外的字节。

3)双指针。

4)(删除)和malloc()和(自由和新的)不一起去

5)看到什么实际的函数返回(返回代码)失败并释放内存,如果它失败。 6)检查大小分配存储器的malloc(FUNC 1)

7)检查如何ü通过双足尖** PTR起作用

8)检查数据大小行为未定义函数调用

9)内存分配失败

+6

解决它们?他们是错误的,只是不要做导致他们的行为。 – James 2010-08-17 20:02:05

+3

由于您要求提供一个列表,而不是一个正确的答案,所以这应该是社区wiki。 – Cascabel 2010-08-17 20:03:00

+1

它没有解决它的调试 – brett 2010-08-17 20:04:17

回答

6

先发制人预防这些错误摆在首位:

1)打开警告,错误级别,克服了未初始化错误。编译器会经常发出这样的警告,并通过让他们访问错误来解决问题。

2)使用Smart pointers。你可以在Boost中找到这样的好东西。 3)使用vectors或其他STL containers。除非你使用Boost variety之一,否则不要使用数组。

4)再次,使用容器对象或智能指针为您处理此问题。

5)在任何地方都可以使用不可变的数据结构,并在共享可变对象的修改点周围放置锁。

与传统应用程序

1)同上的处理。

2)使用集成测试来了解应用程序的不同组件如何发挥出来。这应该会发现许多这种错误的情况。认真考虑由另一个小组完成正式的同行评审,撰写与你的裸指接触的应用程序的不同部分。

3)您可以重载new运算符,以便它在对象前后分配一个额外的字节。这些字节应该填充一些容易识别的值,例如0xDEADBEEF。所有你需要做的就是检查前后的字节,以便见证你的内存是否被这样的错误破坏。

4)通过多次运行应用程序的各种组件来跟踪您的内存使用情况。如果你的内存增长,检查丢失的释放。

5)祝你好运。对不起,但这是可以在99.9%的时间内工作的事情之一,然后,繁荣!客户抱怨。

+0

+1:这是一个好的列表 – Arun 2010-08-17 20:21:56

+0

谢谢。我已经处理了由最低出价转包商构建的第三方“C++”应用程序的公平份额,这些应用程序只返回了裸指针。 – wheaties 2010-08-17 20:30:35

9

使用RAII(资源获取初始化)。你几乎不应该在你的代码中直接使用new和delete。

+0

+1为理想。但是,有很多API(特别是在嵌入式领域)与RAI混淆。 – 2010-08-17 20:04:47

+0

这是一种预防措施,它不适用于调试。 – Potatoswatter 2010-08-17 21:32:53

+1

搞砸RAII的API通常可以隐藏在隐藏丑陋的类中。但我没有做嵌入的东西,所以YMMV在那里。 – 2010-08-20 20:09:54

0

我所知道的最好技术是避免直接执行指针操作和动态分配。在C++中,使用引用参数优先于指针。使用stl对象而不是滚动您自己的列表和容器。使用std::string而不是char *。如果没有这些,请采取Rob K的建议,并在需要进行分区的地方使用RAII。

对于C来说,有一些类似的事情可以尝试去做,但是你几乎注定要失败。获得Lint的副本并祈求怜悯。

1

一个你忘记了:

6)解除引用后,它已被释放。

到目前为止,大家似乎都在回答“如何预防”,而不是“如何调试”。

假设你正在处理已经存在这些问题的代码,这里有一些关于调试的想法。

  • 未初始化的变量使用

    编译器可以检测出了很多本。将RAM初始化为一个已知值有助于调试那些转义的内容。在我们的嵌入式系统中,我们在离开引导加载程序之前进行内存测试,这会将所有内存设置为0x5555。这对于调试非常有用:当一个整数== 21845时,我们知道它从未初始化过。

  • 删除指针两次

Visual Studio中应在运行时检测此。如果你怀疑这是在其他系统中发生的事情,你可以通过定制代码来替换删除调用像

void delete(void*p){ assert(*(int*)p!=p); _system_delete(p); *(int*)p=p;} 
  • 写作数组越界调试

的Visual Studio应该在运行时检测出。在其他系统中,添加自己的哨兵

int headZONE = 0xDEAD; 
int array[whatever]; 
int tailZONE = 0xDEAD; 

//add this line to check for overruns 
//- place it using binary search to zero in on trouble spot 
assert(headZONE==tailZONE&&tailZONE==0xDEAD) 
  • 未能释放内存

    观看堆栈增长。记录创建和销毁对象之前和之后的空闲堆大小;寻找意想不到的变化。可能在内存函数周围编写自己的包装以跟踪块。

  • 竞赛条件

    aaargh。确保你有一个准确的时间戳记录系统。

1
  1. 使用一个很好的编译器,并设置警告级别设置为最大
  2. 总结新/ malloc和删除/自由bookkeep所有的分配/释放操作
  3. 一个数组的类,它的边界检查更换原阵列(或使用std ::矢量)(很难在C做)
  4. 请参阅第2
  5. 这是很难的,存在一些特殊的调试程序,如厄运,专门在这一点,但我不知道他们是怎么好。
+0

还有std :: array。在C++ 0x。 – Arun 2010-08-17 20:19:18

1

确保您了解何时将对象放置在堆上以及何时放在堆栈上。作为一般规则,如果必要的话,只将对象放在堆上,这样可以避免很多麻烦。学习STL并使用标准库提供的容器。

0

我使用的一种常见模式如下。

我把以下三个私人变量中的所有分配器类:

size_t news_; 
    size_t deletes_; 
    size_t in_use_; 

中的分配部构造,三者都被初始化为0。

上。然后, 每当分配不一个新的,它增加news_,并 每当分配器做一个删除,它增量deletes_

基于此,我把很多屁股ers在分配器代码中为:

assert(news_ - deletes_ == in_use_); 

这对我来说非常好。

加成:我把断言为前提和分配器的所有非平凡的方法后置条件。如果断言blovs,那么我知道我做错了什么。如果断言没有发挥作用,那么我可以做所有的测试,那么我对我的程序的内存管理正确性有充分的信心。

2

除了已经说过的所有内容之外,还可以使用valgrind或Bounds Checker来检测程序中的所有错误(竞赛条件除外)。

1

1)未初始化的变量使用

由编译器自动检测(转警示全和警告视为错误)。

2)删除一个指针两次

不要使用原始指针。所有指针都应该在智能指针或管理指针生命周期的某种形式的RAII对象中。

3)写数组越界

不这样做的。这是一个合乎逻辑的错误。 您可以通过uisng容器和抛出的出界访问的方法(矢量::在())

4)不释放内存

不要使用RAW减轻它指针。参见上面的(2)。

5)竞争条件

不要让它们。按优先级顺序分配资源以避免冲突锁定,然后在存在多个写入访问的可能性(或重要时为读取访问)时锁定对象。

+0

+1。此外,#1,你的意思是“把**警告作为错误**。 – Poni 2010-08-17 21:00:23