C ++中的内存管理问题

时间:2010-08-17 19:58:13

标签: c++ c

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

这里有一些我知道的

1)未初始化的变量使用

2)删除指针两次

3)编写数组越界

4)未能释放内存

5)竞争条件

1)malloc传回一个NULL指针。您需要将此指针强制转换为您想要的任何内容。

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

3)双指针。

4)(删除和malloc)和(免费和新的)不一起

5)查看实际函数在失败时返回(返回代码)的内容,如果失败则释放内存。 6)检查大小分配内存malloc(func +1)

7)检查你如何通过双pointe ** ptr来实现

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

9)内存分配失败

10 个答案:

答案 0 :(得分:9)

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

答案 1 :(得分:6)

首先抢先预防这些错误:

1)将警告转到错误级别以克服未初始化的错误。编译器会经常发出这样的警告,并将它们作为错误访问,您将被迫解决问题。

2)使用Smart pointers。你可以在Boost中找到这些东西的好版本。

3)使用vectors或其他STL containers。除非您使用Boost variety之一,否则请勿使用数组。

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

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

处理遗留应用程序

1)与上述相同。

2)使用集成测试来查看应用程序的不同组件如何发挥作用。这应该找到很多这种错误的情况。认真考虑由另一个小组完成正式的同行评审,该小组编写了与您的裸指针接触的应用程序的不同部分。

3)您可以重载new运算符,以便在对象之前和之后分配一个额外的字节。然后应该用一些易于识别的值填充这些字节,例如0xDEADBEEF。然后你要做的就是检查前后的字节,看看你的内存是否以及何时被这些错误破坏。

4)通过多次运行应用程序的各种组件来跟踪内存使用情况。如果你的记忆力增长,检查是否缺少解除分配。

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

答案 2 :(得分:2)

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

答案 3 :(得分:1)

你忘了一个:

6)释放后取消引用指针。

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

假设您正在使用已经存在其中一些问题的代码,这里有一些关于调试的想法。

  • 未初始化的变量使用

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

    1. 删除指针两次

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。确保您的日志记录系统具有准确的时间戳。

答案 4 :(得分:1)

  1. 使用好的编译器并将警告级别设置为max
  2. 包装new / malloc并删除/免费并预订所有分配/解除分配
  3. 将原始数组替换为执行边界检查的数组类(或使用std :: vector)(在C中更难做)
  4. 见2.
  5. 这很难,有一些特殊的调试器,比如jinx专门研究这个,但我不知道它们有多好。

答案 5 :(得分:1)

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

答案 6 :(得分:1)

看看my earlier answer to "Any reason to overload global new and delete?"你会在这里找到一些有助于早期发现和诊断的东西,以及一系列有用的工具。大多数工具和技术都可以应用于C或C ++。

值得注意的是valgrind的memcheck会发现你的4个项目,而helgrind可能会帮助发现最后一个(数据竞赛)。

答案 7 :(得分:1)

  

1)未初始化的变量使用

由编译器自动检测(将警告变为完全并将警告视为错误)。

  

2)删除指针两次

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

  

3)编写数组越界

不要这样做。这是一个逻辑错误。 你可以通过uisng一个容器和一个抛出越界访问的方法来缓解它(vector :: at())

  

4)未能释放内存

不要使用RAW指针。见上文(2)。

  

5)竞争条件

不要允许它们。按优先级顺序分配资源以避免冲突锁定,然后在有可能进行多次写入访问时锁定对象(或在重要时读取访问权限)。

答案 8 :(得分:0)

我所知道的最好的技术是避免直接进行指针操作和动态分配。在C ++中,使用引用参数优先于指针。使用stl对象而不是滚动自己的列表和容器。使用std::string代替char *。如果做不到这一点,请遵守Rob K的建议,并在需要进行迁移的任何地方使用RAII。

对于C,你可以尝试做一些简单的事情,但你几乎要注定失败。获取Lint的副本并祈求怜悯。

答案 9 :(得分:0)

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

我在所有分配器类中保留以下三个私有变量:

  size_t news_;
  size_t deletes_;
  size_t in_use_;

在allocator构造函数中,所有这三个都初始化为0。

然后,  每当分配器执行一个新的时,它会增加news_,和  每当分配器执行删除时,它会递增删除_

基于此,我将很多断言放在分配器代码中:

 assert( news_ - deletes_ == in_use_ );

这对我来说非常有用。

添加:我将断言设置为前提条件后置条件,用于分配器的所有非平凡方法。如果断言blovs,那么我知道我做错了什么。如果断言没有通过我可以做的所有测试,那么我对我的程序的内存管理正确性有了足够的信心。