嵌入式C慣用法

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

https://blog.csdn.net/yahohi/article/details/7927806

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章