2014年7月10日 星期四

do...while(0)的妙用

在C++中,有三种类型的循环语句:for, while, 和do...while, 但是在一般应用中作循环时, 我们可能用for和while要多一些,do...while相对不受重视。
    但是,最近在读我们项目的代码时,却发现了do...while的一些十分聪明的用法,不是用来做循环,而是用作其他来提高代码的健壮性。
1. do...while(0)消除goto语句。
通常,如果在一个函数中开始要分配一些资源,然后在中途执行过程中如果遇到错误则退出函数,当然,退出前先释放资源,我们的代码可能是这样:
version 1

bool Execute()
{
   // 分配资源
   int *p = new int;
   bool bOk(true);

   // 执行并进行错误处理
   bOk = func1();
   if(!bOk) 
   {
      delete p;   
      p = NULL;
      return false;
   }

   bOk = func2();
   if(!bOk) 
   {
      delete p;   
      p = NULL;
      return false;
   }

   bOk = func3();
   if(!bOk) 
   {
      delete p;   
      p = NULL;
      return false;
   }

   // ..........

   // 执行成功,释放资源并返回
    delete p;   
    p = NULL;
    return true;
   
}


这里一个最大的问题就是代码的冗余,而且我每增加一个操作,就需要做相应的错误处理,非常不灵活。于是我们想到了goto:
version 2
bool Execute()
{
   // 分配资源
   int *p = new int;
   bool bOk(true);

   // 执行并进行错误处理
   bOk = func1();
   if(!bOk) goto errorhandle;

   bOk = func2();
   if(!bOk) goto errorhandle;

   bOk = func3();
   if(!bOk) goto errorhandle;

   // ..........

   // 执行成功,释放资源并返回
    delete p;   
    p = NULL;
    return true;

errorhandle:
    delete p;   
    p = NULL;
    return false;
   
}


代码冗余是消除了,但是我们引入了C++中身份比较微妙的goto语句,虽然正确的使用goto可以大大提高程序的灵活性与简洁性,但太灵活的东西往往是很危险的,它会让我们的程序捉摸不定,那么怎么才能避免使用goto语句,又能消除代码冗余呢,请看do...while(0)循环:
version3

bool Execute()
{
   // 分配资源
   int *p = new int;

   bool bOk(true);
   do
   {
      // 执行并进行错误处理
      bOk = func1();
      if(!bOk) break;

      bOk = func2();
      if(!bOk) break;

      bOk = func3();
      if(!bOk) break;

      // ..........

   }while(0);

    // 释放资源
    delete p;   
    p = NULL;
    return bOk;
   
}


“漂亮!”, 看代码就行了,啥都不用说了...

2 宏定义中的do...while(0)
  如果你是C++程序员,我有理由相信你用过,或者接触过,至少听说过MFC, 在MFC的afx.h文件里面, 你会发现很多宏定义都是用了do...while(0)或do...while(false), 比如说:
#define AFXASSUME(cond)       do { bool __afx_condVal=!!(cond); ASSERT(__afx_condVal); __analysis_assume(__afx_condVal); } while(0) 
粗看我们就会觉得很奇怪,既然循环里面只执行了一次,我要这个看似多余的do...while(0)有什么意义呢? 
当然有!
为了看起来更清晰,这里用一个简单点的宏来演示:
#define SAFE_DELETE(p) do{ delete p; p = NULL} while(0)
假设这里去掉do...while(0),
#define SAFE_DELETE(p) delete p; p = NULL;
那么以下代码:
if(NULL != p) SAFE_DELETE(p)
else   ...do sth...
就有两个问题,
1) 因为if分支后有两个语句,else分支没有对应的if,编译失败
2) 假设没有else, SAFE_DELETE中的第二个语句无论if测试是否通过,会永远执行。
你可能发现,为了避免这两个问题,我不一定要用这个令人费解的do...while,  我直接用{}括起来就可以了
#define SAFE_DELETE(p) { delete p; p = NULL;}
的确,这样的话上面的问题是不存在了,但是我想对于C++程序员来讲,在每个语句后面加分号是一种约定俗成的习惯,这样的话,以下代码:
if(NULL != p) SAFE_DELETE(p);
else   ...do sth...
其else分支就无法通过编译了(原因同上),所以采用do...while(0)是做好的选择了。

也许你会说,我们代码的习惯是在每个判断后面加上{}, 就不会有这种问题了,也就不需要do...while了,如:
if(...) 
{
}
else
{
}
诚然,这是一个好的,应该提倡的编程习惯,但一般这样的宏都是作为library的一部分出现的,而对于一个library的作者,他所要做的就是让其库具有通用性,强壮性,因此他不能有任何对库的使用者的假设,如其编码规范,技术水平等。

Ref: http://www.cnblogs.com/flying_bat/archive/2008/01/18/1044693.html

2014年6月30日 星期一

typedef of function pointer

當你在宣告一個變數時是這樣的:
int ImVar;//<-----------------------1
當你在宣告一個函式時卻是這樣:
int ImFun(...);//---------------------2
變數宣告時名稱在最後面,而函式名稱卻在中間,
你會不會覺得這很奇怪?
本來用一個小括號括起來的參數定義就是函式名稱的附屬品
你可以當它是函式名稱的一部份。沒有了它函式名稱就不完整了。
(注意在C++中不同參數的同名函式在編譯器的內部函式名稱是不同的)

typedef int INT;//<------------------3
typedef int *PINT;//<--------------4
typedef int (*PINT);//<--------------5
3式是定義一個int的型態,名為INT
4式是定義一個int的指標型態,名為PINT
5式是定義一個指向int的指標型態,名為PINT
4式和5式的結果是等效的。

現在我們嘗試為函式定義型態:
typedef int IntFun(...);//<------------6
先注意到有關2式的說明,就不應再對為何函式名稱後還有(...)
6式定義一個型態(或返回)int函式,名稱為IntFun。
我們知道,函式名本身俱有隱性指標的性質,所以IntFun和 *IntFun是
等效的。
那麼可以明白的定義IntFun為指標嗎,應該可以的!直觀的感覺是套入
4式:
typedef int * IntFun(...);//<------------7
問題出來了,任何一個編譯器都會把7式解讀為:
定義一個型態(或返回)int *函式,名稱為IntFun。
這不是我們要的,那要如何指定指標('*')給IntFun而不是int呢?
答案就是括號,也就是套入5式而不是4式:
typedef int (*IntFun)(...);//<------------8
這就是原提問要的解答了,唯要注意的是
對型態的定義來說 4式和5式是等效的,
但對函式的定義來說6式和8式才是等效的;
那麼使用6式或8式效好?
一般都使用8弍,它有較好的可讀性,隱式指標總是令人較為困感的。
而且也不敢保證所有的編譯器都可以接受6式的敘述。

Ref: http://www.programmer-club.com.tw/ShowSameTitleN/c/25059.html

C 指標 函式指標 函式指標陣列

下面是一些範例(在64位元的機器中,指標為 8 bytes):
char (*p)[20];
sizeof(p) = 8
sizeof(p[0]) = 20
sizeof(p[1]) = 20
sizeof(*p) = 20
p =  malloc(sizeof(*p));
strcpy(*p, "Kenneth Kuan");
*p = Kenneth Kuan

char *p1;
p1 = (char*) malloc(sizeof(char)*20);
sizeof(p1) = 8
sizeof(*p1) = 1
sizeof(p1[0]) = 1
sizeof(p1[1]) = 1

char p2[20];
sizeof(p2) = 20
sizeof(*p2) = 1

char *p3[20];
sizeof(p3) = 160
sizeof(*p3) = 8
sizeof(p3[0]) = 8


每個函式名稱都是指向該函式的指標,當你建立名為 go_to_warp_speed(int speed)的函式時,你同時也建立了稱作 go_to_wrap_speed的指標變數。

建立函式指標:
int (*warp_fn)(int);
warp_fn = go_to_warp_speed;
warp_fn(4);

warp_fn(4); 跟呼叫 go_to_warp_speed(4)一樣。
在建立函式指標時,記得要指定回傳值型別和接受的參數。


enum response_type {DUMP, SECOND_CHANCE, MARRIAGE);
typedef struct {
    char *name;
    enum response_type type;
}

建立函式指標陣列:
void (*replies[])(response) = {dump, second_chance, marriage};

replies[DUMP] == dump
replies[DUMP]等同於dump函式的名稱

2014年6月25日 星期三

Linux Driver 開發概述

Device Driver 簡稱 Driver
裝置驅動程式用來將硬體本身的功能告訴作業系統,可視驅動程式爲硬體的靈魂,同時也可稱驅動程式為硬體和系統之間的橋樑。

裝置驅動程式可分為下列三類:
1. 字元裝置
    一個位元組(Byte)一個位元組讀取資料的裝置,一般會在驅動層實現 open(), close(), read(), write() and ioctl()等函數。
2. 區塊裝置
    一般是像磁碟一樣的裝置,進行讀寫時,每次只能傳輸一個或多個區塊,但是Linux可以讓應用程式像存取字元裝置一樣的存取區塊裝置。
3. 網路裝置
    主要是面對資料封包的接收和發送而設計的,網路裝置在Linux中是一種很特殊的裝置,不像字元裝置與區塊裝置的API函數,而是實現了一種通訊端界面,任何網路資料傳輸都可以透過通訊端來完成。

CPU有兩種指令:
1. 特權指令:Kernel mode可使用
2. 普通指令:Kernel mode and User mode可使用

模組是可以在執行時加入核心的程式,Linux kernel支援很多種模組,驅動程式就是其中最重要的一種,甚至檔案系統也可以寫成一個模組。利用insmod指令將模組加入正在執行的核心,也可用rmmod指令將一個未使用的模組從核心中刪除。

載入模組有兩種類別:
1. 靜態載入:模組在核心啟動時載入
2. 動態載入:在核心已經執行時載入

Note for C

printf
%p 被用來格式化位址 (16進位, e.g., 0x3E8FA0)
====================================================================
指標有時候會被稱為 reference(參考), *運算子可用來 dereference(解參考)指標.


struct, union, enum 這三者要一起看

typedef struct cell_phone{

...
...
...
} phone;
不需要像上面這種寫法,可以略過struct的名稱,直接定義別名(alias),如下面的寫法。
typedef struct {
...
...
...
} phone;
依照上述例子 phone 是別名
phone *myPhone;
//(*myPhone).xxx == myPhone->xxx
//上述兩式相等
struct結構中可以包含指向另一個struct的指標,但是struct本身不可以再包含完整的遞迴struct。因為C語言必須知道struct確切占據多少記憶體空間。


union的用法與struct相同,但是union只會針對它所定義的欄位之一配置記憶體空間,電腦將提供該union足以存放其最大欄位的空間。
typedef union {
    short count;
    float weight;
    float volume;
} quantity;
quantity q = {.weight=1.5};
上述初始化等同於,下面這種寫法
quantity q;
q.weight = 1.5;


enum讓你可以列舉出一序列的符號,像下面這樣:
enum color {RED, GREEN, PUCE};


bitfield可以讓我們儲存自訂數目的位元,並且應該被宣告為unsigned int。
例如:
typedef struct {
    unsigned int first_visit:1; //表示該欄位只占用一個位元(1bit)
    unsigned int come_again:1;
    unsigned int figner_lost:4;
    unsigned int shark_attack:1;
    unsigned int days_a_week:3; //表示該欄位占用三個位元(3bit)
} survey;


配置與釋放必須一對一
從heap配置動態記憶體 void *p = malloc(sizeof(int)*4);
釋放記憶體 free(p);

如何配置動態二維陣列?
如欲配置一[m][n]的二維陣列
有下列幾種作法
(ㄧ)
int **Array, *pData; 
int m,n,i; 
Array = (int**)malloc(m*sizeof(int *)); 
pData = (int*)malloc(m*n*sizeof(int)); 
for(i = 0; i < m; i++, pData += n)
Array[i] = pData;
只需做兩次malloc,free只要free Array和Array[0]就可以了
(二)
int i;
int **Array;
Array = (int **)malloc(m*sizeof(void *));
for (i = 0; i < m; i++)
Array = (int *)malloc(n*sizeof(int *));
這樣子的配置方式要做很多次的malloc,,並容易造成記憶體碎片化(memory fragment)
(三)
int i;
int **Array, *pData;
Array = (int **)malloc(m*sizeof(int *)+m*n*sizeof(int));
for (i = 0, pData = (int *)(Array+m); i < m; i++, pData += n)
Array[i]=pData;

這樣是最簡便的寫法 只要mallocㄧ次完成,free只要free Array即可

Ref: http://chiakie.pixnet.net/blog/post/3143518-%5Bc%5D-%E5%A6%82%E4%BD%95%E5%8B%95%E6%85%8B%E9%85%8D%E7%BD%AE%E4%BA%8C%E7%B6%AD%E9%99%A3%E5%88%97

GCC make 編譯 C 程式

gcc -c *.c
-c 告訴編譯器你想要為每個原始檔建造目的檔,但是沒有將之連結產生完整的可執行程式

gcc *.o -o a.out
將目的檔連結在一起,並且產生完整的可執行程式(a.out)

make所編譯的每個檔案被稱作 target
針對每個target,make必須被告知兩件事情:
1. dependencies (依存項目): target 將從哪些檔案產生
2. recipe (執行指令): 產生檔案所需要執行的一組指令

關於 target, dependencies and recipe的所有細節都必須被儲存在一個稱為 makefile or Makefile的檔案中。

launch.o: launch.c launch.h thruster.h
        gcc -c launch.c

綠色的部分為target
藍色的部分為dependencies
紅色的部分為recipe
紫色的部分為TAB,所有的recipe必須以TAB字元開頭

進階一點的話可以參考更自動化的工具 autoconf
Ref: https://www.gnu.org/software/autoconf/
Example: http://www.ibm.com/developerworks/cn/linux/l-makefile/