7

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

4

9 回答 9

24

上述所有的。我用:

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

其实我只是在夸大其词。如果你正确地构建你的代码,它并不算太糟糕,而且实际上并不难控制资源。

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

于 2009-10-07T05:39:23.427 回答
16

我使用了大量的断言,并构建了一个“调试”版本和一个“发布”版本。我的调试版本运行速度比我的发布版本慢得多,它所做的所有检查。

我经常在Valgrind下运行,我的代码内存泄漏为零。零。保持程序无泄漏比使用有缺陷的程序并修复所有泄漏要容易得多。

此外,我的代码编译时没有警告,尽管我已经为额外的警告设置了编译器。有时警告很愚蠢,但有时它们指向一个错误,我修复它而不需要在调试器中找到它。

我正在编写纯 C(我不能在这个项目中使用 C++),但我正在以非常一致的方式编写 C。我有面向对象的类,带有构造函数和析构函数;我必须手动调用它们,但一致性会有所帮助。如果我忘记调用析构函数,Valgrind 会打我的头,直到我修复它。

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

每当我malloc()有一些记忆时,我的函数都会用值填充内存0xDC。未完全初始化的结构变得明显:计数太大,指针无效(0xDCDCDCDC),当我在调试器中查看结构时,很明显它未初始化。这比调用时的零填充内存要好得多malloc()。(当然0xDC填充只在调试版本中;发布版本不需要浪费时间。)

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

Valgrind 会告诉我是否有任何代码注销了缓冲区的末尾。如果我没有那个,我会在缓冲区的末尾放置“金丝雀”值,并让健全性检查测试它们。这些金丝雀值,就像签名值一样,只能调试构建,所以发布版本不会有内存膨胀。

我有一组单元测试,当我对代码进行任何重大更改时,运行单元测试并相信我没有严重破坏事情是非常令人欣慰的。当然,我在调试版本和发布版本上运行单元测试,所以我所有的断言都有机会发现问题。

将所有这些结构安装到位需要付出一些额外的努力,但每天都会有回报。当断言触发并将我指向错误时,我感到非常高兴,而不必在调试器中运行错误。从长远来看,始终保持清洁的工作量会减少。

最后,我不得不说我其实很喜欢匈牙利符号。几年前我在微软工作过,和 Joel 一样,我学习了 Apps Hungarian,而不是坏掉的变体。它确实使错误的代码看起来不对

于 2009-10-07T06:23:05.957 回答
13

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

GC 和非 GC 都不是自动优越的。各有利弊,各有各的代价,一个好的程序员应该能够同时应付这两者。

我在回答这个问题时说了这么多。

于 2009-10-07T05:54:23.833 回答
3

我已经使用 C++ 10 年了。我使用过 C、Perl、Lisp、Delphi、Visual Basic 6、C#、Java 和其他各种我想不起来的语言。

您的问题的答案很简单:您必须知道自己在做什么,而不是 C#/Java。不仅如此,还引发了 Jeff Atwood 关于“Java Schools”的咆哮。

从某种意义上说,您的大多数问题都是荒谬的。您提出的“问题”只是硬件如何真正工作的事实。我想挑战你用 VHDL/Verilog 编写 CPU 和 RAM,看看东西是如何工作的,即使真的很简化。您将开始意识到 C#/Java 方式是对硬件的抽象封装。

一个更简单的挑战是从最初的开机就为嵌入式系统编写一个基本的操作系统。它还会向您展示您需要了解的内容。

(我也写过 C# 和 Java)

于 2009-10-07T16:14:01.033 回答
3

我们用 C 语言编写嵌入式系统。除了使用任何编程语言或环境通用的一些技术外,我们还采用:

  • 静态分析工具(例如PC-Lint)。
  • 符合MISRA-C(由静态分析工具强制执行)。
  • 根本没有动态内存分配。
于 2009-10-07T20:36:53.980 回答
2

安德鲁的回答很好,但我也会在列表中添加纪律。我发现在使用 C++ 进行足够的练习之后,你会很好地了解什么是安全的,什么是在乞求迅猛龙来吃你。 您倾向于开发一种在遵循安全实践时感觉舒适的编码风格,并且如果您尝试将智能指针转换回原始指针并将其传递给其他东西,您会感觉很糟糕。

我喜欢把它想象成商店里的电动工具。只要您学会正确使用它并确保始终遵守所有安全规则,它就足够安全了。当您认为可以放弃安全护目镜时,您会受伤。

于 2009-10-07T06:31:41.630 回答
1

我已经完成了 C++ 和 C#,并且我没有看到关于托管代码的所有炒作。

哦,对了,有一个用于内存的垃圾收集器,这很有帮助……除非你避免在 C++ 中使用普通的旧指针,当然,如果你只使用 smart_pointers,那么你就没有那么多问题了。

但是我想知道......你的垃圾收集器是否保护你免受:

  • 保持数据库连接打开?
  • 锁定文件?
  • ...

资源管理比内存管理要多得多。C++ 的好处是您可以快速了解资源管理和 RAII 的含义,从而使其成为一种反射:

  • 如果我想要一个指针,我想要一个 auto_ptr、一个 shared_ptr 或一个 weak_ptr
  • 如果我想要一个数据库连接,我想要一个对象“连接”
  • 如果我打开一个文件,我想要一个对象“文件”
  • ...

至于缓冲区溢出,好吧,我们并不是到处都在使用 char* 和 size_t 。我们确实有一些叫做'string'、'iostream'的东西,当然还有已经提到的vector::at 方法,它可以让我们摆脱这些限制。

经过测试的库(stl、boost)很好,使用它们并解决更多功能问题。

于 2009-10-07T06:58:29.803 回答
1

除了这里给出的许多好技巧之外,我最重要的工具是 DRY——不要重复自己。我不会在我的代码库中散布容易出错的代码(例如,使用 malloc() 和 free() 处理内存分配)。我的代码中只有一个位置,其中调用了 malloc 和 free。它位于包装函数 MemoryAlloc 和 MemoryFree 中。

有所有的参数检查和初始错误处理,通常作为对 malloc 调用的重复样板代码给出。此外,它支持任何只需要修改一个位置的事情,从简单的调试检查开始,例如计算对 malloc 和 free 的成功调用,并在程序终止时验证两个数字是否相等,直至各种扩展的安全检查。

有时,当我在这里读到诸如“我总是必须确保 strncpy 终止字符串,是否有替代方案”之类的问题时。

strncpy(dst, src, n);
dst[n-1] = '\0';

经过几天的讨论,我一直想知道将重复功能提取到函数中的艺术是否是一种失传的高级编程艺术,不再在编程讲座中教授。

char *my_strncpy (dst, src, n)
{
    assert((dst != NULL) && (src != NULL) && (n > 0));
    strncpy(dst, src, n);
    dst[n-1] = '\0';
    return dst;
}

解决了代码重复的主要问题——现在让我们想想 strncpy 是否真的是适合这项工作的工具。表现?提前优化!在它被证明是瓶颈之后,一个单一的位置开始。

于 2009-10-07T09:45:59.593 回答
0

C ++具有您提到的所有功能。

有内存管理。您可以使用智能指针进行非常精确的控制。或者有几个垃圾收集器可用,尽管它们不是标准的一部分(但在大多数情况下,智能指针绰绰有余)。

C++ 是一种强类型语言。就像 C# 一样。

我们正在使用缓冲区。您可以选择使用界面的边界检查版本。但是,如果您知道没有问题,那么您可以自由使用未经检查的界面版本。

将 at() 方法(选中)与 operator[] (未选中)进行比较。

是的,我们使用单元测试。就像您应该在 C# 中使用一样。

是的,我们是谨慎的编码人员。就像你应该在 C# 中一样。唯一的区别是两种语言的陷阱不同。

于 2009-10-07T05:58:36.833 回答