恐怖的C++语言
Linus曾经(2007年9月)在新闻组gmane.comp.version-control.git里和一个微软的工程师(Dmitry Kakurin)争执过用C还是用C++,当时的那个微软的工程师主要是在做Git的Windows版,但他却发现Git的源码居然是C语言写的,而不是C++,于是他(Dmitry Kakurin)在Linux社区里发贴表示对Linux的不满,语言很直接:
Pure C as opposed to C++. No idea why. Please don’t talk about portability, it’s BS. (纯C写的,而不是C++,不知道为什么,请别告诉我是为了移植性,这完全是胡扯。)
Linux之父Linus Torvalds马上跟贴,在贴子中,Linus言辞很直接,直接表明C++是一个很恐怖的语言,他在贴子中说:
*YOU* are full of bullshit. C++ is a horrible language. It’s made more horrible by the fact that a lot of substandard programmers use it. (你才是完全在胡扯。C++是一门很恐怖的语言,而比它更恐怖的是很多不合格的程序员在使用着它)
Linus的这个观点我是比较同意的,我个人也在几年前的《STL String类的写时才拷贝》以及以后的一些文章中表达过C++的确并不是一个很成熟的语言,这种观点一直都围绕着我。这是因为它的学习成本实在是太高了,编译器和类背着你做了很多你不知道的事,而且,C++非常容易地出错和发生很多意想不到的问题。
当然,这篇文章并不是要继续声讨C++,也不是回顾以前的某个事件。我们这里只谈技术。昨天,我在网上看到一个邪恶的C++的示例,在这里给大家share一下,让大家看看C++这种编程语言的恐怖和邪恶的一面。下面的这个例子,比那个“#define private public”还更加邪恶。
请看下面这段代码,你能告诉我它会输出什么吗?(注意main函数中高亮的那一行)
#include <iostream> #include <vector> typedef int UINT4; using namespace std; class Hack { }; Hack& operator< (Hack &a , Hack &b) { std::cerr << "小于操作符\n"; return a; } Hack& operator> (Hack &a, Hack &b) { std::cerr << "大于操作符\n"; return a; } int main(int argc, char ** argv) { Hack vector; Hack UINT4; Hack foo; vector<UINT4> foo; return(0); }
是的,上面这段代码如果只看main函数中的那句“vector<UINT4> foo”,你会觉得很眼熟,然而,事情并非那么简单,我们可以看到vector, UINT4和foo都是Hack类的实例,这就是邪恶的开始,那两个尖括号< >则成了两个运算符,大于和小于,这两个运算符却又被重载了。其实,真正的语句是:
vector.operator<(UNIT4).operator>(foo);
所以,所有的一切都符合我们的C++的规范和语法,自然程序也能被顺利编译通过(至少,在我的G++上是没有问题的)。而整个程序的运行结果自然是:
$ ./horror 小于操作符 大于操作符
是的,如果你通晓C++的一切的一切,你自然不会对这段程序感到惊奇。这样的事情在C/C++的世界中并不少见,要搞乱C/C++的代码并不是一件难事,花样多得数不胜数,只要看看《6个变态的C语言Hello World程序》你就知道了,而且,还有一个简单的教程《如何加密/混乱C源代码》告诉你一些简单的做法。
那么,如果你有一天在读程序中看到“vector<UINT4> foo”,你会觉得那只是一个幻觉吗?
(全文完)
(转载本站文章请注明作者和出处 酷 壳 – CoolShell ,请勿用于任何商业用途)
《恐怖的C++语言》的相关评论
我喜欢这配图~~
如果按照 vector.operator(foo));是不是应该输出:
大于操作符
小于操作符
我觉得vector foo;是不是应该从左到右计算?
谢谢指正,这是我文章的错误,真实的表达式如下:(文中已修改)
vector.operator>(UNIT4).operator<(foo);
我看糊涂了,vector foo; 应该是 vector.operator(foo);
第一次是operator(vector, foo);
应该就是这样。我写了log验证过了:
cout: 10
cout: 11
operator vector foo
cout: 14
cout: 15
cout: 16
昏迷,小于号和大于号全没了。。。昏迷~~~~~~彻底无语~~~
谁这么用C++是故意和自己过不去
不一定自己的代码自己维护啊,有些时候这是和公司、团队或经理过不去啊。
我一看这个就昏迷了。
难道纯汇编是最好的?
而且,永远记住。“写代码是给别人看的。”。高效、稳定是必须的。但是可读性也是至关重要的。写这种代码的人很明显完全不合格。
我很不解为什么25行是对的。
CPP不是应该把这行换成 Hack int了么?这应该是不合法的呀?
如果真有这样的人,给他什么语言他都能搞出类似的东西来。
C++的可怕之处是继承多态模板设计模式等等高级特性的过度使用吧,感觉。
新鲜~:-)
现在很多“高级语言”的编译器们背着你做的事情真可谓是数不胜数,如果以这个作为一个语言是否恐怖的标准的话,那么外面有一大堆更为恐怖的语言呢,灭哈哈哈哈。
没错。背着干事没问题,但一定要干好了,干对了。C++并不是这样的,背着你做了一些事,但出现的症状却让你很难理解。而且,这个编译器这样干,那个编译器那样干。还经常出现一些你意想不到的行为和问题。这才是恐怖的地方。
有个关于C语言的什么混乱大赛,比你这个要好看多了.要比贱的话,那只能是作贱无极限.
每个东西都有阴暗的一面,只是你有没有去看和带特殊意味地去看罢了.欲加之罪,何患无辞.
呵呵,跟这个很像
http://stl.winterxy.com/html/item_06.html
写这样代码的人,是绝对带着极大的仇视心里,写的,如过你觉得C好,那你就用C吧,C++让你用,也没什么前途
当下任何类C语言(包括js,c#,php,java),如果引入泛型的概念和操作符重载的概念,都会碰到你说的这个问题。它们没有,只是因为它们不支持。
C++是all in one的语言,它试图用一门语言尽量多的支持编程模型(oop,gp等等)和编程概念,过程中,因为键盘上好用的符号有限,难免有些小冲突,这都是语言设计者看到的和权衡过的。
你的表述是事实,但是这个事实并不能支持你的观点。恕我直言,盲人摸象大概就是说这种情况。不过你比linus强,至少用了一个事实来试图说明你的观点,这个态度是好的。
@杜宇 谢谢你的留言,我尊重你的观点。不管怎么样,我觉得这个世界有不同声音是好的。我比Linus差太多了,人家是领袖级的人物。而我,只不过一个无名小卒罢了。只不过,十几年来,在吃过很多C++的苦头后,对C++了解得越深,我就越觉得C++并不是一个成熟的编程语言。比如:
> 以前,我一直不理解为什么Java没有模板,因为Java有超级类嘛,这就不需要模板(但现在也有了,呵呵)
> 以前,我也不更理解为什么Java没有重载操作符,原来,重载操作,看似强大,其实,只会产生更多的混乱,让你对所有的语句都带着怀疑的眼光。
> 以前,我也不理解为什么Java会有接口,而没有多重继承。原来接口就是C++的抽象类,但接口更接近于设计模式。让我们扪心自问,多重继承的目的是什么?只是为了酷?不是,最主要的是为了让一个实例有多态的接口。而C++的多重继承的滥用让程序产生了混乱。
> 以前,我也不理解为什么Java没有指针。后来才知道引用的安全和指针的无处不在的危险。而C++中,程序员们需要在返回指针、引用、拷贝通苦的选择
> 以前,我不明白为什么Java的异常必需要被try catch,不然无法编译通过。后来,我知道了,C++的异常管理是相当痛苦的,那个函数上的异常定义也是毫无意义的。
> 还有那把所有的泛型编程和设计模式使用到极致的STL库,但却没有线程安全,而且非常容易出现大量的内存碎片,哎……
> 还有类型转换,有些时候是相当的恐怖啊。
这样的例子举不胜举啊,Exceptional C++书中在讲到异常安全代码的章节中有这么一段话 ——
C++的世界中可能是编程语言中混乱最多的地方了,正如Linus所说,最恐怖的是有一群不合格的C++程序员在使用这门语言。Linus至少从92年就开始写Linux了,15年以上的系统编程经验,网罗了世界顶尖黑客,他这么说,一定是有他的道理的。原贴跟贴无数,不单单只有Linus本人,还有很多重量级的人物,有空你可以多看看。
你比如说linus说C++包括库的代码,可以很晦涩难懂,后来的人浪费时间,还可能搞错东西,是一堆狗屎。且不论C++代码有多晦涩,但是OOP是提倡面向接口编程,你只需要看接口,没人逼你去看完人的代码实现,错了他不认,你就找老板。
还比如,linus说接口,这里应该是说二进制接口,就是跨so/dll的接口,还是用C的最保险,因为跨so/dll的heap管理c++没有做出规定,导致导出stl类等情况,不同编译器是不兼容的,但是java/C#的解决方法就是你别管理内存了,直接用GC甚至虚拟机,这也可以。但问题是没人逼你用两个编译器去编译不同的so/dll。在只能用两个编译器的情况下,也没人逼你非要在导出类里用heap。
在美国,人发你把枪是用来保护私产,没人逼你去往白宫冲。但是美国人认为,不发枪就不对了,这限制了人的自由,你如果不适应,可以在中国活着,也没人逼你。
你上面说的都是事实,我说这些不是为了对错,而是关于选择,选择是all in one的C++的灵魂。你可以在C++给的自由里,选择不那么做。
希望初学者不要被吓到,这是我的目的。
所以系统级语言需要C,也需要一个稍微高级点的,一直以来没有,目前看go有前途
任何事都有两面性,这一面有多么的阳光,多么的强大;另一面就会有多么的黑暗,多么的脆弱。作为一个初学者也好,专家也好,只有了解到了事物的两面性,你才能用好它。如果初学者懂得怎么去管理内存,懂得什么是C++的对象模型,懂得什么是OOP的规则和设计模式,那么它就不是一个初学者了。这就像使用火源和驶车一样,在你不明白“火”和“速度”的恐怖和危险的时候,不懂得怎么才能驾驭或控制他们,而只是想着自由和爽快的话,你必然会“引火烧身”(C++这种语言绝对属于专业人士的语言)。
@feilng
是的,我也有同感,这可能会是一个继C和Java后另一个伟大的语言。
@耗子
说到异常管理就头痛~~~不管c和c++,因为这个是和系统相关很高的方面.我觉得很多时候,用什么语言,是依赖于很多情况的,平台熟悉度,效率要求,跨平台要求~~~没有语言是万能的,也没有语言是十全十美的,只能说是自己熟悉到什么程度~~~~
c很纯粹,但是大的项目,代码量会很恐怖~~~c++比较复杂,但是如果你了解他,他会帮你做很多事情~~~
java了解不多,不胡乱评论,go也是~~~
“Linux曾经(2007年9月)在新闻组gmane.comp.version-control.git里和一个微软的工程师(Dmitry Kakurin)争执过用C还是用C++”
这个……是想说Linus吧……先把人名搞清除再讨论语言好坏吧。
@耗子 同意,语言没有好坏之分,只有适合与否。只有充分理解了某个语言的特性甚至底细,才能把它用在合适的项目上。
呵呵,怎么又想到这个?我的blog上有相对比较完整的讨论译文,大家可以参考:
http://blog.csdn.net/turingbook/archive/2007/09/07/1775488.aspx
没想到已经过去两年多了……
那张配图很漂亮。
RSS输出居然不是全文的。
楼主,能不能给份SyntaxHighlighter,谢谢。
插件主页:http://www.viper007bond.com/wordpress-plugins/syntaxhighlighter/
难道C代码就没有这些”技巧”? 当C代码”需要””技巧”的时候,会弄得比C++更恶心。
java的generic根本就是骗局,和C++的template完全不是一个层次的东西。
没有操作符重载,就会造成如下代码:
BigInteger i0,i1,i2,i3, …;
i0.set(i2);
i0.mul(i3);
i0.plus(i1);
而不是 BigInteger i0 = i1 + i2*i3;
StringBuffer s;
s.append( … );
s.append( … );
…
而不是 string s = .. + .. + .. + .. ;
别说这里有临时对象。这里可以实现得比上面那段繁琐的代码更高效,就看你会与不会了。
在C++0x中,这更是小事一桩。
是否需要多重继承,应该由程序员考虑而不是Gosling那头猪。
多重继承真的无用吗? MouseAdapter就遭遇了没有多重继承的尴尬。 活该。
哦,java没有指针,java再也不需要管理指针!
当你想使用一个vector的时候,你必须使用vector是什么感觉?
java的gc能处理好非内存资源吗? try … finally嵌套真的很好看? 还是单入口单出口真的很好看?
直到java7,才引入了和C#的using相同的设施。
C++给你选择,无论是内存,还是非内存资源。
Java的chekced exception也是被滥用了的。可以去看看hibernate的以前版本和现在版本。
然后问问他们的作者,为什么要放弃使用checked exception?
checked exception的滥用,会造成”即使你没有处理问题的上下文,你也得catch 然后什么都不干仅仅重新抛出”。
stl线程安全? 什么东西都必须线程安全吗? 每个对象你都要放到多个线程中去共享一下吗?
java中先出现的一些容器和StringBuffer是线程安全的(名字可能我记错了),但后来又出现了非线程安全的容器与StringBuilder。
想想这是为什么吧。
内存碎片这是个问题。不是stl有内存碎片,而是没有gc的语言会有内存碎片。
类型转换…… 当我学会C之后,再也不想写出以前pascal中的这种代码(伪代码):
Char c = ‘0’; // 下面的代码为了让c变为’1′
c = ToChar( ToInt(c) + 1 );
在C中,++c即可(ASCII环境)。
想想java里面:
short s = …;
s = s + 1; // error
s += 1; // ok
搞笑
总之,C++提供给你强大的表达能力。是否能正确使用,是程序员的素质问题。
C++的这些特性,是设计给需要且能够理解的人使用的。
如果你不需要、请不要认为所有人都不需要。
如果你不理解、请不要认为所有人都不理解。
“C++代码需要优秀程序员才能维护,所以它不适合软件工业。”
“软件工业需要java这种短期培训就可以上岗的语言。”
java、是一个偏执狂搞出的东西,将它所认为不合理的特性抹掉、将它所认为合理的特性加入。
checked exception要说,也是一个有用的特性,增加语言的表达能力。
但只要是特性,就会被滥用。
java程序员已经被Gosling给蛊惑了,让自己的思想被Gosling同化了,认为java提供的,就是好的;java不提供的,就是不需要的;丝毫不觉得自己的思想被Gosling强奸了。
再说说:
string s = .. + .. + .. + .. ;
这是好像是那个长篇评论中举出的一个例子。
“你不知道这个表达式背后会产生多少临时对象”
一方面,这能体现参与评论的家伙不懂C++,甚至linus本人也不懂。
40多岁的人了,他已经会C,让他诚心去学C++确实有点困难。
另一方面,也体现了C程序员”对库的不信任”,任何事情都喜欢掌控在自己手里。
导致轮子被一次又一次的重复制造。
但是,真的每个C程序员都有能力造出比现存更好的轮子么? 显然不是。
linux kernel那是因为看的人多,不容易藏污纳垢。
找点其他C代码看看? 同样很恶心。
而且这个例子真的没什么说服力。 操作符滥用是有的。
标识符滥用?
vector foo;
会有多少C++程序员(不是C++初学者)会给自己的变量命名为 vector? UINT4???
既然这么做了 —— 责任自负。
btw:
int main()
{
using ::Hack;
using ::UINT4;
using ::vector;
// 会崩掉msvc,gcc我没试过。
C++方面,我最怕的,是自己老是不自觉地想模拟原生类型,而浪费时间写一堆没有太大用途的代码。
所以我不断告诉自己把C++当成有一堆语法糖的C来用。
Linus对C++的论战,对我启发还是很大的。
用C++,容易去追求完美。但完美却是不现实的,追求起来太痛苦。
使用C++…… 要学会克制……
不懂偏用、用了不懂、懂了就用、懂了不用这几个层次中,要努力进化最后一个层次。
仅在必须使用,不用就不能清晰表达思路的情况下,才考虑动用C++的特性。
我现在也是在写Clean C代码。
有些东西,C要表达出来太困难,而使用C++就会清晰很多。
接上:在那些C表达太困难的情况下,对C++特性理解的”储备”就很重要了。
否则根本就想不到这里有更加优美的表达方式,只能继续写丑陋的代码。
当然,C表达能力不足,也不一定非要通过C++弥补。
用其他语言弥补也是可以的。
自从
Sorry,只是不小心回车上,上面的东西回复没写完就提交了……也没办法修改啊?……
我是想说“自从意识到 ownwaterloo 这个 id 之后,发现到哪都能看到他,哈哈哈哈,强~~~”
checked exception的滥用,会造成”即使你没有处理问题的上下文,你也得catch 然后什么都不干仅仅重新抛出”。
===================================================================
完全同意。。。
@耗子
确实没必要这样难为自己,c++语言可以写的通俗易懂。不应该做这样的文字游戏之类的显呗行为。
想知道,类似的用法多吗? 我在看Stroustrup写的《C++程序设计语言》书时,感觉这种用法很普遍,好像资深C++程序员都深谙这类设计技巧似的? C++本科课程也常常对这类使用方法纠缠不休?
不管是C++还是Java好的程序员写出的代码都是好懂的;同时,通常C++写出混乱的代码比Java混乱的多。【1】C++有一些奇怪的特征,比如if(a=7), if(!a)这样的语句很有用吗,好懂吗?没有这些特性不是更好吗?【2】Java的Generic是和C++的Template不一样,只是为啥非得要和C++一样?【3】short s =…; s = s + 1; 编译出错是有点诡异,看起来不完美,C++呢?整数长类型到短类型赋值结果不可确定,不同编译器有不同实现。类似的还有>>,Java 把它分为>>和>>>,不是很合理吗,Python禁止负数>>也很合理呀,而C/C++容易引起混乱。【4】C++的函数如果声明异常表明抛出可能抛出这个异常,不声明反而是可能抛出任何异常,我想B. Stroustrup当时设计C++时不是为了兼容C也不会这么干吧。Java的异常机制是很好用的,C#、Python、Ruby、PHP等编程语言也都是这样,Checked exception是有些人滥用,任何语言特征都有人乱用,我认识一个程序员他说:“我的代码写的比XXX要好,因为我用了指针”,像这样的程序员谁有什么办法?【5】指针、运算符重写、多重继承、GOTO语句是有时有用,有时也很危险,我读过一个二十余行的算法,有三个GOTO语句,我看了两个多小时没看懂,后来找了一个结构化的没有GOTO的实现版本,不到五分钟就读懂了。我不是说C++有这些就不好,只是你不能说没就不好。【6】认为Java提供的,就是好的;Java不提供的,就是不需要的,这种想法是不对。你认为C++提供的,就是好的;C++不提供的,就是不需要的就对了吗?
这个作者就最起码的编程素养都没有,保留名、作用域等都了解吗?就来胡评一切!
这是java比较严谨导致的:s+1被强制转为int值,不允许直接向short转,这样就可以了:s = (short)(s + 1);
@Kurt
sun 的jdk是用什么编写的
对C++足够熟悉的话,自然不会把vector用作变量名。恶搞C++的人其实是对C++很熟悉的。感觉一旦各种库强大起来了,相比之下C++的编译器就弱了。
vector foo;
你好,这段代码有很明显的问题,虽然包含了头文件,但实际上并没有using std::vector,因此vector还是一个未命名过的标识符,也就是说这段代码本身就存在错误,如果这样写 std::vector foo,就不可能通过编译,这段代码有点钻角尖
虽然 ownwaterloo 说话的方式有点冲,但他说的我很认同
c++ 提供了各种各样的能力,就像一把瑞士军刀
你熟悉哪个功能就去用哪个功能好了,、
非要鼓捣自己不熟悉的那个功能,只能是后果自负
这不是军刀的错,也不能得出结论说刀子或者剪刀就比瑞士军刀好。。。
@alexandercer
算符重载啊
千万别在菜鸟阶段就试图玩弄那些技巧,死得很惨的