首页 /  技术专区  /  C 宽屏模式 >

预编译关键字——#、##实际用法

1、#和##到底有什么实际意义 —————— 看看#和##的真实案例

我从Linux内核中抽取了一个真实的案例,在这个案例中#和##都用到了,通过这个例子看看#和##到底有什么使用价值。

(1)没有#和##时的正常做法

#define QDSP_MODULE_AUDPPTASK 1
#define QDSP_MODULE_AUDRECTASK2
#define QDSP_MODULE_UDPREPROCTASK 3

struct adsp_module_info 
{
    const char *name;
    const char *pdev_name;
    uint32_t id;
};

struct adsp_module_info module_info[] = 
{
    {.name="AUDPPTASK", .pdev_name=adsp_AUDPPTASK, .id=QDSP_MODULE_AUDPPTASK},
    {.name="AUDRECTASK", .pdev_name=adsp_AUDRECTASK, .id=QDSP_MODULE_AUDRECTASK},
    {.name="UDPREPROCTASK", .pdev_name=adsp_UDPREPROCTASK, .id=QDSP_MODULE_UDPREPROCTASK}
};

从这个例子中可以看出,给结构体数组初始化的值的名字很有规律,比如。

"AUDPPTASK"

adsp_AUDPPTASK

QDSP_MODULE_AUDPPTASK_1

基于这个规律完全可以使用#和##处理,在Linux内核中确实也是这么做的。

(2)使用#和##处理后

#define QDSP_MODULE_AUDPPTASK_1 1
#define QDSP_MODULE_AUDRECTASK_22
#define QDSP_MODULE_AUDRECTASK_3 3
#define QDSP_MODULE(n) { .name = #n, .pdev_name = "adsp_" #n, .id = QDSP_MODULE_##n }

struct adsp_module_info {
    const char *name;
    const char *pdev_name;
    uint32_t id;
};

struct adsp_module_info module_info[] = 
{
    QDSP_MODULE(AUDPPTASK),
    QDSP_MODULE(AUDRECTASK),
    QDSP_MODULE(UDPREPROCTASK)
};

使用#和##修改后,其实代码的执行效率并没有发生变化,但是使用了#和##后,确使得源码更加的简洁,在Linux内核、框架的C/C++源码中,大量充斥着这种用法,希望通过这里的介绍后,大家不再陌生这样的用法。

预编译时处理的过程:

QDSP_MODULE(AUDPPTASK) 

            V

{ .name = #AUDPPTASK, .pdev_name = "adsp_" #AUDPPTASK, .id = QDSP_MODULE_##AUDPPTASK }

            V

{ .name = "AUDPPTASK", .pdev_name = "adsp_" "AUDPPTASK", .id = QDSP_MODULE_AUDPPTASK }

QDSP_MODULE(AUDRECTASK)和QDSP_MODULE(UDPREPROCTASK)处理也是一样的过程。

查看预编译后的结果:

struct adsp_module_info {
    const char *name;
    const char *pdev_name;
    uint32_t id;
};

struct adsp_module_info module_info[] =
{
    { .name = "AUDPPTASK", .pdev_name = "adsp_""AUDPPTASK", .id = QDSP_MODULE_AUDPPTASK },
    { .name = "AUDRECTASK", .pdev_name = "adsp_""AUDRECTASK", .id = QDSP_MODULE_AUDRECTASK },
    { .name = "AUDRECTASK", .pdev_name = "adsp_""AUDRECTASK", .id = QDSP_MODULE_AUDRECTASK }
};


2、 #和##的一些需要注意的地方

(1)直接使用#和##行不行

#include <stdio.h>

int main(void)
{
    int helloworld = 100;
    printf("%s\n", #hello);
    printf("%d\n", hello##world);
    return 0;
}

查看预编译结果:

# 4 "helloworld.c"
int main(void)
{
    int helloworld = 100;
    printf("%s\n", #hello);
    printf("%d\n", hello##world);
    return 0;
}

从预编译的结果#hello、hello##world可以看出,#和##没有起到任何的作用。

从这里的例子可以看出,#和##只能和宏一起结合使用时,才能起到作用。

#include <stdio.h>
#define MACR1(s) #s
#define MACR2(a, b)  a##b

int main(void)
{
    int helloworld = 100;
    printf("%s\n", MACR1(hello));
    printf("%d\n", MACR2(hello, wolrd));
    return 0;
}

(2)无参宏行不行

#include <stdio.h>
#define MACRO1 #helloworld
#define MACRO2 hello##world

int main(void)
{
    MACRO1;
    MACRO2;
    return 0;
}

查看预处理后的结果:

...
...
# 8 "helloworld.c"
int main(void)
{
    #helloworld; //失败了
    helloworld;  //成功了
    return 0;
}

从这个例子可以看不出,#只对宏参数有效,但是##就不是,对于##来说,以下写法都是OK的。

#define MACRO(a, b) a##b
#define MACRO(a, b) a##b##_tag
#define MACRO info##_tag

(3)使用#和##时,如果宏的参数也是一个宏的话,会怎样

1)在没有#和##的情况下,如果宏的参数是另一个宏的话,会怎样

#include <stdio.h>
#define PI 3.14
#define AREA(r, p) (r)*(r)*(p)

int main(void)
{
    printf("%s\n", AREA(2,PI));
    return 0;
}

查看预编译结果:

...
...
# 8 "helloworld.c"
int main(void)
{
    printf("%d\n", (2)*(2)*(3.14));
    return 0;
}

在没有#和##的情况下,参数是宏时,“参数宏”也可以正常展开。

2)当有#和##时,如果宏参数是另一个宏的话,会怎样

(a)#的例子

#include <stdio.h>
#define NUM 100
#define STR(num) #num

int main(void)
{
    printf("%s\n", STR(NUM));
    return 0;
}

查看预编译结果:

int main(void)
{
    printf("%s\n", "NUM");  //参数宏NUM没有被展开,或者说替换为100
    return 0;
}

展开的过程:

STR(NUM) ———> #NUM ————> "NUM" 

为什么没有展开?

因为#是直接将NUM作为一个符号给处理了,你给它什么它就处理什么,它直接将得到玩意变为字符串,它并不会理会它是不是一个宏,是的话然后去展开它。

如果NUM想要展开的话,怎么办?

再加一层宏定义,先展开参数宏NUM,再展开有#的宏。

改进如下:

#include <stdio.h>
#define NUM 100
#define STR(num) #num
#define _STR(num) STR(num)

int main(void)
{
    printf("%s\n", _STR(NUM));
    return 0;
}

展开的过程:

 _STR(NUM) ——> STR(NUM) ——>STR(100) ——> #100 ———>"100" 

查看预编译结果:

int main(void)
{
    printf("%s\n", "100"); //展开成功
    return 0;
}

NUM展开后,#处理的符号就是100,所以就得到了字符串100。

(b)##的例子

#include <stdio.h>
#define TAG1 info
#define TAG2 _teacher
#define STRUCT(a, b) struct a##b

STRUCT(TAG1, TAG2)
{
    int num;
}

int main(void)
{
    return 0;
}

查看预编译后的结果:

struct TAG1TAG2
{
    int num;
}

int main(void)
{
    return 0;
}

展开的过程:

STRUCT(TAG1, TAG2)  struct TAG1##TAG2   struct TAG1TAG2 

——> struct TAG1##TAG2 ——> struct TAG1TAG2

为什么TAG1和TAG2没有展开?

原因与#一样,你给##什么符号,它就将什么符号连接起来,它不会去识别这个符号是个宏并展开它。

如果想将TAG1和TAG2展开怎么办?

同样的,在中间再加一层宏定义,将参数宏TAG1和TAG2展开后,在使用##连接起来。

#include <stdio.h>
#define TAG1 info
#define TAG2 _teacher
#define STRUCT(a, b) struct a##b
#define _STRUCT(a, b) STRUCT(a, b) //加的一层

_STRUCT(TAG1, TAG2)
{
    int num;
}

int main(void)
{
    return 0;
}

查看预编译后的结果:

struct info_teacher
{
    int num;
}

int main(void)
{
    return 0;
}

展开的过程:

_STRUCT(TAG1, TAG2) ——>STRUCT(TAG1, TAG2) ——> STRUCT(info, _teacher) ——>

——>struct info##_teacher ——> struct info_teacher

加一层宏的原理就是:

先将TAG1和TAG2展开为info, _teacher,然后再交给##,由##将info, _teacher连接在一起。

3、 在看一些#和##的使用例子

#相比##来说,用的不如##多,所以我们这里就不在举#的例子了,我们这里重点再举一些##的例子。

(1)##的例子1

1)定义结构体的例子

struct info_student 
{
    char name[30];
    int num;
};
typedef struct info_student student;

struct info_teacher
{
    char name[30];
    int id;
};
typedef info_teacher teacher;

struct info_administor
{
    char name[30];
    int id;
};
typedef struct info_administor administor;

比如像以上的这些例子,当需要定义好多结构体类型,而且这些结构体类型的格式还非常相似时,我们可以使用##来简化操作,让代码变的更简洁。

2)使用##简化后

#define STRUCT(type) typedef struct info_##type type; struct info_##type

STRUCT(student)
{
    char name[30];
    int num;
};

STRUCT(teacher)
{
    char name[30];
    int id;
};

STRUCT(administor)
{
    char name[30];
    int id;
};

int main(void)
{
    return 0;
}

查看预处理的结果:

...
# 6 "helloworld.c"
typedef struct info_student student;
struct info_student
{
    char name[30];
    int num;
};

typedef struct info_teacher teacher;
struct info_teacher
{
    char name[30];
    int id;
};

typedef struct info_administor administor;
struct info_administor
{
    char name[30];
    int id;
};

int main(void)
{
    return 0;
}

疑问:typedef进行类型命名的操作,在结构体类型定义的前面可以吗?

答:完全可以。

(2)module_init(mouse_device)

#define __define_initcall(level,fn,id) \
    static initcall_t __initcall_##fn##id __used \
    __attribute__((__section__(".initcall" level ".init"))) = fn

module_init宏最终对应的是__define_initcall宏,其中就有使用##,由于之前介绍过,这里就不在赘述。

4、 如何分析#和##

通过前面的例子可知,#和##是不能直接单独使用的,必须和宏结合在一起使用,所以分析#和##的过程,其实就是分析一个复杂宏定义的过程。

那么面对#和##的宏定义时,我们应该怎么办呢?

(1)如果通过宏名称就能明白该宏做了什么事情,此时完全不需要去分析这个宏的宏体

(2)如果不分析该宏就无法理解相关源码的话,那就必须分析该宏定义,如何分析呢?

        我们在前面就讲过,对于复杂宏定义的分析没有捷径可走,只能是一步一步的替换找到本源,只有找到本源后,才能知道该宏的真正用意是什么。


头像
0/200
图片验证码