huolong blog

C语言预处理机制

宏定义

  1. 无参数宏定义 #define PI 3.1415926 #define _WINDOWS_
  • 需要注意的是宏只是简单的替换,宏常量没有数据类型,编译器不会对此进行检查,可能产生意想不到的错误.如果要定义常量,最好使用const定义常量.
  • 宏定义末尾不必加分号,否则连分号一并替换.
  • 预处理是在编译之前的处理,而编译工作的任务之一就是语法检查,预处理不做语法检查.
  1. 带参宏定义 #define MAX(x, y) (((x) > (y)) ? (x) : (y))
  • 宏定义时建议所有的层次都要加括号,因为预处理只是把宏展开,不加括号可能造成意想不到的后果.比如数值计算出错.
  • 带参数的宏定义可以部分替代函数的功能,相比函数的好处是用宏只会增加编译时间,不会增加内存的分配.而函数则会在栈上分配空间,占用内存.
  • #define可以定义多条语句,以替代多行的代码,但应注意替换后的形式,避免出错。宏定义在换行时要加上一个反斜杠”\”,而且反斜杠后面直接回车,不能有空格。例子: #define TMAX_S(type, x, y) ({
    type _x = (x);
    type _y = (y);
    _x > _y ? _x: _y; }) 这是安全版的max函数. Gcc编译器将包含在圆括号和大括号双层括号内的复合语句看作是一个表达式,它可出现在任何允许表达式的地方;复合语句中可声明局部变量,判断循环条件等复杂处理。而表达式的最后一条语句必须是一个表达式,它的计算结果作为返回值。这样部分替代了函数的功能.
  • 取消宏定义可用#undef命令

字符串化操作符#

下面开始就是我们在课堂上没怎么见过的用法了,在C语言的宏中,#的功能是将其后面的宏参数进行字符串化操作(Stringfication),把随后的token(标识符,或者说变量名)转化为C语言的字符串,简单说就是将宏定义中的传入参数名转换成用一对双引号括起来参数名字符串.

#define EXAMPLE(instr) printf("The input string is:%s\n", #instr)
#define EXAMPLE2(instr) #instr

EXAMPLE(Hello);会展开为printf(“The input string is:%s\n”,“Hello”);

  • 忽略传入参数名前面和后面的空格。
  • 当传入参数名间存在多个空格时,编译器会自动连接各个子字符串,每个子字符串间只以一个空格连接。

符号连接操作符##

##运算符(Token Pasting Operator)连接两个token为一个token,即将两个变量名合成一个变量名. 例子:

#define LINK(A,B) A##B
int ab =9;
printf("%d",LINK(a,b));

输出结果就是9

条件编译

#if命令:

 #if 常量表达式
   程序段
 #else
  程序段
 #endif

常量表达式是指值不会改变并且在编译过程就能够得到计算结果的表达式,另外#if预编译命令还可使用多分支语句格式,比如:

#if 常量表达式 1

    程序段 1

#elif 常量表达式 2

    程序段 2
......
#elif 常量表达式 n

    程序段 n

#else

    程序段 m

#endif

#ifdef(或者#if defined)命令和#ifndef(或者#if !defined) #ifdef命令的使用格式如下:

#ifdef 标识符
程序段 1
#else
    程序段 2
#endif

其意义是,如果#ifdef后面的标识符已被定义过,则对“程序段1”进行编译,如果没有定义标识符,则编译“程序段2”. #ifndef命令类似,无非就是逻辑反过来,不再赘述.

预定义的宏名

  • DATE:当前源程序的创建日期。
  • FILE:当前源程序的文件名称(包括盘符和路径)。
  • LINE:当前被编译代码的行号。
  • STDC:返回编译器是否位标准C,若其值为1表示符合标准C,否则不是标准C.
  • TIME:当前源程序的创建时间。
#include<stdio.h>
int main()
{
   printf("日期:%s\n",__DATE__);
   printf("时间:%s\n",__TIME__);
   printf("文件名:%s\n",__FILE__);
   printf("这是第%d行代码\n",__LINE__);
   printf("本编译器%s标准C\n",__STDC__?"符合":"不符合");
   return 0;
}

#pragma命令

该命令的作用是设定编译器的状态,或者指示编译器完全一些特定的动作。 pragma comment指令 格式为 #pragma comment( “comment-type” ,[commentstring] )

该指令将一个注释记录放入一个对象文件或可执行文件中, comment-type(注释类型): 可以指定为 compiler、exestr、lib、linker、user 五种预定义的标识符的中的任意一种。

compiler 选项 将编译器的版本号和名称放入目标文件中,本条注释记录将被编译器忽略。如果你为该记录类型提供了 commentstring 参数,编译器将会产生一个警告。

exestr 选项 该选项将 commentstring 参数放入目标文件中,在链接的时候这个字符串将被放入到可执行文件中,当操作系统加载可执行文件的时候,该参数字符串不会被加载到内存中。但是,该字符串可以被 dumpbin 之类的程序查找出并打印出来,你可以用这个标识符将版本号码之类的信息嵌入到可执行文件中!

lib 选项 (常用) 这是一个非常常用的关键字,用来将一个库文件链接到目标文件中

常用的lib关键字,可以帮我们连入一个库文件。

例如:

#pragma comment(lib, “user32.lib”) 该指令用来将user32.lib库文件加入到本工程中

linker 选项 将一个链接选项放入目标文件中,你可以使用这个指令来代替由命令行传入的或者在开发环境中设置的链接选项,你可以指定 /include 选项来强制包含某个对象,例如:

#pragma comment(linker, “/include:__mySymbol”) 你可以在程序中设置下列链接选项

/DEFAULTLIB /EXPORT /INCLUDE /MERGE /SECTION

默认隐藏 CMD 窗口的编译选项 #pragma comment( linker, “/subsystem:“windows” /entry:“mainCRTStartup”” ) user 选项 将一般的注释信息放入目标文件中 commentstring 参数包含注释的文本信息,这个注释记录将被链接器忽略