C语言-预处理(2)

C·语法 2019-04-20 5158 字 1299 浏览 点赞

起因

之前对预处理、宏定义、条件编译,以及文件包含做了个小小总结。涉及而又常用的预处理相关知识还有一些,这次仍然粗略记下。

预处理操作符

C 语言中有两个预处理操作符:#(字符串化运算符)、##(记号粘贴运算符),都可以在 #define 中使用。

字符串化运算符

“#” 的操作数必须是宏替换文本中的形参。其作用是:用双引号 将这个形参对应到的实参 包起来。来一个简单的示例:

#include <stdio.h>
#define GREET(name) ("Hello " #name)

int main()
{
    printf("%s\n", GREET(Guan));
}
// 输出:
Hello Guan

尤其注意,这里的 Guan 没做定义或申明,但可以直接使用。再来看个列子:

#include <stdio.h>
#define GREET(name) ("Hello " #name)

int main()
{
    char* Guan = "Zhong";  // Guan 是一个 char* 的指针
    puts(Guan);
    printf("%s\n", GREET(Guan));
}
// 输出:
Zhong
Hello Guan

也就是说,操作符 # 不管操作数是不是变量,总之直接将它字符串化,而且是将操作数的表面字符串化。我们再看看上述代码预处理之后的结果:

// example.i
...
int main()
{
    char* Guan = "Zhong";
    puts(Guan);
    printf("%s\n", ("Hello " "Guan"));  
    // 编译器会自动合并紧邻的字符串 =》 "Hello Guan"
}

这样做有什么好处吗?那必然是有的,至少可以简化代码。像下面这样:

#include <stdio.h>
#define PRINT(fmt, value) printf("Value of " #value " is " fmt "\n", value)

int main()
{
    int num = 100;
    PRINT("%d", num);  // 使用 # 操作符

    printf("Value of num is %d\n", num);  // 不使用操作符
}
// 输出:
Value of num is 100
Value of num is 100

记号粘贴运算符

“##” 的作用是把左右操作数结合起来,作为一个记号。你需要确保这个合成的记号在之前已经存在,可以是宏,也可以是变量。

#define TEXT_A "hello world"
#define msg (TEXT_ ## A)

int main()
{
    printf("%s\n", msg);  // 输出:hello world
}

其实就是 “TEXT_ ## A => TEXT_A” ,预处理器发现:欸哥们(TEXT_A)在呀!于是又把宏 TEXT_A 的文本拿来替换。倘若 TEXT_A 不存在,编译器会报错。

test.c:106:14: error: ‘TEXT_A’ undeclared (first use in this function)
 #define msg (TEXT_ ## A)
              ^
test.c:110:20: note: in expansion of macro ‘msg’
     printf("%s\n", msg);
                    ^
test.c:106:14: note: each undeclared identifier is reported only once for each function it appears in
 #define msg (TEXT_ ## A)
              ^
test.c:110:20: note: in expansion of macro ‘msg’
     printf("%s\n", msg);
                    ^

我们来看看预处理器处理后的结果:

// example.i
...
int main()
{
    printf("%s\n", (TEXT_A));
}

显然,预处理器已经尽力了,只是它无力回天,毕竟代码中 TEXT_A 什么也不是。但从预处理后的结果来看,如果 TEXT_A 是个变量,似乎程序就可以执行下去。

#define msg (TEXT_ ## A)

int main()
{
    char* TEXT_A = "hello world";
    printf("%s\n", msg);  // 输出:hello world
}

另一个需要注意的是,操作符 ## 左右两边的操作数不能是宏,但可以是形参。

// 如果是 宏
// example.c
#define TEXT_ 123
#define A 4
#define msg (TEXT_ ## A)

int main()
{
    printf("%s\n", msg);
}
/**************************/
// example.i
...
int main()
{
    printf("%s\n", (TEXT_A));  // TEXT_ 和 A 并没有被前边的宏替换
}
// 如果是 形参
// example.c
#define msg(TEXT_, A) (TEXT_ ## A)

int main()
{
    printf("%s\n", msg(123, 4));
}
/**************************/
// example.i
...
int main()
{
    printf("%s\n", (1234));  // TEXT_ 和 A 被实参替换
}

同操作符 “#” 一样,“##” 也不能转换 C 代码中的变量。其实这是有道理的,预处理只做预处理该做的事,不会对代码做分析。所以预处理阶段并不知道谁是变量、谁不是变量。

// example.c
#define msg(TEXT_, A) (TEXT_ ## A)

int main()
{
    char* a = "123";
    char* b = "4";
    printf("%s\n", msg(a, b));
}
/**************************/
// example.i
int main()
{
    char* a = "123";
    char* b = "4";
    printf("%s\n", (ab));  // 而不是 "1234"
}

预定义宏

一些常用的预定义宏,在代码中可以直接使用。

__FILE__  源文件名
__LINE__  文件当前行
__DATE__  文件被编译日期(Mmm dd yyyy)
__TIME__  文件被编译时间(hh:mm:ss)
__func__  当前所在函数名

示例:

#include <stdio.h>

#define LOG(fmt, ...) printf("%s %s | %s %s %d: " fmt "\n", \
                             __DATE__, __TIME__, \
                             __FILE__, __func__, \
                             __LINE__, __VA_ARGS__)

void sum(int a, int b)
{
    int result = a + b;
    if (result >= 10) {
        LOG("%s", "超出限定值,最后结果必须小于10");
    }
}

int main()
{
    sum(11, 9);
}
// 输出:
Apr 20 2019 16:02:44 | example.c sum 12: 超出限定值,最后结果必须小于10

当然,预定义宏不止这些。

其他指令

一些常见的其他预处理指令。

error

#ifndef HELLOW
    #error "请先定义宏 HELLOW"
#endif

int main()
{
    ;
}

预处理时出错:

Guan@Orchard:~/CC++/19/src/demo$ gcc -E -o i example.c 
example.c:3:6: error: #error "请先定义宏 HELLOW"
     #error "请先定义宏 HELLOW"
      ^

pragma

#pragma 可以做一些 note 这样的提示信息,但考虑到不同机器对它有不同的兼容性,建议少用。

#ifndef HELLOW
    #pragma message("你是不是忘记定义宏 HELLOW 了")
#endif

int main()
{
    ;
}

预处理时没有信息,但编译时会有提示:

Guan@Orchard:~/CC++/19/src/demo$ gcc example.c 
example.c:3:13: note: #pragma message: 你是不是忘记定义宏 HELLOW 了
     #pragma message("你是不是忘记定义宏 HELLOW 了")
             ^

#error 最大的区别是,#pragma 提示之后仍可以生成可执行文件,但 #error 不可以。

line

#line 可以改变默认的行号和文件名。其语法格式为:#line line_number filenamefilename 可以省略,但 line_number 不行。

示例:

#include <stdio.h>

int main()
{
    printf("当前行号:%d\n", __LINE__);
#line 1  // 改变下一行代码的行号
    printf("当前行号:%d\n", __LINE__);

    printf("当前行号:%s\n", __FILE__);
#line __LINE__ "my.c"
    printf("当前行号:%s\n", __FILE__);
}
// 输出:
当前行号:5
当前行号:1
当前行号:example.c
当前行号:my.c

感谢

  • 参考《C 语言核心技术(第 2 版)》
  • 参考 海同网校王正平老师的 C 语言之预处理


本文由 Guan 创作,采用 知识共享署名 3.0,可自由转载、引用,但需署名作者且注明文章出处。

还不快抢沙发

添加新评论