博客首页 注册 建议与交流 排行榜 加入友情链接         宝宝相册的专门空间
推荐 投诉 搜索: 帮助

Free Gentux

Free & Open
   xiaosuo.cublog.cn
关于作者  
姓名:xiaosuo
职业:Linuxer
年龄:Not very young
位置:TianJin
个性介绍:Free & Open.

我的分类  




宏中定义变量要小心
在缺乏引用支持的C语言中,有的时候定义宏几乎成了唯一的选择,虽然有人认为引用代表了一种不怎么明确的语义:不知道参数是否可能会更改,但是如果引用施加于一个语义明确的对象,比如流,那么一切也就无可厚非了。

请看下面一段代码:


#include <stdint.h>
#include <inttypes.h>
#include <stdio.h>

#define get_u16(addr) ({ uint16_t **p = (uint16_t**)&(addr); *(*p)++; })

int main(int argc, char *argv[])
{
        uint16_t a[] = {12, 13};
        char *ptr = (char*)a;

        printf("% " PRIu16 "\n", get_u16(ptr));
        printf("% " PRIu16 "\n", get_u16(ptr));

        return 0;
}


get_u16的参数被当成了字节流的地址,每次调用它除了返回一个16比特的无符号整形外,数据流也会做相应的改变,可以认为get_u16吃掉并返回了16比特的无符号整形。上面代码运行正常:

gentux ~ # ./a.out
12
13

稍作改变,将ptr改成p,编译后运行:

xiaosuo@gentux ~ $ ./a.out
56984
56976

问题出现了,怎么会是这个结果呢?为了一探究竟我们不妨用cpp将宏展开来看:

int main(int argc, char *argv[])
{
 uint16_t a[] = {12, 13};
 char *p = (char*)a;

 printf("% " "u" "\n", ({ uint16_t **p = (uint16_t**)&(p); *(*p)++; }));
 printf("% " "u" "\n", ({ uint16_t **p = (uint16_t**)&(p); *(*p)++; }));

 return 0;
}


不难发现,本来应该是宏参数的变量被宏内部的变量覆盖了,根据C对作用域的定义,在宏内部用到的就是自己的变量,实际上我们应该庆幸,上面的代码没有断错误退出,多少给我们留足了面子。

问题的原因找到了,如何解呢?恕在下愚笨,着实没有找到完美的解决方法,只想到了借助编程惯例:除非是库,否则不应该定义以_开始的变量,这样的话,get_u16可以这样定义:

#define get_u16(addr) ({ uint16_t **_p = (uint16_t**)&(addr); *(*_p)++; })


结论:

因为宏毕竟不是函数,所以在其内部定义变量的时候要格外小心,最好在变量名前加上_,以防止覆盖外部变量,造成意外。

 发表于: 2008-05-01,修改于: 2008-05-01 13:38 已浏览410次,有评论5条 推荐 投诉

  网友评论
  BenBear 时间:2008-05-02 19:58:59 IP地址:219.136.152.★
很好。但是……第一段代码中的
char *ptr = (char*)a;
是否应该是
char *p = (char*)a;

  xiaosuo 时间:2008-05-02 21:29:36 IP地址:218.69.98.★
没错,就是ptr

  BenBear 时间:2008-05-04 01:16:57 IP地址:219.137.107.★
呃……不好意思,我看漏了一段话了……

  mymtom 时间:2008-05-06 22:04:51 IP地址:125.88.4.★
这种宏定义本来就是GNU扩展才支持的.
不建议使用,除非只准备用gcc而且对GNU扩展很熟悉.

  xiaosuo 时间:2008-05-06 22:46:57 IP地址:218.69.98.★
被楼上发现了,呵呵。其实,Linux内核对GNU扩展的依赖很强,并且除了GCC,Intel的ICC也支持GNU扩展,在开源的世界里,我们几乎可以假设编译器支持GCC扩展。
上面的代码,如果想避开GCC扩展,还可以这样定义:

#define def_get_type(type) \
inline type __get_##type(void *addr) \
{ \
        type **p = addr; \
        return *(*p)++; \
}

def_get_type(uint16_t);
#define get_u16(x) __get_uint16_t(&(x))


  发表评论



Copyright © 2001-2006 ChinaUnix.net All Rights Reserved

感谢所有关心和支持过ChinaUnix的朋友们
页面生成时间:1.78452

京ICP证041476号