用C語言比較多,這篇是平時攢下的。有些內容在工作後可能會很常見,但是不用容易忘,所以就寫篇博客吧。
一.printf的用法
%*可以用來跳過字符,可以用於未知縮進。像下面一樣.
%[]可以用來讀取指定的內容,%[^]可以用來忽略指定內容(正則表達式?)
%m可以不帶參數,輸出產生的錯誤信息
二.關於define的用法
#是將後面所跟隨內容字符串化,##則是將前後內容連接起來
還有linux下各種多層宏定義。。。宏定義可謂博大精深啊。。
三.關於setjmp和longjmp
目前在linux多線程的頭文件pthreadtypes.h中包含了此函數的頭文件。
這個主要作用是用來從異常恢復的.也可以做"軟重啓".
因爲setjmp把環境變量的設置和棧的值都保存在參數env數組中,當longjmp時,根據其值來恢復setjmp時的位置.
這個env通常是一個全局變量.畢竟這是要跨越函數的.
四.#include
其實這個東西可以寫在任何的地方,你可以試試將main裏面代碼寫在某個文件中,然後在main中包含這個文件。代碼照樣運行。
五.sizeof
當sizeof一個數組名的時候,如果數組和sizeof在同一個代碼塊中,那麼返回數組長,否則返回指針大小。
(當數組名傳遞給函數的時候傳遞的僅僅是個指針)
六.volatile
防止編譯器優化,讓每次讀取數據都從內存中讀取,而不是在其他位置。可以防止部分編譯器優化。在嵌入式中和多線程中有所應用。
最近學linux發現還可以用用來描述自己定義的鎖!從而避免了編譯器直接把鎖優化了。
七.main函數的奇妙用法.
其實main函數是可以遞歸的。
並且.C文件中就一句main;也是可以編譯過的..剛好連接器可以找到對象..
八.關於||和&&
其實||的優先級比&&低。
九,如何讓返回值有多重類型?
用union定義一個結構體,那麼就可以選擇返回多種類型了.
十.爲何通常c中的指針大小爲4個字節?
一種可能是CPU的地址線只有32根.另一種是由於最大程序限制在4G=2^32B來確定的.
十一.當函數返回double值的時候是80位的.
所以在返回值賦給一個double變量的時候會損失精度.
十二. __cdecl關鍵字
這個關鍵字是用來定義函數參數入棧順序從右向左的.並且我在VC上測試的時候發現是先計算再入棧.這個估計用的很少吧..
剛學AT&T的32位彙編,發現其實是用pushl來將參數逐一入棧,然後彈出傳給函數的.估計加了__cdecl的話就會使用pushl的方式來傳值吧.
貌似這樣就只能是從右到左傳參數給函數啊..嗯..還需學習..
十三.內存對齊
結構體和聯合體中,比如struct{int a;char b; double c};那麼這個結構體大小爲16(32位機),不是13.這一點我經常忘掉..但是發現這個錯誤的時候又很容易想起來..
十四.位段
這個是1位1位的來分配的..(原來還以爲是按字節來的..)
這個東西剛開始覺得似乎蠻有用.但是似乎用途只有在網絡協議需要這麼斤斤計較..
百度百科上說只能是unsigned和int類型指定,但是我在dev c++上寫c程序,也可以用char類型.
並且這個後面:加的值不能超過原來的類型的大小.
考慮到存儲器的對齊和效率應該應用不多.
十五.關於union的用法
其實這個可以用來顯示一些不能見的內容..比方說將一個指針和一個int定義在一起..如果指針是不能見的,那麼可以通過以int的形式來訪問..
union a{
point A;
int b;
};
如果A不能直接訪問,那麼就可以用B來訪問。也算是編譯器的漏洞吧。
我記得C++好像有一種類型指針是這樣的,不允許見到其值.但是可以用int來知道其值.
原先刷題還遇到過一個問題.VC不能用long long類型的於是想用union{char a[8];int a}.這樣來變成long long的..結果printf貌似只能識別到32位長度的大小..
所以還是老老實實去linux下編譯了...
十六.關於typedef與const聯合使用時一個值得注意的地方
typedef char* zifu;
const zifu a;
那麼a是個什麼樣的指針呢?是不能改變所指向地址的值還是不能改變自身?
這個問題我一開是也想錯了...想成用define的效果了...其實上面的定義等價於
char* const a;
也就是說不能改變指針自身,但是可以改變所指向的值..typedef與define相比會擴展一些內容.
十七.關於typedef的其他容易忘記的方法.
函數指針 typedef int (*)(char ) a;
那麼 a c; c就是一個返回值爲int,參數爲char類型的函數指針.
避免弄成int (*)(*f)(int ,char)(char)這種一團麻亂的樣子..很容易就看錯的..
申請數組 typedef int int_shuzu[10];
那麼int_shuzu a; a就是一個數組指針了..這種很容易看不懂的..
十八。關於參數傳遞
void fun(a,b,c)
int a,b,c;
{}
你很可能沒見過這種傳值方式,不過它確實是可以編譯成功的。可以少寫幾個int~
十九.assert
C的異常處理宏。最近才發現有這個東西。這個比try--catch簡單多了。用於判斷某個條件是否滿足,不滿足的話跳轉到自己的函數上來顯示信息。用來調試程序很方便的。建議學習~
二十.關於如何把遞歸用函數指針來代替的方式.
這個方法很奇葩..感覺還是能有點用的.
#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,原來爲零仍然爲零.
這個技巧就當作好玩吧~~
二十一:字符串常量
sizeof( 'a' );你猜猜是幾?結果是4!!!!這是C裏面估計很少人注意到的差別..當然也沒什麼用就是了..(C++裏面是1,但是C++還有'aa'這種單引號裏面兩個字符的情況..結果是4)
二十二.強制轉換
這是我看一個opencv程序中發現的。你可能永遠都只將強制轉換用於賦值符號的右邊,但其實在左邊也是可以的.
就向這樣
((float*)(img->imageData + i* img->widthStep))[j]=mapp[i][j];
二十三。重定向調試
freopen("文件名","r",stdin);寫在所有從輸入流讀取信息的函數之前。
將標準輸入重定向爲文件中的內容。對於那些喜歡刷ACM的人來說,用了這個函數調試真是方便多了啊。
對於stdin這個是操作系統默認分給C程序的3個流指針之一,在linux下貌似文件描述符號0就是指stdin。當然還有1和2,分別是stdout,stderr。
三個指針定義在stdio.h中。
二十四。關於指針與int類型
其實int類型可以賦值給指針類型!!
以前一直記得是必須加強制轉換的,但是在一本書上看到居然可以直接這樣!!
只是給個warming!!這對於底層程序員是多麼方便的事情啊!!!
在dev c++和VC上測試通過。還可以換成short*,double*,long*~
int i;
char*p;
i=1354;
p = i;
二十五。關於宏函數與逗號表達式
最近看操作系統內核源碼發現的。。
其實逗號表達式和宏是一對好基友啊~不僅讓宏函數的功能更多,還可以讓宏函數帶上返回值!
並且和內聯函數一樣高效率!!
好比說交換a和b兩個值並且返回它們的和的宏函數,可以這樣寫:
#define swap(a,b,c) (c=a,a=b,b=c,a+b)
這樣只用保證abc三個變量都是同一類型即可,就不用寫那麼多函數了~~
是不是有點類似C++的模版啊~~~還提高了不少開發效率呢~~~
由於是宏函數,所以就無法用於下面的回調函數中了,不過宏函數本身就這麼方便和高效,這點缺點還是可以容忍的.
二十六.回調函數
原來一直用qsort,但是不知道還有回調函數這一名字.
這就有點像C++中的泛型算法了.可以把判斷的部分通過函數封裝起來.
將判斷方法通過參數的方式傳遞給函數,然後實現泛型~~
void qsort(void *base,int nelem,int width,int (*fcmp)(const void *,const void *));
注意最後一個參數就是排序的條件判斷函數,也就是回調函數。
類似的還有多線程中指定的入口函數。
二十七.關於_exit和exit的差別
_exit終止調用進程,但不關閉文件,不清除輸出緩存,也不調用出口函數。
exit函數將終止調用進程。在退出程序之前,所有文件關閉,緩衝輸出內容.
這實在其他地方直接抄來的.在linux下如果fork子進程來做點事的話可能會用到.
二十八.關於int a[0];的用法。
原來學c的時候弄過這個,但只是當作實驗。。沒想到還真的有人會用這個東西。
主要用來當作內存地址的標籤用。可以通過編譯器來記住指針地址,但是不會消耗實際內存。
只有當分配空間之後才能使用。這是得多麼節約空間纔會這麼寫啊。。。。
注意,這貌似是C99標準才支持的一個用法,並且gcc編譯的時候需要帶上-pedantic選項。
二十九.關於unsigned整數做循環變量
unsigned 的循環變量,判斷條件 < -1相當於是小於最大的數= =。這一點千萬別弄錯了。
原來寫單片機程序就被坑過..編譯器雖然會警告,但是通常都被我忽略了...
記住,只要是for中的循環變量比較,要麼全是有符號的,要麼全是無符號的!!
三十.關於爲什麼要使用二進制讀文件和寫文件
這是學python的時候曉得的。。當你的文件保存爲二進制的話,就可以用你的方法來解析文件。
如果不用二進制,那麼就得看操作系統了。所以,對於保存獨有的數據,多用二進制保存吧。話說數據庫的數據文件都是這種方式讀寫文件的吧。
而且二進制也不容被人瞭解。。好多遊戲的文件都是明文的呢。。。
三十一.sizeof(表達式)的問題
sizeof的表達式並不計算!!!只得出是什麼類型的而已!!
int a=1;
printf("%d\n",sizeof(a=2));
printf("%d\n",a);
結果是4,1
測試環境dev c++
三十二.三目運算符與if/else哪個效率高?
個人在gcc下測試,發現彙編沒有差別。不知道其他平臺是否也是這樣。當然我測試的方式可能不對。下文還會再說三目運算符的。
在CSAPP中說gcc會優化三目運算符。所以估計在某些特定情況下效率比if高點。
三十三.一種簡單的宏交換
#define swap(a,b) (a ^= b^= a^= b)
多麼簡潔啊~雖然以前也寫過這種異或交換,但是從來想過還能用連等啊~~
三十四.long double
這是C99標準中的新類型.在<深入理解計算機系統>裏面說是擴展浮點數.有80個二進制位.
這80位剛好對應intel的CPU中的FPU.我的ATT彙編總結中講過這個寄存器,原來以爲只是浮點返回值和浮點運算中間過程用到.
現在看來還可以當作變量用.需要注意的是在gcc下一般是會按4字節對齊的,所以不是10個字節,而是12個字節.
三十五.關於<符號
這是看CSAPP上的.說<符號對於分支跳轉指令來說預測的準確率並不是特別的好.
所以剛好想到c++中循環的條件判斷用!=的習慣.雖然C++ primer上說是因爲迭代器可能在循環中改變的原因,但是從這個來看還是養成寫!=的習慣比較好.
可能得到的效率不一定提高特別多,但是對於高併發高吞吐量的系統來說,我猜這點細微的差別還是有變化的.
還是養成寫!=的習慣吧~
三十六.關於三目運算符
一般情況下沒什麼問題,但是在某些編譯器下,對於 xp?*xp:0這種xp是NULL指針的時候就不能用了,否則會造成內存錯誤!
這時因爲編譯器對三目運算符進行了優化。先算出後面兩個表達式的結果,然後再來判斷條件。
不過我用gcc測試了一下,發現gcc不是這樣的,所以在gcc下這麼用沒什麼問題。
以後見到這種情況別奇怪就是了~
三十七.關於宏函數中使用 #和##
之前一直不曉得這有什麼實際用處,但是在描述路徑的時候很有用.
好比說你之前文件在inc/現在改成在incc/下.那麼完全可以寫一個函數用來拼接
#define f(PATH,FILE) # x##y
這種形式.那麼更換文件目錄的時候就很方便了.
三十八.關於定義多個同名全局變量
在同一文件中是不能定義多個同名全局變量的,但是在多個文件中就可以。
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!
三十九.restrict關鍵字
這個常常忘記是什麼意思...是C99新加的標準.讓編譯器翻譯的時候,對其內存的操作只能通過其指針來完成.方便編譯器優化.
不過C++裏面好像還沒有這個關鍵字.
四十.const與define定義一個常量的區別
const指定的是一個變量,而define相當於直接替換.
const是可以帶指定類型的,define則是默認類型的.
同時const定義的變量還可以用取地址符&來得到其值.
四十一.關於GCC內聯彙編
用一般的asm volatile()形式的話,在默認情況下是沒什麼問題的。但是如果在編譯條件中加上了-std=c99的話則不行。
具體我也不清楚怎麼搞。。沒查到什麼有用的東西。自己試過在__asm__volatile_和__asm volatile這些形式都不行。
有高手會的話還請指教!
四十二.關於GNU C的__attribute__
這個是GNU C的一個特色。可以設置函數,變量,以及類型的屬性值。通常不用管這個東西,但是有一些軟件有要求會需要用到這個。
具體我就不展開了,百度可以搜出很多內容。這裏提一個__attribute__((aligned(n))).在結構體後面加上後可以按照n字節對齊。默認按最大變量字節對齊。
四十三.關於可變參數宏的實現(va_arg).
有一部分靠的是編譯器支持"..."這種參數形式,然後保證不等長參數都可以完全入棧。實際上,根據C標準的入棧規則,參數是依次從右往左入棧的。只要順着esp寄存器來找,那麼就可以依次讀出參數的其值。需要注意的是讀出多少個字節,不然棧就亂了。所以必須在讀出的時候加上參數類型。printf實現中也是先根據字符串中的類型再讀的。我個人實測後確實如此~
測試結果如下,Linux下gcc編譯。
如圖所示,顯然可以看到printf中實現的算法是先根據字符串對應的格式來從內存中讀取的。也就是說如果類型選錯了,內容顯示一定亂了。
四十四。關於返回結構體
最近看編譯器實現方面的東西,貌似C++那種傳遞性的使用(如cout<< xxx<<<xx;這種)應該是不難實現的。然後我就上測試了下,我發現GCC下函數返回結構體是可以直接在後面加上.成員來訪問成員的。。結果如下
四十五。關於**指針和(*)[]指針
我發現很多人還是不清楚**指針和(*)[]指針的差別。第一個我就叫它雙層指針,第二個叫行指針。有人喜歡把第一個叫二維指針吧,如果這麼叫很容易暈。
實際上前者是一個指向指針的指針,而後者是一個帶長度的指針。前者可以指向後者。後者與一般指針的差別就是你p++的時候是移動多個變量長度!其他指針只移動一個變量長度。這裏的長度說的是存儲變量地址的大小。前者指向一個指針變量的地址,而後者多用於指向一個二維數組的地址。所以傳遞二維數組參數的時候,用後者!
四十六。關於數組名是指針
這裏要注意一點。對數組名取地址,那麼得到的仍然是數組的地址值!而不是想當然的二級指針。也就是說加不加取地址符,其值都一樣。但兩者+1的值不同,前者是對指針增加元素長度,而&之後則成了行指針。
int a[50];
<pre name="code" class="cpp">printf("%d\n",a);
printf("%d\n",a+1);printf("%d\n",&a+1);
我在gcc下測試下a的地址後三位爲656,a+1爲660,&a+1則是856.
四十七。關於%模運算
其實是可以模負數的!但和數學定義不同,對於負數的模仍然帶有符號。
四十七。關於作用域(do_div)
這個函數是我在調試我的內核的時候碰見的。發現很神奇。
#define do_div(n,base) ({ \
int __res; \
__asm__("divl %4":"=a" (n),"=d" (__res):"0" (n),"1" (0),"r" (base)); \
__res; })
意思是用n除以base。然後結果存入n,餘數存入__res並返回!這個可以當函數用。在gcc下可以編譯過。而且很神奇的是,外面有對()括號!替換到相應位置後就相當於是匿名函數了!還有哪些高級語言有的機制C語言也有呢,真是越學C越覺得博大精深啊!
再次測試,在VC6.0下無法通過。。
四十八。關於函數指針取地址
很多人不明白函數名爲什麼是個指針。。其實取了地址也是函數名的值,沒有任何變化。所以別再在函數名前面加&了,看着很彆扭。。
四十九。關於do while 0
在Linux內核的宏中很常見。用法感覺也蠻多的,百度也搜得到。我簡單羅列一下我覺得比較靠譜的說法。
1.打包指令。相當於把其中的指令包起來,就可以在用的時候當作一條指令來用來了。
2.對於空的宏來說,可以避免編譯器給出warming。
3.實際上可以當作函數了。畢竟可以在{}中定義變量。現在絕大部分都用inline吧。感覺也只能在老代碼裏面看見了。
五十:關於C語言中的typeof
注意是typeof.不是sizeof.主要用於強制轉化.下面就用到了.
五十一. 關於0地址的使用方法
估計很多人就只認爲NULL地址只能賦值給空指針吧.但其實還有另外的用途.在Linux內核中,數據結構中的指針是封裝的.
我用的<Linux內核設計與實現上的例子>
struct list_head{
struct list_head *next;
struct list_head *pre;
};
struct fox{
unsigned long tail_length;
unsigned long weight;
bool is_fantastic;
struct list_head list;
};
#define constainer_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type, member) );})
也就是說,不是單獨給數據結構分配個指針,而是封裝好指針,塞進數據結構.
並且可以通過0地址+類型判斷來得到其相對偏移.這樣就可以通過指針地址+便宜來得到指針中的新地址了.
當然這樣很麻煩了...注意link的位置很重要!
五十二.關於多個if和switch效率比較
switch會根據代碼生成一張表,然後根據輸入直接跳轉到相應的代碼區域..而if..else則挨個的判斷..(取自CSAPP)
五十三.關於動態庫函數重名
爲什麼動態庫可以有重名函數呢?這是一位網友問我的問題,開始覺得不太可能,因爲動態庫函數應該有符號表來唯一確定每個函數的入口。
http://bbs.csdn.net/topics/350156218 這個是我搜出來的答案。gcc下,默認是把動態庫的符號設置爲全局的。加載一個動態庫中的函數後,如果後面的動態庫有重名的那麼就會捨棄後面一個重名的函數。到這裏我突然明白namespace 的意義了。gcc下用__attribute__ ((visibility("hidden")))來隱藏其標識,默認不是static
的全部導出到動態庫。
五十三.__stdcall, __cdecl, __pascal, __fastcall,__thiscall的差別
__cdecl這個是C的標準調用標識。從右向左入棧,清棧操作由調用者來執行。並不限定參數個數與類型的完全匹配(好像實際用到的時候還是要匹配的。。)。
__stdcall這個是C++的標準調用標識。從右向左入棧,函數棧由被調用者來清除。嚴格匹配參數的調用。返回指令用的retnX,X表示棧指針移動長度。
__pascal這個簡單來說就是和_cdecl的入棧和清棧方式相反。
__fastcall這個是用兩個寄存器來保存參數。返回方式和_stdcall相同。(這個估計很)
__thiscall則是用在類成員中,可以要求把this指針放在指定的寄存器中傳遞。
C只能用cdecl。C++就可以用下面幾個了。