用C語言比較多,這篇是平時攢下的。有些內容在工作後可能會很常見,但是不用容易忘,所以就寫篇博客吧。
1. printf的用法
%*可以用來跳過字符,可以用於未知縮進。像下面一樣.
for(i = 1; i < 10; i++)
{
printf("%*c\r%*c\n", 9 - abs(i - 5), '*', abs(i - 5) + 1, '*');
}
%[]可以用來讀取指定的內容,%[^]可以用來忽略指定內容(正則表達式?)
%m可以不帶參數,輸出產生的錯誤信息
2. 二.關於define的用法
#是將後面所跟隨內容字符串化,##則是將前後內容連接起來
還有linux下各種多層宏定義。。。宏定義可謂博大精深啊。。
3. .關於setjmp和longjmp
目前在linux多線程的頭文件pthreadtypes.h中包含了此函數的頭文件。
這個主要作用是用來從異常恢復的.也可以做"軟重啟".
因為setjmp把環境變量的設置和棧的值都保存在參數env數組中,當longjmp時,根據其值來恢復setjmp時的位置.
這個env通常是一個全局變量.畢竟這是要跨越函數的.
4. #include
其實這個東西可以寫在任何的地方,你可以試試將main裡面代碼寫在某個文件中,然後在main中包含這個文件。代碼照樣運行。
5. .sizeof
當sizeof一個數組名的時候,如果數組和sizeof在同一個代碼塊中,那麼返回數組長,否則返回指針大小。
(當數組名傳遞給函數的時候傳遞的僅僅是個指針)
6. .volatile
讓每次讀取數據都從內存中讀取,而不是在其他位置。可以防止部分編譯器優化。在嵌入式中和多線程中有所應用。
最近學linux發現還可以用用來描述自己定義的鎖!從而避免了編譯器直接把鎖優化了。
7. main函數的奇妙用法.
其實main函數是可以遞歸的。
8. 關於||和&&
其實||的優先級比&&低。
9. 如何讓返回值有多重類型?
用union定義一個結構體,那麼就可以選擇返回多種類型了.
10. 為何通常c中的指針大小為4個字節?
一種可能是CPU的地址線只有32根.另一種是由於最大程序限制在4G=2^32B來確定的.
11. 當函數返回double值的時候是80位的.
所以在返回值賦給一個double變量的時候會損失精度.
具體的可以看 http://stackoverflow.com/questions/16888621/why-does-returning-a-floating-point-value-change-its-value
12. . __cdecl關鍵字
這個關鍵字是用來定義函數參數入棧順序從右向左的.並且我在VC上測試的時候發現是先計算再入棧.這個估計用的很少吧..
剛學AT&T的32位匯編,發現其實是用pushl來將參數逐一入棧,然後彈出傳給函數的.估計加了__cdecl的話就會使用pushl的方式來傳值吧.
貌似這樣就只能是從右到左傳參數給函數啊..嗯..還需學習..
13. 內存對齊
結構體和聯合體中,比如struct{int a;char b; double c};那麼這個結構體大小為16(32位機),不是13.這一點我經常忘掉..但是發現這個錯誤的時候又很容易想起來..
14. 位段
這個是1位1位的來分配的..(原來還以為是按字節來的..)
這個東西剛開始覺得似乎蠻有用.但是似乎用途只有在網絡協議需要這麼斤斤計較..
百度百科上說只能是unsigned和int類型指定,但是我在dev c++上寫c程序,也可以用char類型.
並且這個後面:加的值不能超過原來的類型的大小.
考慮到存儲器的對齊和效率應該應用不多.
15. 關於union的用法
其實這個可以用來顯示一些不能見的內容..比方說將一個指針和一個int定義在一起..如果指針是不能見的,那麼可以通過以int的形式來訪問..
union a{
point A;
};
如果A不能直接訪問,那麼就可以用B來訪問。也算是編譯器的漏洞吧。
我記得C++好像有一種類型指針是這樣的,不允許見到其值.但是可以用int來知道其值.
原先刷題還遇到過一個問題.VC不能用long long類型的於是想用union{char a[8];int a}.這樣來變成long long的..結果printf貌似只能識別到32位長度的大小..
所以還是老老實實去linux下編譯了...
16. 關於typedef與const聯合使用時一個值得注意的地方
typedef char* zifu;
const zifu a;
那麼a是個什麼樣的指針呢?是不能改變所指向地址的值還是不能改變自身?
這個問題我一開是也想錯了...想成用define的效果了...其實上面的定義等價於
char* const a;
也就是說不能改變指針自身,但是可以改變所指向的值..typedef與define相比會擴展一些內容.
17. .關於typedef的其他容易忘記的方法.
函數指針 typedef int (*)(char ) a;
那麼 a c; c就是一個返回值為int,參數為char類型的函數指針.
避免弄成int (*)(*f)(int ,char)(char)這種一團麻亂的樣子..很容易就看錯的..
申請數組 typedef int int_shuzu[10];
那麼int_shuzu a; a就是一個數組指針了..這種很容易看不懂的..
18. 關於參數傳遞
void fun(a,b,c)
int a,b,c;
{}
你很可能沒見過這種傳值方式,不過它確實是可以編譯成功的。可以少寫幾個int~
19. assert
C的異常處理宏。最近才發現有這個東西。這個比try--catch簡單多了。用於判斷某個條件是否滿足,不滿足的話跳轉到自己的函數上來顯示信息。用來調試程序很方便的。建議學習~
20. 關於如何把遞歸用函數指針來代替的方式.
這個方法很奇葩..感覺還是能有點用的.
#include <stdio.h>
typedef int (*PFUN)(int);
PFUN ptr[2];
int end(int a){
return 0;
}
int sum(int a){
return a + ptr[!!a](a - 1);
}
int main(){
int T, m;
scanf("%d", &T);
while(T--){
ptr[0] = end;
ptr[1] = sum;
scanf("%d", &m);
printf("%d\n", sum(m));
}
return 0;
}
這是通過函數指針加位運算來模仿遞歸...目標是求1+2+...+n..
兩次邏輯取反剛好把大於1的數轉為1,原來為零仍然為零.
這個技巧就當作好玩吧~~
21. 字符串常量
sizeof( 'a' );你猜猜是幾?結果是4!!!!這是C裡面估計很少人注意到的差別..當然也沒什麼用就是了..(C++裡面是1,但是C++還有'aa'這種單引號裡面兩個字符的情況..結果是4)
22. .強制轉換
這是我看一個opencv程序中發現的。你可能永遠都只將強制轉換用於賦值符號的右邊,但其實在左邊也是可以的.
就向這樣
((float*)(img->imageData + i* img->widthStep))[j]=mapp[i][j];
23. 重定向調試
freopen("文件名","r",stdin);寫在所有從輸入流讀取信息的函數之前。
將標准輸入重定向為文件中的內容。對於那些喜歡刷ACM的人來說,用了這個函數調試真是方便多了啊。
對於stdin這個是操作系統默認分給C程序的3個流指針之一,在linux下貌似文件描述符號0就是指stdin。當然還有1和2,分別是stdout,stderr。
三個指針定義在stdio.h中。
24. 關於指針與int類型
其實int類型可以賦值給指針類型!!
以前一直記得是必須加強制轉換的,但是在一本書上看到居然可以直接這樣!!
只是給個warming!!這對於底層程序員是多麼方便的事情啊!!!
在dev c++和VC上測試通過。還可以換成short*,double*,long*~
int i;
char*p;
i=1354;
p = i;
25. 關於宏函數與逗號表達式
最近看操作系統內核源碼發現的。。
其實逗號表達式和宏是一對好基友啊~不僅讓宏函數的功能更多,還可以讓宏函數帶上返回值!
並且和內聯函數一樣高效率!!
好比說交換a和b兩個值並且返回它們的和的宏函數,可以這樣寫:
#define swap(a,b,c) (c=a,a=b,b=c,a+b)
這樣只用保證abc三個變量都是同一類型即可,就不用寫那麼多函數了~~
是不是有點類似C++的模版啊~~~還提高了不少開發效率呢~~~
由於是宏函數,所以就無法用於下面的回調函數中了,不過宏函數本身就這麼方便和高效,這點缺點還是可以容忍的.
26. .回調函數
原來一直用qsort,但是不知道還有回調函數這一名字.
這就有點像C++中的泛型算法了.可以把判斷的部分通過函數封裝起來.
將判斷方法通過參數的方式傳遞給函數,然後實現泛型~~
void qsort(void *base,int nelem,int width,int (*fcmp)(const void *,const void *));
注意最後一個參數就是排序的條件判斷函數,也就是回調函數。
類似的還有多線程中指定的入口函數。
27. 關於_exit和exit的差別
_exit終止調用進程,但不關閉文件,不清除輸出緩存,也不調用出口函數。
exit函數將終止調用進程。在退出程序之前,所有文件關閉,緩沖輸出內容.
這實在其他地方直接抄來的.在linux下如果fork子進程來做點事的話可能會用到.
28. .關於int a[0];的用法。
原來學c的時候弄過這個,但只是當作實驗。。沒想到還真的有人會用這個東西。
主要用來當作內存地址的標簽用。可以通過編譯器來記住指針地址,但是不會消耗實際內存。
只有當分配空間之後才能使用。這是得多麼節約空間才會這麼寫啊。。。。
注意,這貌似是C99標准才支持的一個用法,並且 gcc編譯的時候需要帶上-pedantic選項。
29. 關於unsigned整數做循環變量
unsigned 0-1答案的二進制碼是全一啊!!!
原來寫單片機程序就被坑過..編譯器雖然會警告,但是通常都被我忽略了...
記住,只要是for中的循環變量比較,要麼全是有符號的,要麼全是無符號的!!
30. 關於為什麼要使用二進制讀文件和寫文件
這是學python的時候曉得的。。當你的文件保存為二進制的話,就可以用你的方法來解析文件。
如果不用二進制,那麼就得看操作系統了。所以,對於保存獨有的數據,多用二進制保存吧。話說數據庫的數據文件都是這種方式讀寫文件的吧。
而且二進制也不容被人了解。。好多游戲的文件都是明文的呢。。。
31. .sizeof(表達式)的問題
sizeof的表達式並不計算!!!只得出是什麼類型的而已!!
int a=1;
printf("%d\n",sizeof(a=2));
printf("%d\n",a);
結果是4,1
測試環境dev c++
32. 三目運算符與if/else哪個效率高?
個人在gcc下測試,發現匯編沒有差別。不知道其他平台是否也是這樣。當然我測試的方式可能不對。下文還會再說三目運算符的。
在CSAPP中說gcc會優化三目運算符。所以估計在某些特定情況下效率比if高點。
33. .一種簡單的宏交換
#define swap(a,b) (a ^= b^= a^= b)
多麼簡潔啊~雖然以前也寫過這種異或交換,但是從來想過還能用連等啊~~
34. long double
這是C99標准中的新類型.在<深入理解計算機系統>裡面說是擴展浮點數.有80個二進制位.
這80位剛好對應intel的CPU中的FPU.我的ATT匯編總結中講過這個寄存器,原來以為只是浮點返回值和浮點運算中間過程用到.
現在看來還可以當作變量用.需要注意的是在gcc下一般是會按4字節對齊的,所以不是10個字節,而是12個字節.
35. 關於<符號
這是看CSAPP上的.說<符號對於分支跳轉指令來說預測的准確率並不是特別的好.
所以剛好想到c++中循環的條件判斷用!=的習慣.雖然C++ primer上說是因為迭代器可能在循環中改變的原因,但是從這個來看還是養成寫!=的習慣比較好.
可能得到的效率不一定提高特別多,但是對於高並發高吞吐量的系統來說,我猜這點細微的差別還是有變化的.
還是養成寫!=的習慣吧~
36. 關於三目運算符
一般情況下沒什麼問題,但是在某些編譯器下,對於 xp?*xp:0這種xp是NULL指針的時候就不能用了,否則會造成內存錯誤!
這時因為編譯器對三目運算符進行了優化。先算出後面兩個表達式的結果,然後再來判斷條件。
不過我用gcc測試了一下,發現gcc不是這樣的,所以在gcc下這麼用沒什麼問題。
以後見到這種情況別奇怪就是了~
37. .關於宏函數中使用 #和##
之前一直不曉得這有什麼實際用處,但是在描述路徑的時候很有用.
好比說你之前文件在inc/現在改成在incc/下.那麼完全可以寫一個函數用來拼接
#define f(PATH,FILE) # x##y
這種形式.那麼更換文件目錄的時候就很方便了.
38. .關於定義多個同名全局變量
在同一文件中是不能定義多個同名全局變量的,但是在多個文件中就可以。
CSAPP上寫的.gcc的連接器在linking的時候額會把全局變量分為弱和強兩種狀態.
並且優先選擇強狀態,但可能類型會存在問題
好比在A文件如下定義了x和y
void other();
int x=112233;
int y=332211;
int main(){
prntf("x=0x%x y=0x%x \n",x,y);
other();
prntf("x=0x%x y=0x%x \n",x,y);
}
在另一個文件中也定義了x,但類型是double
double x;
void other(){
x=-0.0;
}
然後gcc 兩個文件。有一個警告。暫時先不管。
你運行了之後會發現,y的值居然變了!
也就是說,在other中,仍然認為x是double類型的!
double由於是8字節而int是4字節,給x賦值會覆蓋原來的y!
表示如果other函數中換成純粹的0.那麼y就是0!
39. .restrict關鍵字
這個常常忘記是什麼意思...是C99新加的標准.讓編譯器翻譯的時候,對其內存的操作只能通過其指針來完成.方便編譯器優化.
不過C++裡面好像還沒有這個關鍵字.
40. .const與define定義一個常量的區別
const指定的是一個變量,而define相當於直接替換.
const是可以帶指定類型的,define則是默認類型的.
同時const定義的變量還可以用取地址符&來得到其值.
41. 關於GCC內聯匯編
用一般的asm volatile()形式的話,在默認情況下是沒什麼問題的。但是如果在編譯條件中加上了-std=c99的話則不行。
具體我也不清楚怎麼搞。。沒查到什麼有用的東西。自己試過在__asm__volatile_和__asm volatile這些形式都不行。
有高手會的話還請指教!
42. .關於GNU C的__attribute__
這個是GNU C的一個特色。可以設置函數,變量,以及類型的屬性值。通常不用管這個東西,但是有一些軟件有要求會需要用到這個。
具體我就不展開了,百度可以搜出很多內容。這裡提一個__attribute__(( aligned(n))).在結構體後面加上後可以按照n字節對齊。默認按最大變量字節對齊。
43. .關於可變參數宏的實現(va_arg).
有一部分靠的是編譯器支持"..."這種參數形式,然後保證不等長參數都可以完全入棧。實際上,根據C標准的入棧規則,參數是依次從右往左入棧的。只要順著esp寄存器來找,那麼就可以依次讀出參數的其值。需要注意的是讀出多少個字節,不然棧就亂了。所以必須在讀出的時候加上參數類型。printf實現中也是先根據字符串中的類型再讀的。我個人實測後確實如此~
測試結果如下,Linux下gcc編譯。
如圖所示,顯然可以看到printf中實現的算法是先根據字符串對應的格式來從內存中讀取的。也就是說如果類型選錯了,內容顯示一定亂了。
44. 關於返回結構體
最近看編譯器實現方面的東西,貌似C++那種傳遞性的使用(如cout<< xxx<<<xx;這種)應該是不難實現的。然後我就上測試了下,我發現GCC下函數返回結構體是可以直接在後面加上.成員來訪問成員的。。結果如下
45. 關於**指針和(*)[]指針
我發現很多人還是不清楚**指針和(*)[]指針的差別。第一個我就叫它雙層指針,第二個叫行指針。有人喜歡把第一個叫二維指針吧,如果這麼叫很容易暈。
實際上前者是一個指向指針的指針,而後者是一個帶長度的指針。前者可以指向後者。後者與一般指針的差別就是你p++的時候是移動多個變量長度!其他指針只移動一個變量長度。這裡的長度說的是存儲變量地址的大小。前者指向一個指針變量的地址,而後者多用於指向一個二維數組的地址。所以傳遞二維數組參數的時候,用後者!
46. 關於%模運算
其實是可以模負數的!但和數學定義不同,對於負數的模仍然帶有符號。
用C語言比較多,這篇是平時攢下的。有些內容在工作後可能會很常見,但是不用容易忘,所以就寫篇博客吧。
1. printf的用法
%*可以用來跳過字符,可以用於未知縮進。像下面一樣.
for(i = 1; i < 10; i++)
{
printf("%*c\r%*c\n", 9 - abs(i - 5), '*', abs(i - 5) + 1, '*');
}
%[]可以用來讀取指定的內容,%[^]可以用來忽略指定內容(正則表達式?)
%m可以不帶參數,輸出產生的錯誤信息
2. 二.關於define的用法
#是將後面所跟隨內容字符串化,##則是將前後內容連接起來
還有linux下各種多層宏定義。。。宏定義可謂博大精深啊。。
3. .關於setjmp和longjmp
目前在linux多線程的頭文件pthreadtypes.h中包含了此函數的頭文件。
這個主要作用是用來從異常恢復的.也可以做"軟重啟".
因為setjmp把環境變量的設置和棧的值都保存在參數env數組中,當longjmp時,根據其值來恢復setjmp時的位置.
這個env通常是一個全局變量.畢竟這是要跨越函數的.
4. #include
其實這個東西可以寫在任何的地方,你可以試試將main裡面代碼寫在某個文件中,然後在main中包含這個文件。代碼照樣運行。
5. .sizeof
當sizeof一個數組名的時候,如果數組和sizeof在同一個代碼塊中,那麼返回數組長,否則返回指針大小。
(當數組名傳遞給函數的時候傳遞的僅僅是個指針)
6. .volatile
讓每次讀取數據都從內存中讀取,而不是在其他位置。可以防止部分編譯器優化。在嵌入式中和多線程中有所應用。
最近學linux發現還可以用用來描述自己定義的鎖!從而避免了編譯器直接把鎖優化了。
7. .main函數的奇妙用法.
其實main函數是可以遞歸的。
8. .關於||和&&
其實||的優先級比&&低。
9. ,如何讓返回值有多重類型?
用union定義一個結構體,那麼就可以選擇返回多種類型了.
10. .為何通常c中的指針大小為4個字節?
一種可能是CPU的地址線只有32根.另一種是由於最大程序限制在4G=2^32B來確定的.
11. .當函數返回double值的時候是80位的.
所以在返回值賦給一個double變量的時候會損失精度.
具體的可以看 http://stackoverflow.com/questions/16888621/why-does-returning-a-floating-point-value-change-its-value
12. . __cdecl關鍵字
這個關鍵字是用來定義函數參數入棧順序從右向左的.並且我在VC上測試的時候發現是先計算再入棧.這個估計用的很少吧..
剛學AT&T的32位匯編,發現其實是用pushl來將參數逐一入棧,然後彈出傳給函數的.估計加了__cdecl的話就會使用pushl的方式來傳值吧.
貌似這樣就只能是從右到左傳參數給函數啊..嗯..還需學習..
13. 內存對齊
結構體和聯合體中,比如struct{int a;char b; double c};那麼這個結構體大小為16(32位機),不是13.這一點我經常忘掉..但是發現這個錯誤的時候又很容易想起來..
14. .位段
這個是1位1位的來分配的..(原來還以為是按字節來的..)
這個東西剛開始覺得似乎蠻有用.但是似乎用途只有在網絡協議需要這麼斤斤計較..
百度百科上說只能是unsigned和int類型指定,但是我在dev c++上寫c程序,也可以用char類型.
並且這個後面:加的值不能超過原來的類型的大小.
考慮到存儲器的對齊和效率應該應用不多.
15. .關於union的用法
其實這個可以用來顯示一些不能見的內容..比方說將一個指針和一個int定義在一起..如果指針是不能見的,那麼可以通過以int的形式來訪問..
union a{
point A;
};
如果A不能直接訪問,那麼就可以用B來訪問。也算是編譯器的漏洞吧。
我記得C++好像有一種類型指針是這樣的,不允許見到其值.但是可以用int來知道其值.
原先刷題還遇到過一個問題.VC不能用long long類型的於是想用union{char a[8];int a}.這樣來變成long long的..結果printf貌似只能識別到32位長度的大小..
所以還是老老實實去linux下編譯了...
16. .關於typedef與const聯合使用時一個值得注意的地方
typedef char* zifu;
const zifu a;
那麼a是個什麼樣的指針呢?是不能改變所指向地址的值還是不能改變自身?
這個問題我一開是也想錯了...想成用define的效果了...其實上面的定義等價於
char* const a;
也就是說不能改變指針自身,但是可以改變所指向的值..typedef與define相比會擴展一些內容.
17. .關於typedef的其他容易忘記的方法.
函數指針 typedef int (*)(char ) a;
那麼 a c; c就是一個返回值為int,參數為char類型的函數指針.
避免弄成int (*)(*f)(int ,char)(char)這種一團麻亂的樣子..很容易就看錯的..
申請數組 typedef int int_shuzu[10];
那麼int_shuzu a; a就是一個數組指針了..這種很容易看不懂的..
18. .關於參數傳遞
void fun(a,b,c)
int a,b,c;
{}
你很可能沒見過這種傳值方式,不過它確實是可以編譯成功的。可以少寫幾個int~
19. .assert
C的異常處理宏。最近才發現有這個東西。這個比try--catch簡單多了。用於判斷某個條件是否滿足,不滿足的話跳轉到自己的函數上來顯示信息。用來調試程序很方便的。建議學習~
20. 關於如何把遞歸用函數指針來代替的方式.
這個方法很奇葩..感覺還是能有點用的.
#include <stdio.h>
typedef int (*PFUN)(int);
PFUN ptr[2];
int end(int a){
return 0;
}
int sum(int a){
return a + ptr[!!a](a - 1);
}
int main(){
int T, m;
scanf("%d", &T);
while(T--){
ptr[0] = end;
ptr[1] = sum;
scanf("%d", &m);
printf("%d\n", sum(m));
}
return 0;
}
這是通過函數指針加位運算來模仿遞歸...目標是求1+2+...+n..
兩次邏輯取反剛好把大於1的數轉為1,原來為零仍然為零.
這個技巧就當作好玩吧~~
21. 字符串常量
sizeof( 'a' );你猜猜是幾?結果是4!!!!這是C裡面估計很少人注意到的差別..當然也沒什麼用就是了..(C++裡面是1,但是C++還有'aa'這種單引號裡面兩個字符的情況..結果是4)
22. .強制轉換
這是我看一個opencv程序中發現的。你可能永遠都只將強制轉換用於賦值符號的右邊,但其實在左邊也是可以的.
就向這樣
((float*)(img->imageData + i* img->widthStep))[j]=mapp[i][j];
23. 重定向調試
freopen("文件名","r",stdin);寫在所有從輸入流讀取信息的函數之前。
將標准輸入重定向為文件中的內容。對於那些喜歡刷ACM的人來說,用了這個函數調試真是方便多了啊。
對於stdin這個是操作系統默認分給C程序的3個流指針之一,在linux下貌似文件描述符號0就是指stdin。當然還有1和2,分別是stdout,stderr。
三個指針定義在stdio.h中。
24. 關於指針與int類型
其實int類型可以賦值給指針類型!!
以前一直記得是必須加強制轉換的,但是在一本書上看到居然可以直接這樣!!
只是給個warming!!這對於底層程序員是多麼方便的事情啊!!!
在dev c++和VC上測試通過。還可以換成short*,double*,long*~
int i;
char*p;
i=1354;
p = i;
25. 關於宏函數與逗號表達式
最近看操作系統內核源碼發現的。。
其實逗號表達式和宏是一對好基友啊~不僅讓宏函數的功能更多,還可以讓宏函數帶上返回值!
並且和內聯函數一樣高效率!!
好比說交換a和b兩個值並且返回它們的和的宏函數,可以這樣寫:
#define swap(a,b,c) (c=a,a=b,b=c,a+b)
這樣只用保證abc三個變量都是同一類型即可,就不用寫那麼多函數了~~
是不是有點類似C++的模版啊~~~還提高了不少開發效率呢~~~
由於是宏函數,所以就無法用於下面的回調函數中了,不過宏函數本身就這麼方便和高效,這點缺點還是可以容忍的.
26. .回調函數
原來一直用qsort,但是不知道還有回調函數這一名字.
這就有點像C++中的泛型算法了.可以把判斷的部分通過函數封裝起來.
將判斷方法通過參數的方式傳遞給函數,然後實現泛型~~
void qsort(void *base,int nelem,int width,int (*fcmp)(const void *,const void *));
注意最後一個參數就是排序的條件判斷函數,也就是回調函數。
類似的還有多線程中指定的入口函數。
27. 關於_exit和exit的差別
_exit終止調用進程,但不關閉文件,不清除輸出緩存,也不調用出口函數。
exit函數將終止調用進程。在退出程序之前,所有文件關閉,緩沖輸出內容.
這實在其他地方直接抄來的.在linux下如果fork子進程來做點事的話可能會用到.
28. .關於int a[0];的用法。
原來學c的時候弄過這個,但只是當作實驗。。沒想到還真的有人會用這個東西。
主要用來當作內存地址的標簽用。可以通過編譯器來記住指針地址,但是不會消耗實際內存。
只有當分配空間之後才能使用。這是得多麼節約空間才會這麼寫啊。。。。
注意,這貌似是C99標准才支持的一個用法,並且 gcc編譯的時候需要帶上-pedantic選項。
29. 關於unsigned整數做循環變量
unsigned 0-1答案的二進制碼是全一啊!!!
原來寫單片機程序就被坑過..編譯器雖然會警告,但是通常都被我忽略了...
記住,只要是for中的循環變量比較,要麼全是有符號的,要麼全是無符號的!!
30. 關於為什麼要使用二進制讀文件和寫文件
這是學python的時候曉得的。。當你的文件保存為二進制的話,就可以用你的方法來解析文件。
如果不用二進制,那麼就得看操作系統了。所以,對於保存獨有的數據,多用二進制保存吧。話說數據庫的數據文件都是這種方式讀寫文件的吧。
而且二進制也不容被人了解。。好多游戲的文件都是明文的呢。。。
31. .sizeof(表達式)的問題
sizeof的表達式並不計算!!!只得出是什麼類型的而已!!
int a=1;
printf("%d\n",sizeof(a=2));
printf("%d\n",a);
結果是4,1
測試環境dev c++
32. 三目運算符與if/else哪個效率高?
個人在gcc下測試,發現匯編沒有差別。不知道其他平台是否也是這樣。當然我測試的方式可能不對。下文還會再說三目運算符的。
在CSAPP中說gcc會優化三目運算符。所以估計在某些特定情況下效率比if高點。
33. .一種簡單的宏交換
#define swap(a,b) (a ^= b^= a^= b)
多麼簡潔啊~雖然以前也寫過這種異或交換,但是從來想過還能用連等啊~~
34. long double
這是C99標准中的新類型.在<深入理解計算機系統>裡面說是擴展浮點數.有80個二進制位.
這80位剛好對應intel的CPU中的FPU.我的ATT匯編總結中講過這個寄存器,原來以為只是浮點返回值和浮點運算中間過程用到.
現在看來還可以當作變量用.需要注意的是在gcc下一般是會按4字節對齊的,所以不是10個字節,而是12個字節.
35. .關於<符號
這是看CSAPP上的.說<符號對於分支跳轉指令來說預測的准確率並不是特別的好.
所以剛好想到c++中循環的條件判斷用!=的習慣.雖然C++ primer上說是因為迭代器可能在循環中改變的原因,但是從這個來看還是養成寫!=的習慣比較好.
可能得到的效率不一定提高特別多,但是對於高並發高吞吐量的系統來說,我猜這點細微的差別還是有變化的.
還是養成寫!=的習慣吧~
36. .關於三目運算符
一般情況下沒什麼問題,但是在某些編譯器下,對於 xp?*xp:0這種xp是NULL指針的時候就不能用了,否則會造成內存錯誤!
這時因為編譯器對三目運算符進行了優化。先算出後面兩個表達式的結果,然後再來判斷條件。
不過我用gcc測試了一下,發現gcc不是這樣的,所以在gcc下這麼用沒什麼問題。
以後見到這種情況別奇怪就是了~
37. .關於宏函數中使用 #和##
之前一直不曉得這有什麼實際用處,但是在描述路徑的時候很有用.
好比說你之前文件在inc/現在改成在incc/下.那麼完全可以寫一個函數用來拼接
#define f(PATH,FILE) # x##y
這種形式.那麼更換文件目錄的時候就很方便了.
38. .關於定義多個同名全局變量
在同一文件中是不能定義多個同名全局變量的,但是在多個文件中就可以。
CSAPP上寫的.gcc的連接器在linking的時候額會把全局變量分為弱和強兩種狀態.
並且優先選擇強狀態,但可能類型會存在問題
好比在A文件如下定義了x和y
void other();
int x=112233;
int y=332211;
int main(){
prntf("x=0x%x y=0x%x \n",x,y);
other();
prntf("x=0x%x y=0x%x \n",x,y);
}
在另一個文件中也定義了x,但類型是double
double x;
void other(){
x=-0.0;
}
然後gcc 兩個文件。有一個警告。暫時先不管。
你運行了之後會發現,y的值居然變了!
也就是說,在other中,仍然認為x是double類型的!
double由於是8字節而int是4字節,給x賦值會覆蓋原來的y!
表示如果other函數中換成純粹的0.那麼y就是0!
39. .restrict關鍵字
這個常常忘記是什麼意思...是C99新加的標准.讓編譯器翻譯的時候,對其內存的操作只能通過其指針來完成.方便編譯器優化.
不過C++裡面好像還沒有這個關鍵字.
40. .const與define定義一個常量的區別
const指定的是一個變量,而define相當於直接替換.
const是可以帶指定類型的,define則是默認類型的.
同時const定義的變量還可以用取地址符&來得到其值.
41. 關於GCC內聯匯編
用一般的asm volatile()形式的話,在默認情況下是沒什麼問題的。但是如果在編譯條件中加上了-std=c99的話則不行。
具體我也不清楚怎麼搞。。沒查到什麼有用的東西。自己試過在__asm__volatile_和__asm volatile這些形式都不行。
有高手會的話還請指教!
42. .關於GNU C的__attribute__
這個是GNU C的一個特色。可以設置函數,變量,以及類型的屬性值。通常不用管這個東西,但是有一些軟件有要求會需要用到這個。
具體我就不展開了,百度可以搜出很多內容。這裡提一個__attribute__(( aligned(n))).在結構體後面加上後可以按照n字節對齊。默認按最大變量字節對齊。
43. .關於可變參數宏的實現(va_arg).
有一部分靠的是編譯器支持"..."這種參數形式,然後保證不等長參數都可以完全入棧。實際上,根據C標准的入棧規則,參數是依次從右往左入棧的。只要順著esp寄存器來找,那麼就可以依次讀出參數的其值。需要注意的是讀出多少個字節,不然棧就亂了。所以必須在讀出的時候加上參數類型。printf實現中也是先根據字符串中的類型再讀的。我個人實測後確實如此~
測試結果如下,Linux下gcc編譯。
如圖所示,顯然可以看到printf中實現的算法是先根據字符串對應的格式來從內存中讀取的。也就是說如果類型選錯了,內容顯示一定亂了。
44. 關於返回結構體
最近看編譯器實現方面的東西,貌似C++那種傳遞性的使用(如cout<< xxx<<<xx;這種)應該是不難實現的。然後我就上測試了下,我發現GCC下函數返回結構體是可以直接在後面加上.成員來訪問成員的。。結果如下
45. 關於**指針和(*)[]指針
我發現很多人還是不清楚**指針和(*)[]指針的差別。第一個我就叫它雙層指針,第二個叫行指針。有人喜歡把第一個叫二維指針吧,如果這麼叫很容易暈。
實際上前者是一個指向指針的指針,而後者是一個帶長度的指針。前者可以指向後者。後者與一般指針的差別就是你p++的時候是移動多個變量長度!其他指針只移動一個變量長度。這裡的長度說的是存儲變量地址的大小。前者指向一個指針變量的地址,而後者多用於指向一個二維數組的地址。所以傳遞二維數組參數的時候,用後者!
46. 關於%模運算
其實是可以模負數的!但和數學定義不同,對於負數的模仍然帶有符號。