器→工具, 术→技巧, 研发, 编程语言

C语言:预处理器与宏

钱魏Way · · 1,174 次浏览

编译一个C程序包含很多步骤,其中第一个步骤被称为预处理阶段。C预处理器在源代码编译之前对其进行一些文本性质的操作。它的主要任务包括删除注释、插入被#include指令包含的文件的内容、定义和替换由#define指令定义的符号以及确定代码的部分内容是否应该根据一些条件编译指令进行编译。

预处理指令的一些规则:

  • 指令都以#开始
  • 在指令的符号之间空间而已插入任意数量的空格或水平制表符
  • 指令总在第一个换行处结束,除非明确的指明要延续。如果想要在下一行延续指令,必须在当前行的末尾加上\符号
  • 指令可以出现在程序中的任何位置
  • 注释可以和指令放在同一行

宏定义

无参数的宏

常见形式:

#define 宏名称 替代文字

通常用来定义常量。另外替代文本也可以为空。比如常用于条件编译的

#define DEBUG

常见错误:

  • 宏定义中使用 =
  • 宏定义中的末尾使用分号结尾

带参数的宏

也被成为函数式宏,格式为:

#define 标识符(x1,x2,..) 替换列表

要求:

  • 宏的标识符与左括号间不能有空格,否则会被认定为无参数宏
  • 替代列表最外层用括号括起来,防止替代后出现歧义。
  • 为防止歧义,替代列表中的参数也要用括号括起来。

示例:

#define MAX(x,y) (((x) > (y))?(x):(y))
#define IS_EVEN(n) ((n)%2==0)

#运算符和##运算符

我们已经知道,预处理指令以#号开始,但是#号在宏定义中还有另外的作用:

  • #:将宏的一个参数转化成字符串字面量
  • ##:可以将2个记号(如标识符)粘合在一起,成为一个记号。

示例:

#include <stdio.h>

#define PRINT_INT(n) printf(#n " = %d\n", n)
#define MK_ID(n) i##n

int main() {
    int i, j;
    PRINT_INT(i / j); /* printf("i/j" " = %d\n", n) 8? */

    int MK_ID(1), MK_ID(2);  /* int n1, n2 */
    return 0;
}

取消定义

#undef 宏名称

文件包含

#include指令告诉预处理器打开一个特定的文件,将它的内容作为正在编译的文件的一部分包含进来。编译器支持2种不同类型的#include文件包含:库函数文件和本地文件。事实上他们的区别很小:

  • 库函数文件包含语法:#include <filename>
  • 本地文件包含语法:#include “filename”

规避方式是使用条件编译。

条件编译

条件编译的主要指令有:#if、#ifdef、#ifndef、#elif、#else、#endif。其中#ifdef等价于#if defined(标识符),#ifndef等价于#if !defined(标识符)

其他指令

#error指令

通常与条件编译一起,用于检测正常编译过程中不应出现的情况。遇到#error指令预示程序中出现了严重错误,通常编译器会立即停止编译。

#if INT_MAX < 100000
#error int type is to small
#endif

#line指令

  • 用来改变程序行编号。
  • #line n这条指令导致后续的编号为n、n+1、n+2…
  • #line n “文件”,指令后面的行会被认为来自文件,行号由n开始。
  • #line指令使用的场景非常少,不掌握也无所谓。

#pragma指令

为要求编译器执行某些特殊操作提供一种方法。这条指令对非常大的程序或需要使用特定编译器的特殊功能的程序非常有用。#pragma中出现的命令集在不同的编译器上是不同的(需要查看各自编译器文档)。如果#pragma包含了无法识别的命令,预处理器会忽视这些指令,并且不给出出错提示。

预定义的宏

C语言提供了一些预定义宏,这些宏提供发了当前编译或编译器本身的信息:

  • __DATE__:编译的日期(格式 mm dd yyyy)
  • __TIME__:编译的时间(格式 hh:mm:ss)
  • __FILE__:被编译的文件名
  • __LINE__:被编译的文件中的行号
  • __STDC__:如果编译器遵循C标准,则值为1

参考链接:

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注