一些语言特性整理——预处理指令、volatile、标准预定义宏

[GCC官方文档:http://gcc.gnu.org/onlinedocs/cpp/index.html]

 

一、预处理

C语言的预处理主要有三个方面的内容:

1.宏定义;#define[分无参宏和带参宏]

#undef 取消定义宏(之前的宏定义的作用域在该行之后失效)[宏能够被重定义,在重定义前,必须使用#undef指令取消原来的宏定义

使用简单宏定义定义常量符号起源于C语言,但在C++中,定义常量可以用const关键字,并且还附加类型检查的功能,因此C++中已经尽量避免使用宏定义来定义常量了。

带参宏:

定义形式:#define 宏名(形参表) 字符串 

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

注:

带参数宏定义在C++中的使用同样也在减少,因为:1,C++的内联函数提供了和带参数宏同样高的代码执行效率,同时没有后者那样的语义歧义;

2,C++模板提供了和带参数宏同样高的灵活性,还能够执行语法分析和类型检查。

引用操作符#:将操作对象替换为带引号的字符串  如:

#define CheckPtr(ptr) \

if ((ptr) == 0) cout << #ptr << " is zero!\n"

则CheckPtr(tree->left);

扩展为:

if ((tree->left) == 0) cout << "tree->left" << " is zero!\n";

拼接操作符##:用来连接宏中两个实际参数

在一般编程时很少用到拼接操作符,但在编写编译器程序或源代码生成器时特别有用,因为它能轻易的构造出一组标识符。

宏定义写成多行时,需要在一行的行末添加“\”字符,如#define CheckError if (error) exit(1)可以写成:

#define CheckError \

if (error) \

exit(1)

#define dlogh(...)  NSLog(@"DEBUGLog File=%s Line%d >>",                \

__FILE__,__LINE__),                                                  \

NSLog(__VA_ARGS__);

对于宏定义,我们要说明以下几点。

①宏定义是用宏名来表示一个字符串,在宏展开时又以该字符串取代宏名,这只是一种简单的代换。字符串中可以含任何字符,可以是常数,也可以是表达式。预处理程序对它不作任何检查。如有错误,只能在编译已被宏展开后的源程序时发现。

②宏定义不是说明语句,在行末不必加分号。如加上分号,则连分号也一起置换。

③宏定义必须写在方法之外,其作用域为宏定义命令起到源程序结束。如要终止其作用域,则可使用#undef命令。

④宏名在源程序中若用引号括起来,则预处理程序不对其作宏代换。

⑤宏定义允许嵌套,在宏定义的字符串中可以使用已经定义的宏名。在宏展开时由预处理程序层层代换。例如:

  1. #define PI 3.1415926  
  2. #define S PI*y*y /* PI 是已定义的宏名*/ 

⑥习惯上宏名用大写字母表示,以便于与变量区别,但也允许用小写字母。

⑦可用宏定义表示数据类型,方便书写。例如:

  1. #define INTEGER int 

在程序中即可用INTEGER作整型变量说明:

  1. INTEGER a,b; 

应注意用宏定义表示数据类型和用typedef定义数据说明符的区别。

⑧对输出格式作宏定义,可以减少书写麻烦。

2.文件包含;#include

3.条件编译。 

#if 常量  //这种方式常用于添加测试代码,如做测试时将常量设为1,发布时将其改为0
#ifdef 宏名  //常用于打开和关闭某项功能
#ifndef 宏名  //同#ifdef

4、其他指令

#line 改变当前行号或者文件名

#error 输出一条错误信息[强迫编译程序停止编译,主要用于程序调试。]

#warning 同#error,不过编译器将继续执行下去[常见使用场景:在废弃的头文件中使用#warning,输出信息来指示用户应该使用的正确文件名]

#progma,主要功能是为编译程序提供非常规的控制流信息。

二、c语言中volatile的使用:

作用:

1、告诉compiler不能做任何优化;

2、表示用volatile定义的变量会在程序外被改变,每次都必须从内存中读取,而不能把他放在cache或寄存器中重复使用。

[volatile关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改,比如:操作系统、硬件或者其它线程等。

遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。

使用该关键字的例子如下:volatile int nVint;]

下面是volatile变量的几个例子:

1) 并行设备的硬件寄存器(如:状态寄存器)

2) 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)

3) 多线程应用中被几个任务共享的变量

  这是区分C程序员和嵌入式系统程序员的最基本的问题。嵌入式系统程序员经常同硬件、中断、RTOS等等打交道,这些都要求volatile变量。

三、标准预定义宏

ANSI C标准中有几个标准预定义宏:
__LINE__:在源代码中插入当前源代码行号;
__FILE__:在源文件中插入当前源文件名;
__DATE__:在源文件中插入当前的编译日期
__TIME__:在源文件中插入当前编译时间;
__func__ 当前所在函数名,在编译器的较高版本中支持
__FUNCTION__ 当前所在函数名

__STDC__:当要求程序严格遵循ANSI C标准时该标识被赋值为1;
__cplusplus:当编写C++程序时该标识符被定义。

GCC中其他的预定义宏:

__GNUC__、__GNUC_MINOR__、__GNUC_MINOR__、__GNUC_PATCHLEVEL__:用于得到GNU版本:

__VERSION__:用于得到编译器的版本

__COUNTER__:自身计数器,用于记录以前编译过程中出现的__COUNTER__的次数,从0开始计数。常用于构造一系列的变量名称,函数名称等。

__INCLUDE_LEVEL__:用于表示文件被包含的计数,从0开始递增,常作为递归包含的限制条件。

原文地址:https://www.cnblogs.com/zhulin/p/2425159.html