为什么说C++落伍了?
前一段时间,在C++版的几个帖子里参与了一点讨论。也许某些观点触痛了几个C++爱好者的神经,被指责为“不懂装懂”、“没有建设性”、“踢场子”。
我当然不是大师,也不是牛人,不过相比某些满嘴跑火车,夸夸其谈没有来路的“OO规则”、什么“黑盒OO”、“白盒OO”的“神人”(比大师还牛,一开口就能“自主创新”),自信还是要懂那么一点点。
既然有人要我“拿出证据”,我也必须对自己的言论负责,就再装一回懂,谈谈“为什么C++落伍了”。
我的意思当然是讨论了,不过某些人非要说我“踢场子”,就当是踢场子吧。
先说一点,我并没有贬低C++,C++有很多优点,诸如语言能力比较强,实现平台多,有很多免费的工具和库可用等等,我在另一个帖子里也说了“C++是3GL的集大成者”。我针对的只是“C++就是好,就是好来就是好”、“全面优于其他语言”、“综合能力优于其他语言”(这种观点还算可以讨论)。
C++的一些缺点,诸如学习曲线的问题,缺少标准的应用框架(GUI、线程库、GC)等等,已经说得太多了,连BS这样的“C++之父”都说了(当然他是有一些辩解的)。
我就象某人那样“只谈技术”、“只谈语言本身”。
1. C++对象模型问题
准确地讲,是C++的对象存储模型。
C++的对象存储,使用一种简单模型,也就是对象(对象是类的实例)直接代表一个存储区域,存储它的内部成员。这种结构当然有它的优点:占用存储空间比较少,对象的创建和销毁比较简单也比较快(针对基于栈的,基于堆的对象在C++中要显式使用一个指向对象的指针,更加复杂)。
// 这些特征让我们想起它产生的时代背景:计算机比较慢、内存比较少。
但是这种结构也有明显的问题:
a. 为了实现多态,需要引入指针或引用。
b. 子类兼容也需要引入指针或引用。
c. 默认是基于栈的,而对象占用的空间较多(相比简单类型),潜在的导致栈溢出的可能性更大一些。
d. 对象的赋值是拷贝,这经常导致代码的性能问题(要解决就要显式使用指针)。
e. 这种存储结构有被利用进行缓冲区溢出攻击的潜在可能。
前面几个都是常识了,讲解C++的书多少都会提及,只多说一下e问题。
由于C++的对象直接映射到一个实际存储空间,利用缓冲区溢出篡改对象的内部数据结构从而改变程序流程的可能性要比使用引用模型的语言大。
比如如下程序:
#include <iostream>
#include <string.h>
using namespace std;
class bot
{
private:
char password[8];
public:
bot(){strcpy(password, "abc");};
virtual ~bot(){};
bool checkpwd(const char *pwd){return (!strcmp(password, pwd));};
};
char s[8];
bot b;
int main()
{
for(;;)
{
cout << "Enter password: ";
cin >> s;
if (b.checkpwd(s))
{
cout << "Access permitted.\n\n";
break;
}
else
cout << "Access denied.\n\n";
}
return 0;
}
密码是abc,正常情况下不输入abc是不可能通过的。
但是我们输入123456789012123试试,还是不行。
我们再输入123,居然通过了。
原因就是我们输入123456789012123的时候造成了缓冲区溢出,把b的内部数据password覆盖了,改成了123,这样再次输入123的时候就通过了。
(注:
(1). bcc32 5.82(BCB 2006)和cl 13.10.3077(VC++ 7.1)编译通过,其他编译器未测试。
(2). 之所以需要12个字节才覆盖到b是数据对齐的原因。
(3). 这个例子只是用来说明C++的对象模型可能导致缓冲区溢出攻击的原因,凡是批评程序结构不当、风格有问题的免开尊口。
)
这个例子是基于静态数据区的。基于栈分配,仍然可能构成攻击条件,尽管某些编译器(比如VC++ 7.1),在编译时有意调整局部变量的顺序,把char[]之类的缓冲区排在高地址而避免潜在的风险。但是这种避免只是在一个函数内部,不同函数中的局部变量编译器就无法调整了,所以一个函数内部的缓冲区还是可能溢出到直接或者间接调用它的函数在栈上创建的对象(当然形成攻击要更困难一些,因为覆盖了函数的返回地址)。
在基于引用的对象模型中,声明一个对象只是创建一个引用(本质上是指针),对象的实际存储空间是在堆上动态创建的,形成这种缓冲区溢出攻击的条件要困难得多。而在栈上的这种缓冲区溢出攻击,对基于引用的对象模型彻底无效,因为你再溢出也只能在栈段内部,无法篡改堆上的对象存储空间。
客观地说,C++的对象模型是80年代的产物(1989年的Turbo Pascal 5.5就使用与C++类似的对象模型,Delphi中仍然兼容支持这种object类型,但是标明“不推荐使用”)。
它无法隐藏指针和引用,在语法上不完美,在结构上有严重问题(允许在OO结构中暴露指针,这已经破坏了OO的三大特征之一---封装),而且容易被利用进行缓冲区溢出攻击。
90年代以来的OOPL,毫无例外地都使用引用模型,没有使用类似C++的模型的(也许我孤陋寡闻了,但至少象C#、Delphi、Java这些使用比较广泛的语言都是如此)。
这只能证明C++的对象模型过时了,总不可能C++的设计者最高明,其他语言的设计者都很蠢吧(可能性有多大呢?应该比你买彩票中大奖的可能性要小)。
而一个基本对象模型都过时了的语言,称之为“落伍”不算夸张吧。:)
(待续,明天接着写,没时间就是后天)