奇怪的方式c语言,奇怪的C语言写法

C语言是一门博大精深的语言,有些高手灵活应用写出的代码使得我们有些菜鸟很难看得明白,这是对一些C语言的奇怪写法进行收录介绍:

一、do{}while(0)

linux内核和其他一些开源的代码中,经常会遇到这样的代码:

do{

...

}while(0)

这样的代码一看就不是一个循环,do..while表面上在这里一点意义都没有,那么为什么要这么用呢?

实际上,do{...}while(0)的作用远大于美化你的代码。查了些资料,总结起来这样写主要有以下几点好处:

1、辅助定义复杂的宏,避免引用的时候出错:

举例来说,假设你需要定义这样一个宏:

#define DOSOMETHING()\

foo1();\

foo2();

这个宏的本意是,当调用DOSOMETHING()时,函数foo1()和foo2()都会被调用。但是如果你在调用的时候这么写:

if(a>0)

DOSOMETHING();

因为宏在预处理的时候会直接被展开,你实际上写的代码是这个样子的:

if(a>0)

foo1();

foo2();

这就出现了问题,因为无论a是否大于0,foo2()都会被执行,导致程序出错。

那么仅仅使用{}将foo1()和foo2()包起来行么?

我们在写代码的时候都习惯在语句右面加上分号,如果在宏中使用{},代码里就相当于这样写了:“{...};”,展开后就是这个样子:

if(a>0)

{

foo1();

foo2();

};

这样甚至不会编译通过。所以,很多人才采用了do{...}while(0);

#define DOSOMETHING() \

do{ \

foo1();\

foo2();\

}while(0)\

...

if(a>0)

DOSOMETHING();

...

这样,宏被展开后,才会保留初始的语义。GCC提供了Statement-Expressions用以替代do{...}while(0); 所以你也可以这样定义宏:

#define DOSOMETHING() ({\

foo1(); \

foo2(); \

})

http://www.spongeliu.com/

2、避免使用goto对程序流进行统一的控制:

有些函数中,在函数return之前我们经常会进行一些收尾的工作,比如free掉一块函数开始malloc的内存,goto一直都是一个比较简便的方法:

int foo()

{

somestruct* ptr = malloc(...);

dosomething...;

if(error)

{

goto END;

}

dosomething...;

if(error)

{

goto END;

}

dosomething...;

END:

free(ptr);

return 0;

}

由于goto不符合软件工程的结构化,而且有可能使得代码难懂,所以很多人都不倡导使用,那这个时候就可以用do{}while(0)来进行统一的管理:

int foo()

{

somestruct* ptr = malloc(...);

do{

dosomething...;

if(error)

{

break;

}

dosomething...;

if(error)

{

break;

}

dosomething...;

}while(0);

free(ptr);

return 0;

}

这里将函数主体使用do()while(0)包含起来,使用break来代替goto,后续的处理工作在while之后,就能够达到同样的效果。

3、避免空宏引起的warning

内核中由于不同架构的限制,很多时候会用到空宏,在编译的时候,空宏会给出warning,为了避免这样的warning,就可以使用do{}while(0)来定义空宏:

#define EMPTYMICRO do{}while(0)

4、定义一个单独的函数块来实现复杂的操作:

当你的功能很复杂,变量很多你又不愿意增加一个函数的时候,使用do{}while(0);,将你的代码写在里面,里面可以定义变量而不用考虑变量名会同函数之前或者之后的重复。

二、(void)(&__x == &__y);

在GCC的文档中建议使用如下的min宏定义:

#define min(X,Y)  \

(__extension__  \

({  \

typeof(X) __x=(X), __y=(Y);   \

(void)(&__x == &__y);\

(__x<__y>

}) \

)

本文讨论了这样作法的意义。

1、传统的min带来的副作用

2、GCC中的({statement list})的扩展

3、typeof(expression)

4、__extension__的含义

5、使用typeof和({})实现min,避免了副作用

附录1、旧版本的的GCC中的的解决方法

附录2、C++中使用template的解决方法

1、传统的min带来的副作用

min通常被定义成这样的宏:

#define min(X,Y) ((X) < (Y) ? (X) : (Y))

复制代码

这种定义会带来一些副作用,看下面的例子:

int x = 1, y = 2;

int main()

{

printf("min=%d\n", min(x++, y++));

printf("x = %d, y = %d\n", x, y);

}

复制代码

执行完min(x++、y++),我们期望x的值为2,y的值为3。

但是,实际的结果是,执行完mini(x++、y++)后,x的值为3,y的值为3,原因在于宏展开后x++被执行了两次:

int x = 1, y = 2;;

int main()

{

printf("min=%d\n", x++ < y++ ? x++ : y++);

printf("x = %d, y = %d\n", x, y);

}

2、GCC中的({statement list})的扩展

({statement list})是一个表达式,逗号表达式类似,但是功能更强,({与})中可以包含有多条语句(可以是变量定义、复杂的控制语句),该表达式的值为statement list中最后一条语句的值,举例:

int main()

{

int result = ({

int i, sum = 0;

for (i = 1; i <= 100; i++)

sum+= i;

sum;

})

printf("result=%d\n", result);

}

运行结果:

result=5050

复制代码

3、typeof(expression)

typeof(expression)用来获取expression的类型,举例:

int main()

{

int integer;

typeof(100) i;  /* 表达式100的类型是int,定义了int型变量i */

typeof(integer) j; /* 表达式integer的类型是int,定义了int型变量j */

i = 1;

j = 2;

}

复制代码

4、__extension__的含义

GCC引入了很多标准C中的没有的扩展,如({和)},GCC提供了pednatic选项用于检测程序是否使用了GCC的扩展,当使用pedantic选项编译如下程序时

int main()

{

int result = ({

int i, sum = 0;

for (i = 1; i <= 100; i++)

sum+= i;

sum;

})

printf("result=%d\n", result);

}

复制代码

编译器发出警告:

$ cc -pedantic test.c

test.c: 在函数 ‘main’ 中:

test.c:9: 警告:ISO C 不允许在表达式中使用花括号组

复制代码

编译器提醒程序员,这段C程序使用了不符合ISO C标准的语法,如果使用其他的编译器(非GCC)编译这段代码有能会出错。在所有使用GNU 扩展关键字的表达式之前加__extension__ 关键字后,使用pedantic选项编译时,编译器就不再发出警告信息:

int main()

{

int result = __extension__({

int i, sum = 0;

for (i = 1; i <= 100; i++)

sum+= i;

sum;

})

printf("result=%d\n", result);

}

$ cc -pedantic test.c

$ 编译成功!

复制代码

5、使用typeof和({})实现min,避免了副作用

#define min(X,Y) \

({  \

typeof(X) __x=(X), __y=(Y);  \

(__x<__y>

})

复制代码

使用传统的min会出现问题的例子:

int x = 1, y = 2;;

int main()

{

printf("min=%d\n", min(x++, y++));

printf("x = %d, y = %d\n", x, y);

}

复制代码

它被扩展为

int x = 1, y = 2;;

int main()

{

printf("min=%d\n",

({

typeof(x) __x = (x++), __y = (y++);  /* 定义了两个整形变量 */

(void)(&__x == &__y);/*这样当类型不兼容时会有警告。尤其有符号和无符号的比较,这很重要。*/

(__x<__y>

})

);

printf("x = %d, y = %d\n", x, y);

}

在执行min(x++, y++)期间,x++和y++只执行了一次,因而结果是正确的。

三 、#define pr_debug(fmt,arg...) \

标准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)四、GCC下 特殊的数组声明

struct ast_app_option {

unsigned int flag;

unsigned int arg_index;

};

static const struct ast_app_option read_app_options[128] = {

['s'] = { .flag = OPT_SKIP },

['i'] = { .flag = OPT_INDICATION },

['n'] = { .flag = OPT_NOANSWER },

}

这段代码目的很灵巧,就是建立一个用字母做数组index的128位数数组,而且结构体的赋值直接用了{.flag=xxxxx}的简单方法,这是linux c特有的写法。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值