恐怖的C++语言

恐怖的C++语言

我爱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微信公众账号可以在手机端搜索文章

(转载本站文章请注明作者和出处 酷 壳 – CoolShell ,请勿用于任何商业用途)

——=== 访问 酷壳404页面 寻找遗失儿童。 ===——
好烂啊有点差凑合看看还不错很精彩 (39 人打了分,平均分: 4.54 )
Loading...

恐怖的C++语言》的相关评论

  1. 如果按照 vector.operator(foo));是不是应该输出:
    大于操作符
    小于操作符

    我觉得vector foo;是不是应该从左到右计算?

  2. Kurt :

    如果按照 vector.operator(foo));是不是应该输出:
    大于操作符
    小于操作符

    我觉得vector foo;是不是应该从左到右计算?

    谢谢指正,这是我文章的错误,真实的表达式如下:(文中已修改)
    vector.operator>(UNIT4).operator<(foo);

  3. 我看糊涂了,vector foo; 应该是 vector.operator(foo);
    第一次是operator(vector, foo);

    应该就是这样。我写了log验证过了:

    cout: 10
    cout: 11
    operator vector foo
    cout: 14
    cout: 15
    cout: 16

  4. 难道纯汇编是最好的?
    而且,永远记住。“写代码是给别人看的。”。高效、稳定是必须的。但是可读性也是至关重要的。写这种代码的人很明显完全不合格。

  5. 我很不解为什么25行是对的。
    CPP不是应该把这行换成 Hack int了么?这应该是不合法的呀?

  6. 如果真有这样的人,给他什么语言他都能搞出类似的东西来。
    C++的可怕之处是继承多态模板设计模式等等高级特性的过度使用吧,感觉。

  7. 现在很多“高级语言”的编译器们背着你做的事情真可谓是数不胜数,如果以这个作为一个语言是否恐怖的标准的话,那么外面有一大堆更为恐怖的语言呢,灭哈哈哈哈。

  8. Seraph :

    现在很多“高级语言”的编译器们背着你做的事情真可谓是数不胜数,如果以这个作为一个语言是否恐怖的标准的话,那么外面有一大堆更为恐怖的语言呢,灭哈哈哈哈。

    没错。背着干事没问题,但一定要干好了,干对了。C++并不是这样的,背着你做了一些事,但出现的症状却让你很难理解。而且,这个编译器这样干,那个编译器那样干。还经常出现一些你意想不到的行为和问题。这才是恐怖的地方。

  9. 有个关于C语言的什么混乱大赛,比你这个要好看多了.要比贱的话,那只能是作贱无极限.
    每个东西都有阴暗的一面,只是你有没有去看和带特殊意味地去看罢了.欲加之罪,何患无辞.

  10. 写这样代码的人,是绝对带着极大的仇视心里,写的,如过你觉得C好,那你就用C吧,C++让你用,也没什么前途

  11. 当下任何类C语言(包括js,c#,php,java),如果引入泛型的概念和操作符重载的概念,都会碰到你说的这个问题。它们没有,只是因为它们不支持。

    C++是all in one的语言,它试图用一门语言尽量多的支持编程模型(oop,gp等等)和编程概念,过程中,因为键盘上好用的符号有限,难免有些小冲突,这都是语言设计者看到的和权衡过的。

    你的表述是事实,但是这个事实并不能支持你的观点。恕我直言,盲人摸象大概就是说这种情况。不过你比linus强,至少用了一个事实来试图说明你的观点,这个态度是好的。

    1. @杜宇 谢谢你的留言,我尊重你的观点。不管怎么样,我觉得这个世界有不同声音是好的。我比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本人,还有很多重量级的人物,有空你可以多看看。

  12. 你比如说linus说C++包括库的代码,可以很晦涩难懂,后来的人浪费时间,还可能搞错东西,是一堆狗屎。且不论C++代码有多晦涩,但是OOP是提倡面向接口编程,你只需要看接口,没人逼你去看完人的代码实现,错了他不认,你就找老板。

    还比如,linus说接口,这里应该是说二进制接口,就是跨so/dll的接口,还是用C的最保险,因为跨so/dll的heap管理c++没有做出规定,导致导出stl类等情况,不同编译器是不兼容的,但是java/C#的解决方法就是你别管理内存了,直接用GC甚至虚拟机,这也可以。但问题是没人逼你用两个编译器去编译不同的so/dll。在只能用两个编译器的情况下,也没人逼你非要在导出类里用heap。

    在美国,人发你把枪是用来保护私产,没人逼你去往白宫冲。但是美国人认为,不发枪就不对了,这限制了人的自由,你如果不适应,可以在中国活着,也没人逼你。

    你上面说的都是事实,我说这些不是为了对错,而是关于选择,选择是all in one的C++的灵魂。你可以在C++给的自由里,选择不那么做。

    希望初学者不要被吓到,这是我的目的。

  13. 所以系统级语言需要C,也需要一个稍微高级点的,一直以来没有,目前看go有前途

  14. 任何事都有两面性,这一面有多么的阳光,多么的强大;另一面就会有多么的黑暗,多么的脆弱。作为一个初学者也好,专家也好,只有了解到了事物的两面性,你才能用好它。如果初学者懂得怎么去管理内存,懂得什么是C++的对象模型,懂得什么是OOP的规则和设计模式,那么它就不是一个初学者了。这就像使用火源和驶车一样,在你不明白“火”和“速度”的恐怖和危险的时候,不懂得怎么才能驾驭或控制他们,而只是想着自由和爽快的话,你必然会“引火烧身”(C++这种语言绝对属于专业人士的语言)。

  15. @耗子

    说到异常管理就头痛~~~不管c和c++,因为这个是和系统相关很高的方面.我觉得很多时候,用什么语言,是依赖于很多情况的,平台熟悉度,效率要求,跨平台要求~~~没有语言是万能的,也没有语言是十全十美的,只能说是自己熟悉到什么程度~~~~

    c很纯粹,但是大的项目,代码量会很恐怖~~~c++比较复杂,但是如果你了解他,他会帮你做很多事情~~~

    java了解不多,不胡乱评论,go也是~~~

  16. “Linux曾经(2007年9月)在新闻组gmane.comp.version-control.git里和一个微软的工程师(Dmitry Kakurin)争执过用C还是用C++”
    这个……是想说Linus吧……先把人名搞清除再讨论语言好坏吧。

  17. 难道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强奸了。

  18. 再说说:
    string s = .. + .. + .. + .. ;

    这是好像是那个长篇评论中举出的一个例子。
    “你不知道这个表达式背后会产生多少临时对象”

    一方面,这能体现参与评论的家伙不懂C++,甚至linus本人也不懂。
    40多岁的人了,他已经会C,让他诚心去学C++确实有点困难。

    另一方面,也体现了C程序员”对库的不信任”,任何事情都喜欢掌控在自己手里。
    导致轮子被一次又一次的重复制造。
    但是,真的每个C程序员都有能力造出比现存更好的轮子么? 显然不是。
    linux kernel那是因为看的人多,不容易藏污纳垢。
    找点其他C代码看看? 同样很恶心。

  19. 而且这个例子真的没什么说服力。 操作符滥用是有的。

    标识符滥用?
    vector foo;
    会有多少C++程序员(不是C++初学者)会给自己的变量命名为 vector? UINT4???

    既然这么做了 —— 责任自负。

    btw:

    int main()
    {
    using ::Hack;
    using ::UINT4;
    using ::vector;

    // 会崩掉msvc,gcc我没试过。

  20. C++方面,我最怕的,是自己老是不自觉地想模拟原生类型,而浪费时间写一堆没有太大用途的代码。
    所以我不断告诉自己把C++当成有一堆语法糖的C来用。
    Linus对C++的论战,对我启发还是很大的。
    用C++,容易去追求完美。但完美却是不现实的,追求起来太痛苦。

  21. func :
    C++方面,我最怕的,是自己老是不自觉地想模拟原生类型,而浪费时间写一堆没有太大用途的代码。
    所以我不断告诉自己把C++当成有一堆语法糖的C来用。
    Linus对C++的论战,对我启发还是很大的。
    用C++,容易去追求完美。但完美却是不现实的,追求起来太痛苦。

    使用C++…… 要学会克制……
    不懂偏用、用了不懂、懂了就用、懂了不用这几个层次中,要努力进化最后一个层次。
    仅在必须使用,不用就不能清晰表达思路的情况下,才考虑动用C++的特性。

    我现在也是在写Clean C代码。
    有些东西,C要表达出来太困难,而使用C++就会清晰很多。

  22. 接上:在那些C表达太困难的情况下,对C++特性理解的”储备”就很重要了。
    否则根本就想不到这里有更加优美的表达方式,只能继续写丑陋的代码。

    当然,C表达能力不足,也不一定非要通过C++弥补。
    用其他语言弥补也是可以的。

  23. Sorry,只是不小心回车上,上面的东西回复没写完就提交了……也没办法修改啊?……

    我是想说“自从意识到 ownwaterloo 这个 id 之后,发现到哪都能看到他,哈哈哈哈,强~~~”

  24. checked exception的滥用,会造成”即使你没有处理问题的上下文,你也得catch 然后什么都不干仅仅重新抛出”。
    ===================================================================
    完全同意。。。

  25. 想知道,类似的用法多吗? 我在看Stroustrup写的《C++程序设计语言》书时,感觉这种用法很普遍,好像资深C++程序员都深谙这类设计技巧似的? C++本科课程也常常对这类使用方法纠缠不休?

  26. ownwaterloo :难道C代码就没有这些”技巧”? 当C代码”需要””技巧”的时候,会弄得比C++更恶心。
    java的generic根本就是骗局,
    ……丝毫不觉得自己的思想被Gosling强奸了。

    不管是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++不提供的,就是不需要的就对了吗?

  27. 这个作者就最起码的编程素养都没有,保留名、作用域等都了解吗?就来胡评一切!

  28. ownwaterloo :
    想想java里面:
    short s = …;
    s = s + 1; // error
    s += 1; // ok
    搞笑

    这是java比较严谨导致的:s+1被强制转为int值,不允许直接向short转,这样就可以了:s = (short)(s + 1);

  29. 对C++足够熟悉的话,自然不会把vector用作变量名。恶搞C++的人其实是对C++很熟悉的。感觉一旦各种库强大起来了,相比之下C++的编译器就弱了。

  30. vector foo;
    你好,这段代码有很明显的问题,虽然包含了头文件,但实际上并没有using std::vector,因此vector还是一个未命名过的标识符,也就是说这段代码本身就存在错误,如果这样写 std::vector foo,就不可能通过编译,这段代码有点钻角尖

  31. 虽然 ownwaterloo 说话的方式有点冲,但他说的我很认同
    c++ 提供了各种各样的能力,就像一把瑞士军刀
    你熟悉哪个功能就去用哪个功能好了,、
    非要鼓捣自己不熟悉的那个功能,只能是后果自负
    这不是军刀的错,也不能得出结论说刀子或者剪刀就比瑞士军刀好。。。

发表评论

电子邮件地址不会被公开。 必填项已用*标注

*