Linux下的C编程

1. 命名习惯

#define PI 3.1415926
int min_value, max_value;
void send_data(void);

变量名,函数名全都小写,单词之间用“_”连接。


2. Case范围

GNU C支持case x...y这样的语法,区间[x,y]的数都会满足这个case的条件,例如:

swith(ch)
{
    case '0'...'9': c -= '0';
    break;

    case 'a'...'f': c -= 'a' -10;
    break;
}

代码中的case '0'...'9'等价于标准C中的如下代码:

case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':

3. 语句表达式

GNU C把包含在括号中的复合语句看作是一个表达式,称为语句表达式,它可以出现在任何允许表达式的地方。我们可以在语句表达式中使用原本只能在符合语句中使用的循环变量、局部变量等,例如:

#define min_t(type,x,y) \
({ type _ _x = (x); type _ _y = (y); _ _x < _ _y ? _ _x: _ _y; })
int ia, ib, mini;
float fa, fb, minf;
mini = min_t(int, ia, ib);
minf = min_t(float, fa, fb);

因为重新定义了__x和__y这两个局部变量,所以以上述方式定义的宏不会有副作用。在标准C中,对应的如下宏则会产生副作用:

#define min(x,y) ((x) < (y) ? (x) : (y))

代码min(++ia, ++ib)会被展开为((++ia)<(++ib)?(++ia):(++ib)), 传入宏的参数被增加了两次。


4. typeof关键字
typeof(x)语句可以获得x的类型,因此,我们可以借助typeof重新定义min这个宏:
#define min(x,y) ({ \
const typeof(x) _x = (x); \
const typeof(y) _y = (y); \
(void) (&_x == &_y); \
_x < _y ? _x : _y; })
我们不需要像min_t(type,x,y)这个宏那样把type传入,因为通过typeof(x)、typeof(y)可以获得type。代码行(void)(&_x==&_y)的作用是检查_x和_y的类型是否一致。

5. 可变参数的宏

标准C只支持可变参数的函数,意味着函数的参数是不固定的,例如printf()函数的原型为:

int printf( const char *format [, argument]... );
而在GNU C中,红叶可以接受可变数目的参数,例如:

#define pr_debug(fmt,arg...) \
printk(fmt,##arg)
这里arg表示其余的参数可以是零个或多个,这些参数以及参数之间的逗号构成arg的值,在宏扩展时替换arg,例如下列代码:

pr_debug("%s:%d",filename,line)
会被扩展成为:

printk("%s:%d", filename, line)

使用##的原因是处理arg不代表任何参数的情况,这时候,前面的逗号就变得多余了。使用“##”之后,GNU C预处理器会丢弃前面的逗号。这样,代码:

pr_debug("success!\n")

会被正确的扩展为:

printk("success!\n")

而不是:

printk("success!\n",)

6. 当前函数名

GNU C预定义了两个标识符保存当前函数的名字,__FUNCTION__保存函数在源码中的名字,__PRETTY_FUNCTION__保存带语言特色的名字。在C函数中,这两个名字是相同的。

void example()
{
printf("This is function:%s", _ _FUNCTION_ _);
}

代码中的__FUNCTION__意味着字符串“example"。


7. do{} while(0)

在Linux内核中,经常会看到 do{} while(0)这样的语句,许多人开始都会疑惑,认为do{} while(0)毫无意义,因为它只会执行一次,加不加do{}while(0)效果完全一样的,其实do{}while(0)主要用于宏定义中。

这里用一个简单的宏来演示:

#define SAFE_FREE(p) do{ free(p); p = NULL;} while(0)

假设这里去掉do...while(0),即定义为:

#define SAFE_FREE(p) free(p); p = NULL;

那么以下代码:

if(NULL != p)
  SAFE_DELETE(p)
else
  ...//do something

会被展开为:

if(NULL != p)
  free(p); p = NULL;
else
  ...//do something

展开的代码存在两个问题:

(1)if分支后有两个语句,导致else分支没有对应的if,编译失败;

(2)假设没有else分支,则SAFE_FREE中的第二个语句无论if测试是否通过都会执行。

将SAFE_FREE定义加上{}就可以解决上述问题了,即:

#define SAFE_FREE(p) { free(p); p = NULL;}

这样,代码:

if(NULL != p)
  SAFE_DELETE(p)
else
  ...//do something

会被展开为:

if(NULL != p)
  { free(p); p = NULL; }
else
  ...//do something

但是,在C程序中,每个语句后面加一个分号是一种约定俗成的习惯,那么,如下代码:

if(NULL != p)
  SAFE_DELETE(p);
else
  ...//do something

将被扩展为:

<span style="font-size: 9pt;">if(NULL != p)
<span style="font-size: 9pt;">{ free(p); p = NULL; };
<span style="font-size: 9pt;">else
<span style="font-size: 9pt;">...//do something</span></span></span></span>
这样,else分支就又没有对应的if了,编译将无法通过。假设用了do{} while(0),情况就不一样了,同样的代码会被展开为:

if(NULL != p)
  do{ free(p); p = NULL;} while(0);
else
  ...//do something
不会再出现编译问题。 do{}while(0)的使用完全就是为了保证宏定义的使用者能无编译错误的使用宏,它不对其使用者做任何假设。




参考资料:《linux设备驱动开发详解》 宋宝华


你可能感兴趣的:(编程,c,嵌入式,linux驱动)