學 C 語言時,有沒有遇到過讓你「痛不欲生」、「揪心」或「不得要領」的術語?現在又是怎麼理解它的?


輪帶逛有感: @vczh大大的回答說:

一開始的時候看到longjmp我就覺得這是個什麼雞吧東西,後來學了彙編,就明白了。

我對setjmp() / longjmp()的感想是:這玩兒用Clang編譯的話可能會運行出錯 &>_&<

==================================

回到正題。肯定有很多回答會說指針、函數、函數指針之類。我就先說點別的。

隨便舉個栗子:C裡面的cast是有內建的轉換語義的。例如說:

int i = 1;
float f = (float) i;

此時f的值會是1.0F,等於來源 i 的整數值,而不是把 i 給 bitcast 成一個 float。

但是有時候我們就是要bitcast怎麼辦?C語言說:自己想辦法。

於是就有這樣的標準解法:

union {
int i;
float f;
} s;
s.i = 1;
float f = s.f;

這樣就能得到內部編碼為0x00000001的float,也就是數值為1.4E-45的那個float值了。

普通cast與bitcast概念的區別,大概能讓很多初學者痛不欲生好一會兒。

不過很快就能掌握。然後又可以原地滿血復活了 &>_&<

掌握了bitcast的概念後,同學們對於各種類型的值的表現形式應該會有更深入的了解,然後就有很多引伸玩法了。

於是這裡推薦3本我喜歡的書:

  • Introduction to Computing Systems: From Bits and Gates to C and Beyond

  • Computer Systems: A Programmer"s Perspective (3rd Edition)

  • Hacker"s Delight (2nd Edition)

==================================

至於評論區里說 bitcast 是跟指針與內存相關的概念——bitcast只跟值的表現形式有關係,跟內存/指針是正交的概念。

當然可以通過指針操作來實現bitcast,但以為那就是bitcast的本質就不對了。

請看看這個例子:

float bitcast(int i) {
union {
int i;
float f;
} s;
s.i = i;
return s.f;
}

在Linux x86-64上用Clang 3.7.1 -O2編譯得到的結果是:

bitcast(int): # @bitcast(int)
movd %edi, %xmm0
retq

/* LLVM IR

; Function Attrs: norecurse nounwind readnone
define float @bitcast(i32) local_unnamed_addr #0 {
%2 = bitcast i32 %0 to float
ret float %2
}

*/

在Linux ARM32上用GCC 4.8.2 -O2編譯得到的結果是:

bitcast(int):
vmov s0, r0
bx lr

在Linux MIPS64上用GCC 5.4 -O2編譯得到的結果是:

bitcast(int):
j $31
mtc1 $4,$f0

(給我自己的筆記:libFirm在x86-64上的結果也可以檢查一下。在32位x86上的結果也挺有趣)

請說說看這個bitcast實現中,指針與內存在哪裡?

使用指針操作來實現bitcast自然是可以的:

float bitcast(int i) {
float f;
*((int*)f) = i;
return f;
}

通過指針與memcpy來實現bitcast也是可以的:

float bitcast(int i) {
float f;
memcpy(f, i, sizeof(int));
return f;
}

在不少編譯器上,這兩個版本(優化-)編譯出來的結果會跟前面用union的版本一樣。因為這些模式都實在是太常見了。

但既然不通過指針操作也可以實現,又怎能說它是本質。


其實我現在有的時候,用 printf, fprintf, sprintf, snprintf, printf_s, fprintf_s 還是要看文檔。

還要查 Fixed width integer types (since C99) 里的 PRIxx等,以前還有 VC 自訂的格式如 %Iu 等。


一堆括弧加指針,比如這種:int (*(*foo)(const void *))[3] 直接暈倒。

後來有了讀了Clockwise/Spiral Rule 以及cdecl: C gibberish ? English ,逐漸可以理解和掌握了


句柄,handle!

句柄你大爺!


一開始的時候看到longjmp我就覺得這是個什麼雞吧東西,後來學了彙編,就明白了。


一句話可以幫小白c碼農省好多事兒:

treat warning as error


scanf的格式字元串其實可以當半個正則表達式用。。。

所以sscanf就有了很神奇的用法……


預設,句柄,魯棒性


C99的compound literal

當然看不懂主要是因為C Primer Plus翻譯的太垃圾,什麼複合文字

最後懂了,這不就是數組/結構體字面值嘛

所以我決定以後不向初學者推薦C Primer Plus了


逗號表達式,到現在我都記不清規則... 公司禁用


嵌入式鏈表,一堆看不懂的宏,還有看不出來結構體類型的coredump


大一剛開始學那會兒,對於數組取地址的含義繞了很久

最近一個應該是找模板批量特化的方法,最後還是無果,但沒那麼多時間研究了,不知道能不能實現這種:

template &

class A {

//默認實現方案

};

template &

class A& {

//對上面這些類型有個特化實現方式

};

還是說得動手把下面這些類型一個個都寫一遍


hPenRed = CreatePen(PS_SOLID, 1, RGB(255, 0, 0));
hPen = SelectObject(hdc, hPenRed);

為什麼把新建的畫筆用SelectObject()選入DC以後返回的是之前畫筆的句柄?

因為一個DC里一次只能用一種畫筆,就像在紙上畫圖一樣,要換鴨嘴筆了,就把原來的鉛筆頂替出來了。

畫完圖以後要

SelectObject(hdc, hPen);
DeleteObject(hPenRed);

新建的畫筆是要佔內存的,畫完以後要把它釋放掉。不然畫圖的時候電腦一下子就……然後內存就被吃光了,這個是畫圖操作里最痛苦的。

如果不把原來的畫筆選回來,那麼DeleteObject()也就不能順利進行了。

所以旁邊經常放著MSDN幫助文檔來供查閱,發現程序有問題就用VS去調試,不能指望用記事本去寫程序,不然這個efficiency會非常低下


typedef 和 define

尤其define,年輕的時候以為就是很簡單的替換…後來…


函數指針,數組指針,指針數組。


我的第一個hello world程序全靠背。什麼iostream,我一直以為是跟蘋果有關係(有iOS啊,誰讓我是個果粉,下意識想到)。自習的時候還會默寫到紙上(我學的是商科,周圍的同學就都默默看著我,挺不好意思的)。

後來懂了之後感覺當時的我好可愛。

哦對了,我當時被騙了,學著C++還以為自己學的是C。不過後來我又把C學了一遍。


作為學嚴謹的Pascal入門,直接過渡到面向對象的C++,避開了c的很多奇巧淫技。

handle,是在接觸win32 api 最讓人難受的術語。

大學的時候還問過c++任課老師(浙大博士),

當時給的答案就是句柄。切,這金山詞霸也知道,等於沒回答。

現在看來,只是用c實現了部分面向對象思想的一種方式,

handle也好,context也好,只是一個指向特定結構的指針而已,用宏轉義罷了。

舉例:

開源代碼XZip.h中聲明了一個自定義的handle,HZIP,

DECLARE_HANDLE(HZIP);

#define DECLARE_HANDLE(name) struct name##__; typedef struct name##__ *name

展開就是

struct HZIP__;
typedef struct HZIP__ *HZIP;

XZip.cpp實際用起來,直接強轉:

typedef struct
{ DWORD flag;
TZip *zip;
} TZipHandleData;

TZipHandleData *han = new TZipHandleData;
han-&>flag = 2;
han-&>zip = zip;
return (HZIP)han;

總結:反正都當對象來,就沒啥疑義了。

設計者的某些設定,好比數學上的公理,接受就好了,深究為什麼,並沒多大意義。

還有就是c++的template ,主題是討論c,就不多說了。


printf和scanf的格式化語法有很大差異,一直是個坑

我就不說微軟的更加特別了


我踏馬開始的時候怎麼都搞不懂賦值運算符,為此還和老師/學長來往了幾封郵件……就比如,咳:

int a = 1;

int b = a;

為什麼啊為什麼啊 b 等於 a ??? 蛤???

所以後來被教導:等於你大爺啊,跟我讀,b 賦值為 a

ヾ(?&>﹏&


C語言倒是沒有,當初學 C++ 的時候對繼承、重載相當迷惑。

後來學 Delphi 的時候弄明白了……

(肯定是當初看的書不好~~~


推薦閱讀:

寫程序需要編譯器,編譯器是程序,輸入輸出也需要驅動,驅動也是程序,那麼第一個在電子計算機運行的程序是怎麼產生的?
Scheme語言的優勢?
Mathematica 能否成為取代 Python 乃至其他編程語言的程序設計語言?
新入職的軟體開發公司,看不懂代碼怎麼辦?
Python 中循環 import 造成的問題如何解決?

TAG:資料庫 | 編程 | C編程語言 | 計算機技術 | 底層開發 |