如何在托管代码环境之外安全编程?

Rob*_*vey 8 c c++ pointers memory-leaks buffer-overrun

如果您是使用C或C++编程的人,没有内存管理的托管语言优势,类型检查或缓冲区溢出保护,使用指针算法,您如何确保您的程序是安全的?您是否使用了大量的单元测试,或者您只是一个谨慎的编码器?你有其他方法吗?

And*_*ith 24

上述所有的.我用:

  1. 很多谨慎
  2. 智能指针尽可能多
  3. 已经过测试的数据结构,很多标准库
  4. 一直进行单元测试
  5. 内存验证工具,如MemValidator和AppVerifier
  6. 每天晚上祈祷它不会在客户现场崩溃.

实际上,我只是夸大其词.它不是太糟糕,如果你正确地构造你的代码,它实际上不太难控制资源.

有趣的说明.我有一个大型应用程序,它使用DCOM并具有托管和非托管模块.非托管模块通常在开发期间更难调试,但由于在其上运行了许多测试,因此在客户站点上执行得非常好.托管模块有时会遇到错误的代码,因为垃圾收集器非常灵活,程序员在检查资源使用时会变得懒惰.

  • @ SDX2000:退休语言的概念是可笑的.每种语言都有助于解决不同应用空间中的问题.C#/ Java/C++/C都有不同(但重叠)的区域,它们发光,而其他区域则没有用.您不应该使用某种语言,因为它是您所知道的语言,您应该使用最适合您尝试编写程序的问题域的语言. (6认同)
  • @ SDX2000:我认为大多数经验丰富的C++开发人员都认为,与正确使用智能指针相比,垃圾收集效率最低,最糟糕的情况是拐杖.有可用于C++的垃圾收集器,但由于有效的实现和各种可用的智能指针实现,它们不受青睐.显然你对智能指针的理解似乎影响了你的意见我建议进一步阅读如何以及何时使用它们(因为auto_ptr的用途不是很有限,它有一个非常精确的定义使用(所有权转移)). (4认同)
  • @Martin - 在回答你的第二条评论时,你的确是可笑的.当我说C++现在要退休时,我应该更具体一些.我的意思是......现在是时候重新评估C++作为通用问题解决工具的地位,并停止在其他现代语言更好地服务的领域中使用.如果您曾经使用过C#,那么您就会知道C++是一个PITA.在过去的15年里,我一直用C++编程,我的C++ chops在这里没有问题. (3认同)
  • 我因为看到C++代码中的裸指针而变得过敏.如果我看到一个我的直觉是用智能指针包裹它,即使这是不必要的.本能对我有好处 - 我不记得有一个悬挂指针大概十年或更长时间. (2认同)

ste*_*eha 16

我使用了大量的断言,并构建了"调试"版本和"发布"版本.我的调试版本运行速度远远低于我的发布版本,并且所做的所有检查都是如此.

我经常在Valgrind下运行,我的代码没有内存泄漏.零.保持程序无泄漏比采取错误程序并修复所有泄漏要容易得多.

此外,我的代码编译没有警告,尽管事实上我有编译器设置额外的警告.有时警告是愚蠢的,但有时他们指向一个错误,我修复它而不需要在调试器中找到它.

我正在编写纯C(我不能在这个项目中使用C++),但我以非常一致的方式做C语言.我有面向对象的类,有构造函数和析构函数; 我必须亲自调用它们,但一致性有帮助.如果我忘了打电话给一个析构函数,Valgrind会打我的头,直到我解决它.

除了构造函数和析构函数之外,我还编写了一个自检函数来查看对象并确定它是否合理; 例如,如果文件句柄为空但关联的文件数据未归零,则表示存在某种错误(句柄被破坏,或文件未打开,但对象中的那些字段中有垃圾).此外,我的大多数对象都有一个"签名"字段,必须设置为特定值(特定于每个不同的对象).使用对象的函数通常断言对象是理智的.

每当我malloc()有一些记忆,我的功能就会用0xDC值填充内存.未完全初始化的结构变得显而易见:计数太大,指针无效(0xDCDCDCDC),当我查看调试器中的结构时,显然它是未初始化的.调用时,这比零填充内存要好得多malloc().(当然0xDC填充仅在调试版本中;不需要发布版本来浪费那段时间.)

任何时候我释放内存,我擦除指针.这样,如果我有一个愚蠢的错误,代码在其内存被释放后尝试使用指针,我立即得到一个空指针异常,这指向我正确的错误.我的析构函数不接受指向对象的指针,它们接受指针指针,并在破坏对象后破坏指针.此外,析构函数在释放它们之前擦除它们的对象,因此如果某些代码块具有指针的副本并尝试使用对象,则完整性检查断言会立即触发.

Valgrind会告诉我是否有任何代码写下缓冲区的末尾.如果我没有那个,我会在缓冲区结束后放入"canary"值,并进行完整性检查测试它们.这些金丝雀值,如签名值,将仅调试版本,因此发布版本不会有内存膨胀.

我有一系列单元测试,当我对代码进行任何重大更改时,运行单元测试非常令人欣慰,并且有一些信心我没有可怕的破坏.当然,我在调试版本和发布版本上运行单元测试,所以我的所有断言都有机会发现问题.

将所有这些结构放置到位需要付出额外的努力,但每天都能获得回报.当一个断言触发并指出我正确的错误时,我感到非常高兴,而不是必须在调试器中运行该错误.从长远来看,一直保持清洁的工作要少一些.

最后,我不得不说我其实喜欢匈牙利表示法.几年前我在微软工作,和乔尔一样,我学习了匈牙利应用程序而不是破碎的变体.它确实使错误的代码看起来错了.


Ste*_*314 13

同样相关 - 如何确保你的文件和套接字关闭,你的锁被释放,yada yada.内存不是唯一的资源,使用GC,您本身就会失去可靠/及时的破坏.

GC和非GC都不会自动优越.每个都有好处,每个都有它的价格,一个优秀的程序员应该能够应付这两个.

我在回答这个问题时也说了很多.