几种难度比较大的C宏定义

在Linux内核、嵌入式代码等传统的C代码里,会有一些难以识别的宏定义。我记得在eCos, UBoot, FFmpeg有一些比较BT的宏定义,很难读懂。对于C++程序员来说,最好将这种难读的宏定义转成inline函数或模板函数。本章对这些较难的重定义进行汇总。


1.  在宏定义中指定义类型参数

    #define FPOS_TO_VAR(fpos, typed, var)                    (var) = (typed)((fpos).__pos)
    #define VAR_TO_FPOS(fpos, var)                          (fpos).__pos = (var)

此句在faac的代码里可以见到,其特殊之处是以类型名作为参数,而且是用宏定义一行赋值语句。像如下的调用:

    uint64_t  ret;
    FPOS_TO_VAR(fpos,  uint64_t, ret);

相当于

    uint64_t ret;
    ret = (uint64_t) (fpos.__pos);

一般可以对宏定义作换行显示,换行后会更清晰易懂一些:

    #define FPOS_TO_VAR(fpos, typed, var)     \   
                    (var) = (typed)((fpos).__pos)


2. 宏定义取结构偏移

这种用法似乎在uCosII中用到,记不清了。举例来说,我们知道一个struct(类型为T)中有一个变量为v,那么由v的地址来得到T的指针呢?

#define S_OFFSET(T,v)      (&((T*)0)->v)

struct ABC
{
    int a;
    char b;
    int c;
};

int main()
{
    unsigned int offset = (unsigned int) S_OFFSET(ABC, b);
    return 0;
}

主要是分析一下(&((T*)0)->v)是个什么意思,相当于以下几种代码:
T* p = (T*) 0;  // 把0地址强转为一个T*
void* offset = &p->v; // 由于变量p的地址为0,所以成员变量v的偏移就是其地址
得到上述偏移之后怎么用呢?在函数传递的过程中,只需要一个v的指针,就可以反推出其所在的结构的地址。而offset一般不方便指定为一个固定值,因为不同环境下的padding可能不一致,也不方便扩展。

void* p = ...;
T* t = (T*) (p - offset);

3. 宏定义中的连接符##

这也是一种比较特殊的用法,在eCos3中经常用到。用于替换一个完整名字(类型名、函数、变量名) 的一部分。以下再种写法等效,请仔细体会:

my_type_t   var1;
my_type_t   var2;

struct my_type_t
{
	int a;
	char b;
};

#define    MY_TYPE(T,  name)       \
	T  var##name

MY_TYPE(my_type_t, 1);
MY_TYPE(my_type_t, 2);

int main()
{
	unsigned int offset = (unsigned int) S_OFFSET(my_type_t, b);
	var1.a = 10;
}

4. 宏定义中的字符串#

#define  MYSTR(value)   #value
int main()
{
	char* cc = MYSTR(hello);
}
相当于

char* cc = "hello";


【待续】


你可能感兴趣的:(c,嵌入式,BT,linux内核)