1、cpp裏的c代碼按照c的方式來編譯和調用
時常在cpp的代碼之中看到這樣的代碼:
#ifdef __cplusplus
extern "C" {
#endif
//一段代碼
#ifdef __cplusplus
}
#endif
這樣的代碼到底是什麼意思呢?首先,__cplusplus是cpp中的自定義宏,那麼定義了這個宏的話表示這是一段cpp的代碼,也就是說,上面的代碼的含義是:如果這是一段cpp的代碼,那麼加入extern "C"{和}處理其中的代碼。
C++之父在設計C++之時,考慮到當時已經存在了大量的C代碼,爲了支持原來的C代碼和已經寫好C庫,需要在C++中儘可能的支持C,而extern "C"就是其中的一個策略。爲了在C++代碼中調用用C寫成的庫文件,就需要用extern "C"來告訴編譯器:這是一個用C寫成的庫文件,請用C的方式來鏈接它們。
2、多使用移位代替乘除操作,效率高
大小端交換操作:
int32_t swapInt32(int32_t value)
{
return ((value & 0x000000FF) << 24) |
((value & 0x0000FF00) << 8) |
((value & 0x00FF0000) >> 8) |
((value & 0xFF000000) >> 24) ;
}
判斷2的冪:
static bool is_power_of_2(uint32_t x)
{
return (x != 0) && ((x & (x - 1)) == 0);
}
3、頭文件中的static inline函數
inline 關鍵字實際上僅是建議內聯並不強制內聯,gcc中O0優化時是不內聯的,即使是O2以上,如果該函數被作爲函數指針賦值,那麼他也不會內聯,也必須產生函數實體,以獲得該函數地址。經測試c文件中的僅inline函數即使Os優化也不內聯,因爲沒有static,編譯認他是全局的,因此像普通函數一樣編譯了,本c文件也一樣通過 bl inline_func 這樣的方式調用,不像網上別人說的,本c會內聯,其他c文件則通過bl inline_func 方式。加入static 後則內聯了。(Os優化等級測試)
所以在頭文件中用inline時務必加入static,否則當inline不內聯時就和普通函數在頭文件中定義一樣,當多個c文件包含時就會重定義。所以加入static代碼健壯性高,如果都內聯了實際效果上是一樣的。(gcc下驗證過O0級別includes.h中僅定義inline的函數,編譯失敗,Os編譯成功)
爲什麼要在頭文件中定義函數呢?
雖然知道了頭文件中用inline函數時要加入static,但是爲什麼要在頭文件中定義函數呢?
一些簡單的封裝接口函數,如 open() { vfs_open() } 僅僅是爲了封裝一個接口,我們不希望耗費一次函數調用的時間,解決方法一是宏,但是作爲接口,宏不夠清晰。那選擇inline,但是如果在c文件中寫
main.c
inline void open(void)
{
vfs_open();
}
頭文件加聲明,外部要使用則不會內聯的,因爲編譯器有個原則,以c文件爲單位進行逐個編譯obj,每個c文件的編譯是獨立的,該c文件用到的外部函數都在編譯時預留一個符號,只有等到所有obj生成後鏈接時纔給這些符號地址(鏈接腳本決定地址),所以其他c文件編譯時只會看到這個函數的聲明而無法知道她的實體,就會像普通函數一樣通過bl 一個函數地址,等鏈接的時候再填入該地址了,他做不到內聯展開。
所以要內聯則必須在每個用到它的c文件體現實體,那就只有在頭文件了,所以會把這類希望全局使用又希望增加效率的函數實現在頭文件中static inline。
static inline 的壞處
因爲inline 是C99纔有的關鍵字,C89沒有,有部分編譯器不支持,或者部分支持,如支持__inline 或 __inline__等,所以我們一般會用一個宏定義inline 如:
#define INLINE static inline
不支持inline時:
#define INLINE static
但是這樣如果編譯器不支持inline 即意味着之前 static inline的函數全部被修改爲 static,在頭文件中寫static會有什麼後果呢?
經過測試果然和我們想的一樣,每個c文件包含了該頭文件後全部都有了該函數副本。這無疑增大了很多代碼量。比如在include.h
這樣的大頭文件,幾乎每個c文件我們都會包含他,相當於每一C文件都會加入一個 static void func(void){...} 實體。如果是函數宏則不會有這種問題,函數宏是沒有實際代碼的,沒調用他時代碼不存在。這就是頭文件中用static inline 函數的壞處。但是可以通過優化解決,經過測試,O0優化下在頭文件中定義static 函數包含該頭文件的三個c文件的確都有了該函數,但是在Os優化下則只有調用了該函數的C文件纔有實體。這是由編譯器對static函數的特性決定的。總之他的法則和我們想的一致,就是頭文件僅僅是單純的展開,而每個C獨立編譯,不會因爲知道其他個C文件定義了該函數,這個c文件就把他當外部bl了。
4、UL類型
0UL--------無符號長整型0
1UL--------無符號長整型1
如果沒有UL後綴,則系統默認爲 int類型,即,有符號整形
5、結構體中的偏移量
#define offsetof(TYPE,MEMBER) (size_t)&((TYPE*)0)->MEMBER
6、結構體字節對齊
字節對齊的作用不僅是便於cpu快速訪問,同時合理的利用字節對齊可以有效地節省存儲空間。
對齊跟數據在內存中的位置有關。如果一個變量的內存地址正好位於它長度的整數倍,他就被稱做自然對齊。比如在32位cpu下,假設一個整型變量的地址爲0x00000004,那它就是自然對齊的。
需要字節對齊的根本原因在於CPU訪問數據的效率問題。假設上面整型變量的地址不是自然對齊,比如爲0x00000002,則CPU如果取它的值的話需要訪問兩次內存,第一次取從0x00000002-0x00000003的一個short,第二次取從0x00000004-0x00000005的一個short然後組合得到所要的數據,如果變量在0x00000003地址上的話則要訪問三次內存,第一次爲char,第二次爲short,第三次爲char,然後組合得到整型數據。而如果變量在自然對齊位置上,則只要一次就可以取出數據。一些系統對對齊要求非常嚴格,比如sparc系統。
數組 :按照基本數據類型對齊,第一個對齊了後面的自然也就對齊了。
聯合 :按其包含的長度最大的數據類型對齊。
結構體: 結構體中每個數據類型都要對齊。
我們可以按照自己設定的對齊大小來編譯程序,GNU使用__attribute__選項來設置,比如我們想讓剛纔的結構按一字節對齊,我們可以這樣定義結構體
struct stu{
char sex;
int length;
char name[10];
}__attribute__ ((aligned (1)));
struct stu my_stu;
則sizeof(my_stu)可以得到大小爲15。否則不設置aligned的話得到的大小爲20。
7、位段
一個位域必須存儲在同一個字節中,不能跨兩個字節。如一個字節所剩空間不夠存放另一位域時,應從下一單元起存放該位域。
位域可以無位域名,這時它只用來作填充或調整位置。無名的位域是不能使用的。
如果結構體中含有位域(bit-field),那麼VC中準則是:
1) 如果相鄰位域字段的類型相同,且其位寬之和小於類型的sizeof大小,則後面的字段將緊鄰前一個字段存儲,直到不能容納爲止;
2) 如果相鄰位域字段的類型相同,但其位寬之和大於類型的sizeof大小,則後面的字段將從新的存儲單元開始,其偏移量爲其類型大小的整數倍;
3) 如果相鄰的位域字段的類型不同,則各編譯器的具體實現有差異,VC6採取不壓縮方式(不同位域字段存放在不同的位域類型字節中),Dev-C++和GCC都採取壓縮方式;
系統會先爲結構體成員按照對齊方式分配空間和填塞(padding),然後對變量進行位域操作。
8、部分函數實現
1.下面來看strcpy()原型寫法: 字符串拷貝.
char *strcpy(char *strDest, const char *strSrc)
{
assert((strDest!=NULL) && (strSrc !=NULL));
char *address = strDest;
while( (*strDest++ = * strSrc++)·1 != '/0')
NULL ;
return address ;
}
2.下面來看下memcpy函數的原型寫法:內存拷貝
void *memcpy(void *dest, const void *source, size_t count)
{
assert((NULL != dest) && (NULL != source));
char *tmp_dest = (char *)dest;
char *tmp_source = (char *)source;
while(count --)//不對是否存在重疊區域進行判斷
*tmp_dest ++ = *tmp_source ++;
return dest;
}
3.下面來看下memmove函數的原型寫法:
void *memmove(void *dest, const void *source, size_t count)
{
assert((NULL != dest) && (NULL != source));
char *tmp_source, *tmp_dest;
tmp_source = (char *)source;
tmp_dest = (char *)dest;
if((dest + count<source) || (source + count) <dest))
{// 如果沒有重疊區域
while(count--)
*tmp_dest++ = *tmp_source++;
}
else
{ //如果有重疊(反向拷貝)
tmp_source += count - 1;
tmp_dest += count - 1;
while(count--)
*--tmp_dest = *--tmp;
}
return dest;
}
單鏈表排序:
void link_order(STU *p_head)
{
STU *pb, *pf, temp;
pf = p_head;
if(p_head == NULL) {//鏈表爲空
printf("needn't order.\n");
return ;
}
if(p_head->next == NULL) {//鏈表有1個節點
printf("only one print, needn't order.\n");
return ;
}
while(pf->next != NULL) {//以pf指向的節點爲基準節點
pb = pf->next;//pb從基準點的下一個節點開始
while(pb != NULL) {
if(pf->num > pb->num) {
temp = *pf;
*pf = *pb;
*pb = temp;
temp.next = pf->next;
pf->next = pb->next;
pb->next = temp.next;
}
pb = pb->next;
}
pf = pf->next;
}
return ;
}
參考鏈接:https://www.cnblogs.com/yuemw/p/7908413.html
https://blog.csdn.net/kuai0705/article/details/20841133
https://blog.csdn.net/huanghui167/article/details/41346663
https://blog.csdn.net/lanzhihui_10086/article/details/44353381
https://www.cnblogs.com/bigrabbit/archive/2012/09/20/2695543.html