如果您是使用C或C++编程的人,没有使用托管语言的内存管理、类型检查或缓冲区溢出保护,使用指针算术操作时,如何确保您的程序是安全的?您会使用许多单元测试,还是只是一个非常谨慎的编码者?您是否有其他方法?
如果您是使用C或C++编程的人,没有使用托管语言的内存管理、类型检查或缓冲区溢出保护,使用指针算术操作时,如何确保您的程序是安全的?您会使用许多单元测试,还是只是一个非常谨慎的编码者?您是否有其他方法?
我会采用以下的方法:
实际上,我有点夸张了。如果您正确地组织代码,控制资源并不太难。
有趣的是,我有一个大型应用程序,它使用DCOM并具有托管和非托管模块。在开发过程中,非托管模块通常很难调试,但由于进行了许多测试,因此在客户端执行得非常好。托管模块有时会遭受糟糕的代码,因为垃圾收集器非常灵活,程序员变得懒惰,不检查资源使用情况。
我使用很多的"asserts",并且同时构建了"debug"版本和"release"版本。由于所有的检查,我的debug版本运行速度比release版本慢得多。
我经常在Valgrind下运行,并且我的代码没有任何内存泄漏。零泄漏。保持程序无泄漏比修复所有泄漏的错误程序要容易得多。
此外,尽管我将编译器设置为额外警告,但我的代码仍可以编译通过,没有任何警告。有时这些警告是无意义的,但有时它们会直接指向一个错误,然后我就可以不用调试就把它修复了。
我正在编写纯C代码(这个项目不能使用C++),但我以非常一致的方式来做C编程。我有面向对象的类,有构造函数和析构函数;虽然我需要手动调用它们,但是这种一致性是有帮助的。如果我忘记调用析构函数,Valgrind就会让我头疼到我去解决它。
除了构造函数和析构函数,我还编写了一个自检函数,检查对象并决定它是否正常;例如,如果文件句柄为null但关联的文件数据没有清零,则表示存在某种错误(句柄可能被覆盖,或者文件没有被打开,但对象中的这些字段中有垃圾数据)。此外,我的大部分对象都有一个"signature"字段,必须设置为特定值(对于每个不同的对象都是特定的)。使用对象的函数通常会断言对象是正常的。
每当我使用malloc()
分配内存时,我的函数会用0xDC
值填充内存。未完全初始化的结构变得明显:计数太大,指针无效(0xDCDCDCDC
),当我在调试器中查看结构时,它很明显是未初始化的。这比在调用malloc()
时将内存清零要好得多。(当然,0xDC
填充仅在调试版本中存在;不需要在发布版本中浪费时间。)同样重要的是 - 您如何确保文件和套接字已关闭、锁已释放等。内存不是唯一的资源,在使用GC时,您会失去可靠/及时的销毁。
无论是GC还是非GC都不是自动优越的。每种方法都有其优点和代价,一个好的程序员应该能够处理两种方法。
我在回答这个问题时也提到了这一点。
我已经使用C++十年了。我还用过C、Perl、Lisp、Delphi、Visual Basic 6、C#、Java和其他许多语言,但我无法想起来。
回答你的问题很简单:你必须知道自己在做什么,比C#/Java更重要。这个更重要是Jeff Atwood关于“Java学校”的愤怒发泄的原因。
在某种程度上,你提出的大部分问题都是无意义的。你提出的“问题”只是硬件运作的事实。我想挑战你用VHDL / Verilog编写CPU和RAM并看看它们如何工作,即使是真正简化的版本。你会开始欣赏C#/Java方式是对硬件的抽象。
一个更容易的挑战是为嵌入式系统编写一个基本的操作系统,从初始上电开始;这也将向你展示需要了解的内容。
(我也写过C#和Java)
我喜欢把它看作是车间里的电动工具。一旦您学会了正确使用它并始终遵守所有安全规则,它就足够安全。当您认为可以放弃安全眼镜时,您会受伤。
除了这里提供的许多好建议之外,我最重要的工具是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是否真的是适合该工作的正确工具。性能?过早优化!一旦它被证明是瓶颈,就可以从一个单一位置开始。
我已经学过C++和C#,但我不明白为什么管理代码如此受欢迎。
哦对了,有一个垃圾回收器来管理内存,这很有帮助...除非你在C++中使用普通指针,如果你只使用智能指针,那么问题就不会太多。
但是我想知道...你的垃圾回收器是否可以保护你免受以下问题的困扰:
资源管理比内存管理复杂得多。好处是,C++让你快速学习资源管理和RAII的含义,以至于它成为一种反应:
至于缓冲区溢出,我们并不是到处都在使用char*和size_t。我们确实有一些东西叫做“string”、“iostream”,当然还有已经提到的vector::at方法,这些都使我们摆脱了这些限制。
已经测试过的库(stl、boost)很好,使用它们并着手解决更多功能性问题。
C++拥有你提到的所有功能。
它有内存管理。你可以使用智能指针进行非常精确的控制。或者有一些垃圾回收器可用,尽管它们不是标准的一部分(但在大多数情况下,智能指针已经足够)。
C++是一种强类型语言。就像C#一样。
我们正在使用缓冲区。您可以选择使用接口的边界检查版本。但是,如果您知道没有问题,则可以自由使用接口的未检查版本。
将方法at()(已检查)与运算符[](未检查)进行比较。
是的,我们使用单元测试。就像您应该在C#中使用一样。
是的,我们是谨慎的编码者。就像您在C#中应该是一样的。唯一的区别是这两种语言的陷阱是不同的。
std :: string
吧。如果您仍在使用C而不是C ++,请停止。 - Tom Hawtin - tackline