【C】C语言编译与预处理指令

预处理指令

C语言规定:源程序中可以加入一些预处理指令。但预处理指令并不是C语言本身的组成部分,编译器不能识别它们,不能直接对这些指令进行编译。在使用时需要以“#”开头,用以与C语言区分。

所谓预处理,就是指源程序在进行编译的第一遍扫描(词法分析和语法分析)之前所做的工作由预处理程序完成。当对一个源程序进行编译时,系统将自动引用预处理程序对源程序中的预处理部分进行处理(代替),处理完毕自动进入对源程序的编译。

 

宏定义

在C语言源程序中允许用一个标识符来表示字符串,称为宏。宏定义是以“#”开头的,均为预处理命令,同时使用“define”作为宏定义命令。宏定义,根据标识符的形式,大体可以分成两种,分别是变量式宏定义和函数式宏定义。变量式宏定义的书写类似于变量的声明,用来定义常量;函数式宏定义的书写类似于函数,用来定义稍复杂些的带参数的表达式。

变量式宏定义

变量式宏定义也就是不带参数的宏定义,其定义的一般形式为:

#define 标识符 字符串

其中:字符串部分可以为浮点数、运算符、字符串、表达式等。比如:

#define PI 3.1415926
#define LARGE >
#define NEWLINE "\n"
#define M (x*x+2*x)

在使用标识符代替表达式的时候,需要注意:

  • 在表达式中出现了x,那么在主函数中必须定义变量x,否则就会编译错误
  • 宏定义中,表达式两边的括号不能少,否则可能会发生错误。

文件中的宏定义默认是从定义就有效到文件结束,但也可以对其作用范围进行圈定,使用的方法为:

#define                /* 宏定义开始 */
    ...                /* 宏定义内容 */
#undef                /* 宏定义结束 */

宏定义嵌套

在宏定义的过程中,宏也是可以嵌套定义的。比如:

#define A (1+2)
#define B A*A
#define C B+B

那么,宏展开对应为:

#define A (1+2)
#define B (1+2)*(1+2)
#define C (1+2)*(1+2)+(1+2)*(1+2)

函数式宏定义

函数式宏定义也就是带参数的宏定义,其定义的一般形式为:

#define 标识符(参数列表) 字符串

其中:字符串中应包含标识符中的参数。比如:

#define MAX(a,b) a>b?a:b
#define MUL(a,b) (a*b)

对于这两个函数我们乍一看没有什么问题,我们看一下下面的例子:

#include 

#define MAX(a,b) a>b?a:b
#define MUL(a,b) a*b

int main()
{
	int a, b, c, d;
	scanf_s("%d %d %d %d", &a, &b, &c, &d);
	printf("MAX(%d,%d)=%d\n", a + b, c + d, MAX(a + b, c + d));
	printf("MUL(%d,%d)=%d\n", a + b, c + d, MUL(a + b, c + d));

	return 0;
}

这段程序的运行结果为:

3 4 5 6
MAX(7,11)=11
MUL(7,11)=29
请按任意键继续. . .

我们向函数式宏定义的参数传递的是表达式a+b,c+d,而不是单纯的一个变量,这个时候我们进行代替看一下:

MAX(a + b, c + d)=a + b > c + d ? a + b : c + d
MUL(a + b, c + d)=a + b * c + d
  • MAX函数我们看一下,根据运算符的优先级和结合性,没有问题;
  • MUL函数我们看一下,根据运算符的优先级和结合性,很显然不是我们预期的。

也就是说,在函数式宏定义的时候,我们必须将每个参数用小括号括起来:

#define MAX(a,b) ((a)>(b)?(a):(b))
#define MUL(a,b) ((a)*(b))

宏函数和函数之间的主要区别:

  • 两者的定义形式不同:宏定义中只需要给出形式参数,不需要给出形式参数的类型,而在函数定义的时候,必须给出类型;
  • 宏函数是由预处理程序处理的,而函数是由编译程序处理的。宏程序调用的时候,仅仅作简单的替代,不作任何计算,而且是在编译之前。函数是在编译之后,在目标程序执行期间,依次求出实参的值,再执行调用;
  • 函数调用的时候,不需要求实参的类型和形参的类型相一致。而宏函数则不作任何检查;
  • 函数可以使用return进行返回一个值,宏函数不返回值。

多行宏定义

通常宏定义必须是单行的,但是也可以使用多行来定义一个宏,定义的方法就是使用反斜杠“\”。比如:

#define MAX(a,b) \
	((a)>(b)?(a):(b))

这里第一行的末尾必须是反斜杠,第二行的开头可以为了美观增添一些空格。也就是说,反斜杠后面必须紧跟回车符。

宏定义与操作符的区别:

宏定义是C语言的预处理命令之一,它是一个替代操作,不进行计算和表达式求解,不占用内存和编译时间。

终止宏名的作用域

要终止宏名的作用域,可以使用命令:

#undef 标识符

预定义宏

预定义宏是gcc自带的宏定义,用户不需要定义就可以直接用来使用的。分别有__FUNCTION__、__FILE__、__LINE__,它们表示的是函数名、文件名、行号。例如:在文件hello.c中:

#include 

int main()
{
    printf("%s,%s,%d",__FUNCTION__,__FILE__,__LINE__);

    return 0;
}

那么输出结果为:main,hello.c,5

宏展开下的#、##

一般在某些C语言代码中,会发现这样的内容,它们的含义见后面:

#define ABC(x) #x                //字符串化
#define DAY(x) day##x            //连接符号

例如:

#include 

#define ABC(x) #x
#define DAY(x) day##x

int main()
{
    int day1=10;

    printf(ABC(ab\n));
    printf("%d\n",DAY(1));

    return 0;
}

最终的输出为ab 10。

 

文件包含

文件包含指令的功能是把指定的文件插入该指令行位置取代指令行,从而把指定的文件和当前的源文件连成一个源文件。我们通常以头文件来编写这些被包含的文件,也就是以.h作为这些文件的扩展名。当然不是非要这样命名才可以。也可以使用.c为扩展名,或者干脆不写扩展名,这样都是允许的。只是使用.h更为普遍而已。

文件包含通常有以下两种形式:

#include "文件名"
#include <文件名>

两种文件包含方式的区别:

  • <>:文件包含标准头文件。搜索顺序是:从系统目录下开始搜索,然后再搜索PATH环境变量所列出的目录,不搜索当前目录。如stdio.h、assert.h、stdlib.h等;
  • "":文件包含自定义文件。搜索顺序是:从当前目录开始搜索,然后是系统目录和PATH环境变量所列出的目录。如led.h、usart.h、dac.h等。

 

条件编译

所谓“条件编译”,就是程序的内容指定编译的条件。一般情况下,源程序的所有行都参与编译,但是我们希望部分行在满足一定条件的情况下再进行编译,这就要用到条件编译。

条件编译的形式

一般条件编译的形式有#ifdef、#ifndef、#if三种形式,下面依次介绍:

  • #ifdef形式的一般形式如下:
#ifdef 标识符
    程序段1
#else
    程序段2
#endif

作用是:如果标识符已经被定义了,则对程序段1进行编译;否则编译程序段2。但也可以不写#else分句,也是没有问题的。

其中:标识符是用#define指令进行定义的,程序段可以是语句,可以是命令行。

  • #ifndef形式的一般形式如下:
#ifndef 标识符
    程序段1
#else
    程序段2
#endif

作用是:如果标识符没有被定义了,则对程序段1进行编译;否则编译程序段2。但也可以不写#else分句,也是没有问题的。

  • #if形式的一般形式如下:
#if 表达式
    程序段1
#else
    程序段2
#endif

作用是:如果if表达式的值非0,则对程序段1进行编译;否则编译程序段2。但也可以不写#else分句,也是没有问题的。

文件嵌套包含和条件编译

由于嵌套包含文件的原因,一个头文件可能会被多次包含在一个源文件中,而使用条件指示符就可以防止这种头文件的重复处理。例如:

#ifndef COMMONEILE_H
    #define COMMONFILE_H
    ...(.h文件内容)
#endif

条件指示符检测COMMONFILE_H标识符是否已经被定义了,如果没有定义,就定义并包含头文件到源文件;如果定义了,说明该头文件已经被包含进去了,就不执行接下来的内容,从而达到防止文件的嵌套包含。

GCC对条件编译的支持

使用条件编译还是需要判断某个标识符是否被定义,这还是需要对源代码进行添加和删除操作的,gcc提供了一种方式可以避免。即 gcc -D,使用方法为:

gcc hello -D标识符名 -o hello.c

这就相当于在hello.c文件中define了一个标识符,注意-D后面不需要加空格。

 

assert()宏

assert()宏是在标准库头文件中定义的,称为断言。assert()宏可以让开发人员在程序中插入任何的表达式,用来诊断表达式的值是否为false,也就是0。作为assert()宏的参数的表达式的结果是一个整型数据。例如:

assert(a == b);

当a和b相等时,表达式的结果为true,也就是1;否则为false,也就是0。

断言机制可以和符号常量NDEBUG配合使用,由NDEBUG的符号决定是否启用断言机制。

如果在引用之前定义了NDEBUG,如下:

#define NDEBUG
#include 

这样,源文件中使用的assert()就会被忽略掉。在某些系统里,断言机制默认是不可使用的,在这种情况下,需要开启断言机制:

#undef NDEBUG
#include 

【C】C语言编译与预处理指令_第1张图片

 

你可能感兴趣的:(《编程语言》C/C++语言笔记)