其它的一些“预处理”关键字

  • #error
  • defined
  • #line
  • #和##
  • #pragma

一、#error 关键字

我们先不忙着介绍#error,先看看如果宏定义不存在的话,如何报宏定义不存在的错误


文件:test.c

#include <stdio.h>

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

开始预编译

gcc -E test.c -o test.i

预编译后,打开 test.i 文件后,会发现因为没有定义PI宏,所以PI没有被替换

开始编译 test.i

gcc -s test.i -o test.s
test.c: In function ‘main’:
test.c:5:20: error: ‘PI’ undeclared (first use in this function)
     printf("%f\n", PI);
                    ^
test.c:5:20: note: each undeclared identifier is reported only once for each function it appears in

在编译阶段,就报错 error: ‘PI’ undeclared

由于PI这个宏没有定义,因此预编译阶段处理时,没办法对PI进行替换,
所以PI这个符号会一直留到编译阶段,第二阶段编译时就会报PI不存在的错误


但是由“编译阶段”来报宏的错误存在如下缺点:

1. 编译器无法精准报错

它只会提示缺少符号PI,但是并不能提示缺少的PI是一个宏,因为编译器只知道PI这个符号没有定义,
但是并不知道PI其实是一个宏,所以我们自己排错时,需要我们自己区分PI是个函数、变量、还是宏

总之,报错时如果能够告诉你符号PI是个宏的话,可以帮助我们快速排查PI这个宏相关的错误


2. 当源码很庞大时,编译时报错会非常迟缓

对于庞大的项目源码来说,往往可能有成百上千个.c文件,编译时间动辄就会花上30分~十几小时
某个引用“未定义宏”的.c,可能要等30分钟后才编译到它,然后再报符号未定义的错误

对于编译的四个过程来说,第二个阶段“编译”所花费的时间最久,预编译的时间最短,
所以如果在“预编译阶段”就报宏的错误,而且准确提示未定义的符号是一个宏的话,宏的排错效率更高

而且宏本来就是在“预编译阶段”处理的,所以在“预编译”阶段报宏的错误,也是最贴切的。


所以综合起来就是,宏没有定义的错误就应该在“预编译”阶段进行报告,而不是留到编译阶段

当然那些只能编译阶段报的错误,你就不能到预编译阶段报错了,比如某个变量、函数没有定义,
像这种的就只能在编译阶段报错了


二、使用 #error

1. 作用

在“预编译阶段”打印提示相关信息。可以打印任何你要的信息,具体什么信息,可以由你自己定义

例子:

#include <stdio.h>
#error helloworld  //注意helloworld不需要双引号

int main(void)
{
    return 0;
}

预编译到 #error 就报错了

$ gcc -E test.c -o test.i
test.c:2:2: error: #error this is error test
 #error this is error test
  ^~~~~

其实“干用#error”的意义并不大,前面就说过,#error常用于提示宏相关的错误,
比如举一个简单的例子:

#include <stdio.h>

int main(void) {
#ifdef PI
    printf("%d\n", PI);
#else
# error PI not defined
#endif
    return 0;
}

预编译报错

$ gcc -E test.c -o test.i
test.c:7:3: error: #error PI not defined
 # error PI not defined
   ^~~~~

2. 特点

  • #error 是在“预编译阶段”由预编译器处理的“预编译关键字”
  • 执行#error后,“预编译”的处理过程会被立即终止
  • #error输出字符串时,信息内容不需要使用双引号(””)括起来

3. #error 提示宏不存在时怎么办

1)如果该宏定义在了某个.h
我们只需要将该.h包含到.c中,该宏就有了

疑问:我怎么知道这个宏在哪个头文件呢?

如果是常用宏,一般我们自己都知道这个宏定义在了哪个.h中,比如stdin这个宏就定义在了stdio.h

但是在移植官方源码时,源码会非常复杂,涉及到的.h会非常多,很难搞清楚在哪个.h中,
不过源码除了会使用#error报某个宏未定义外,还会精确的提示你这个宏在哪个.h,你应该包含这个.h

举一个简单例子演示下。

#ifndef stdin
#error macro stdin not defined, please include stdio.h

#endif

int main(void)
{
    fprintf(stdin, "hello world\n"); //等价于printf("hello world\n");
    return 0;
}

这个例子很傻瓜,没有太大的现实意义,但是在复杂的C工程源码中,这种类似的做法确实非常常见的

很多同学在编译某些源码时,经常看到“你需要包含什么头文件”的提示,就是使用#error在预编译阶段提示的

2)如果宏没有定义在某个头文件中呢,怎么办呢?
此时可能就需要我们在某个.h或者.c亲自去定义了

1)例子1

#include <stdio.h>

int main(void)
{

#ifdef PI 
    printf("%d\n", PI); 

#else 
# error PI not defined 
#endif
retutn 0;
}

直接在.c头上、或者在自己的某个.h中,自定义PI#define PI 3.1415

2)例子2

对于从官方下载的复杂c源码来说,好些宏是与条件编译相关,由于源码太过复杂,而且又不是你自己写的,
如果你想通过阅读源码,然后再源码中去定义缺失的宏,这种方式几乎是不可能实现的

所以在复杂的C源码中,如果是与条件编译相关的宏,我们需要去修改配置文件,已得到相应的宏