2016年5月10日 星期二

[嘴炮] 回應 "C++的反思"



不知道為什麼, 看到《C++的反思》就是覺得很不爽, 我只是普通人, 這是正常情緒的能量釋放。

真不知道 c++ 哪裡得罪這些人, 貶低別的程式語言並不會使你看起來很高明

這篇文章很長 (真是辛苦, 怎麼不把這時間拿去用 c++ 寫程式呢?), 我沒興趣回應所有部份, 太花時間了, 我寧願用這時間寫點 c++ 程式、或是非嘴炮的技術文章, 所以只回應以下這段:

系统层的挫败

C++遭受挫败是进军嵌入式和操作系统这样靠近硬件层的东西。大家觉得宇宙级别的编程语言,自然能够胜任一切任务,很快发现几个问题:

  1. 无法分配内存:原来用 C可以完全不依赖内存分配,代码写几千行一个 malloc没有都行。嵌入式下处理器加电后,跳到特定地址(比如起始地址0),第一条指令一般用汇编来写,固定在0地址,就是简单初始化一下栈,然后跳转到 C语言的 start函数去,试想此时内存分配机制都还没有建立,你定义了两个类,怎么构造呀?资源有限的微处理器上大部分时候就是使用一块静态内存进行操作。C++写起来写爽了,各种隐式构造一出现,就傻了。

    是你傻, 不是 c++ 傻。我用 c++ 寫的 bare-metal simple scheme 不含 simple libc++ 大概是 3000 行, 一樣沒有用到 malloc/new 這些 function, 全都是用 array, 這和 c 是完全一樣的, 而且我一樣有定義不少 class/object 來使用, 不要使用 global object (local object) 用起來就和在 os 底下的 c++ 一樣, 該有的建構/解構函式一樣都可以使用, 若要使用 global object 也不是什麼問題, 自己定義一個 init(), destory() 手動呼叫即可, 這和 c 一樣, 沒什麼好抱怨的吧!

    簡單初始化 stack 就可以用 c, 你以為只有這樣就可以了嗎? 在最常見的 x86 上, 完全不是這樣, 在最常見的嵌入式 arm 上, 也不見得是這樣, 你知道要符合什麼條件才可以這麼做嗎? 在我遇到的平台中, 目前只有 rpi2 可以符合這條件。

  2. 标准库依赖:在语言层面,C语言的所有特性都可以不用依赖任何库就运行,这为编写系统层和跨平台跨语言代码带来了很方便的特性。而C++就不行,我要构造呀,我要异常呀,你为啥不能给我强大的运行时呢?什么你还想用 stl?不看看那套库有多臃肿呀(内存占用,代码尺寸)。

    只打造和 c 一樣的 c++ runtime, 的確是得不到 exception handle 這個能力, 不過 eh 很複雜, 就算你有辦法打造這個功能, 付出的代價也會讓你不想用他, global/static object ctor/dtor 也不能用, 但是 local object ctor/dtor 完全沒問題, 除了這個能用還有什麼呢? 多得很 ...

    • namespace
    • function overloaded
    • function default argument
    • template function
    • reference
    • inline function
    • 更嚴格的型別檢查
    • c++11 的部份特性: ex: lamdbda function

    stl 做部份的修改也是可以用在 bare-metal 環境的, 當然門檻高了點, 但是不是很臃腫? 如果和你自己手工打造的簡單的功能來比, 當然臃腫 (你寫的出能和 stl 一拼的臃腫程式嗎?), 但這一樣和 c 可以自己手寫, 用 c++ 哪來這些困擾呢? 不用就不臃腫了。

  3. 异常处理问题:底层开发需要严格的处理所有错误返回,这一行调用,下一行就判断错误。而异常是一种松散的错误处理方式,应用层这么写没问题,系统层这么写就很狼狈了。每行调用都try一下和 C的调用后if判断结果有什么区别?C++的构造函数是没有返回值的,如果构造内部出错,就必须逼迫你catch构造函数的异常,即便你catch住了,构造异常的时候当然会自动触发相关内部对象的析构,但是有很多并没有析构的资源(比如系统资源,比如C接口的资源,他们都没有一个析构),整个过程是很难控制的,此时这个实例是一个半初始化实例,你该怎么处理它呢?于是有人把初始化代码移除构造函数,构造时只初始化一下变量,新增加一个带返回的init函数,这样的代码写的比C冗余很多。何况硬件中断发生时,在你不知道的情况下,同事调到一些第三方的库,你最外层没有把新的exception给 catch住,这个exception该往哪里抛呀?内存不够的时候你想抛出一个 OutOfMemoryException,可是内存已经不够了,此时完全无能力构造这个异常又该怎么办呢?

    c++ exception handling 會造成額外負擔, 而且要自己打造 c++ exception handle 是很難的技術, 我很懷疑你在 bare-metal 環境可以使用 eh, 你說 "调到一些第三方的库,你最外层没有把新的exception给 catch住,这个exception该往哪里抛呀?" 在 bare-metal 環境根本就不會有 "使用 exception handle 的 library" 可用, 你把在 os 下和 bare-metal 環境搞在一起了。

    我的 bare-metal 對應到你的系統層。

    使用 init, 難道你用 c 就不需要用 init 來初始化嗎? 為什麼 c++ 用這樣的方式就比 c 冗余很多? c 要把那個 struct 傳進去其實比 c++ 還冗余。ex: c 用法: aobj->init(&aobj, 1, 2); VS c++ 用法: aobj->init(1,2); 到底誰比較冗余? 你一定看過類似要模擬 c++ class 的 c 用法吧! 你也一定覺得醜的不得了吧!

    再說在 bare-metal 可以使用 setjmp/longjmp 來模擬 exception handle, 這是 c 的東西, 你該不會說他的壞話了吧?

  4. 处理器兼容:C++的类依赖基地址+偏移地址的寻址方式,很多非 Intel系列的微处理器上只有简单的给定地址寻址,不支持这样一条语句实现BASE+OFFSET的寻址,很多C++代码编译出来需要更多的指令来运算地址,导致性能下降很多,得不偿失。

    看不懂, 不回應。

    程式員不應該打馬虎眼, 請反組譯一段 c++ 程式來說明你的論點。

  5. 隐式操作问题:C的特点是简单直接,每行语句你都能清楚的知道会被翻译成什么样子,系统会严格按照你的代码去执行。而用C++,比如 str1 = str2 + "Hello" + str3; 这样的语句,没几个人真的说得清楚究竟有多少次构造和拷贝,这样的写法编写底层代码是很不负责任的,底层需要更为精细和严格的控制,用C语言控制力更强

    這和 c++ 有什麼關係,「搞不清楚究竟有多少次构造和拷贝」那就去搞懂他阿! 有這麼難嗎? 需要我推薦參考書籍嗎?

    你怎麼不提在 c 需要用 strcpy + malloc + free 才能做到這件事情好不好用? 還得記得那個 \0 不要忘記算了。光是 malloc/free char * 就搞死你。

    表演一下你用 c 要完成這個動作要花的功夫如何? 看看是 c 優雅還是 c++ 優雅。

    很難用的 strncpy》 紀錄了 c style string 的古怪。

不過我得承認《c++ 在系统层的挫败》這點, 我不知道為什麼在 bare-metal 環境上 c++ 並不像 c 那麼常被使用, 為了證明用 c++ 來開發 bare-metal 程式沒什麼問題, 我才使用 c++ 開發 bare-metal simple scheme, 並確認沒什麼問題。

而 simple os 一開始沒用 c++ 也是我對於 c++ 沒信心以及沒那麼好的掌握度, 所以用了 c, 我得承認用 c++ 會有一點和 c 不一樣的感覺, 那感覺很難說明, 也許就是那微妙的差異, 讓大多數 bare-metal 開發者還是習慣用 c 吧!

沒有留言:

張貼留言

使用 google 的 reCAPTCHA 驗證碼, 總算可以輕鬆留言了。

我實在受不了 spam 了, 又不想讓大家的眼睛花掉, 只好放棄匿名留言。這是沒辦法中的辦法了。留言的朋友需要有 google 帳號。