我想知道与C和C ++相关的常见内存管理问题是什么。我们如何调试这些错误。
这里有一些我知道的
1)未初始化的变量使用
2)删除指针两次
3)编写数组越界
4)未能释放内存
5)竞争条件
1)malloc传回一个NULL指针。您需要将此指针强制转换为您想要的任何内容。
2)对于字符串,需要为结束字符分配一个额外的字节。
3)双指针。
4)(删除和malloc)和(免费和新的)不一起
5)查看实际函数在失败时返回(返回代码)的内容,如果失败则释放内存。 6)检查大小分配内存malloc(func +1)
7)检查你如何通过双pointe ** ptr来实现
8)检查行为未定义函数调用的数据大小
9)内存分配失败
答案 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时,我们知道它从未被初始化。
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)
答案 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,那么我知道我做错了什么。如果断言没有通过我可以做的所有测试,那么我对我的程序的内存管理正确性有了足够的信心。