设为首页 加入收藏

TOP

C++预处理详解(一)
2014-03-10 12:54:13 来源: 作者: 【 】 浏览:350
Tags:处理 详解

  1. 简介

  通常我们说C++的Build(这里没用"编译"是怕混淆)可分为4个步骤:预处理、编译、汇编、链接。预处理就是本文要详细说的宏替换、头文件包含等;编译是指对预处理后的代码进行语法和语义分析,最终得到汇编代码或接近汇编的其他中间代码;汇编是指将上一步得到的汇编或中间代码转换为目标机器的二进制指令,一般是每个源文件生成一个二进制文件(VS是。obj,GCC是。o);链接是对上一步得到的多个二进制文件"链接"成可执行文件或库文件等。

  这里说的"预处理"其实并不很严格,在C++标准中对C++的translation分为9个阶段(Phases of translation),其中第4个阶段是Preprocessor,而我们说的通常的"预处理"其实是指所有这4个阶段,下面列出这4个阶段(说的不详细,详见参考文献):

  字符映射(Trigraph replacement):将系统相关的字符映射到C++标准定义的相应字符,但语义不变,如对不同操作系统上的不同的换行符统一换成规定字符(设为newline);

  续行符处理(Line splicing):对于"\"紧跟newline的,删去"\"和newline(我们在#define等中用的续行在Preprocessor之前就处理了),该过程只进行1遍(如果是"\\"后有两个换行只会删去一个"\");

  字串分割(Tokenization):源代码作为一个串被分为如下串(Token)的连接:注释、whitespace、preprocessing tokens(标示符等这时都是preprocessing tokens,因为此时不知道谁是标示符,经过下一步之后,真正的预处理符会被处理);

  执行Preprocessor:对#include指令做递归进行该1-4步,此步骤时候源代码中不再含有任何预处理语句(#开头的哪些)。

  需要强调的是,预处理是在编译前已经完成的,也就是说编译时的输入文件里已经不含有任何预处理语句了, 这包括,条件编译的测试不通过部分被删去、宏被替换、头文件被插入等。

  有了这些知识之后,本文后面对第4步的Preprocessor做详细介绍。

  2. 一般格式及概览

  Preprocessor指令一般格式如下:

  # preprocessing_instruction [arguments] newline

  其中preprocessing_instruction是以下之一:define, undef, include, if, ifdef, ifndef, else, elif, endif, line, error, pragma;arguments是可选的参数,如#include后面的文件名;Preprocessor占一行,可用"\"紧跟newline续行,但续行不是Preprocessor的专利,且续行在Preprocessor前处理。

  Preprocessor指令有以下几种:

  Null,一个 # 后跟 newline ,不产生任何影响,类似于空语句;

  条件编译,由 #if, #ifdef, #ifndef, #else, #elif, #endif 定义;

  源文件包含,由 #include 定义;

  宏替换,由 #define, #undef, #, ## 定义;

  重定义行号和文件名,由 #line 定义;

  错误信息,由 #error 定义;

  编译器预留指令,由 #pragma 定义。

  要指出的是,除了以上所列的Preprocessor指令外,其他指令是不被C++标准支持的,尽管有些编译器实现了自己的预处理指令。很据"可移植性比效率更重要"的原则,应该尽量仅适用C++标准的Preprocessor.

  下一节将对以上每个进行详细说明,除了 Null 预处理指令。

  3. 详细解释

  条件编译

  条件编译由 #if, #ifdef, #ifndef 开始,后跟 0-n 个 #elif ,后跟 0-1 个 #else ,后跟 #endif .#if, #ifdef, #ifndef, #elif 后面接expression,条件编译的控制逻辑同 if-else if-else 条件语句(每个没配对的 else 和上面最近的没配对 if 配对这条也类似),只不过它是条件的对代码进行编译而不是执行。#if, #elif 的expression为常量表达式,expression非0时测试为真,expression还可以含有 defined(Token) 测试,即Token为宏定义时为真。#ifdef Token 等价于 #if defined(Token ),#ifndef Token 等价于 #if !defined(Token )。请看例子(摘自cppreference.com):

  #include <iostream>

  #define ABCD 2

  int main()

  {

  #ifdef ABCD

  std::cout 《 "1: yes\n";

  #else

  std::cout 《 "1: no\n";

  #endif

  #ifndef ABCD

  std::cout 《 "2: no1\n";

  #elif ABCD == 2

  std::cout 《 "2: yes\n";

  #else

  std::cout 《 "2: no2\n";

  #endif

  #if !defined(DCBA) && (ABCD < 2*4-3)

  std::cout 《 "3: yes\n";

  #endif

  std::cin.get();

  return 0;

  }

  条件编译被大量用于依赖于系统又需要跨平台的代码,这些代码一般会通过检测某些宏定义来识别操作系统、处理器架构、编译器,进而条件编译不同代码,以和系统兼容。但话又说回来,C++标准的最大价值就是让所有版本的C++实现都一致,从这个层面上将,除非调用系统功能,否则不应该对系统做出任何假设,除了假设它支持C++标准以外。

  源文件包含

  文件包含指示将某个文件的内容插入到该#include处,这里"某个文件"将被递归预处理(1-4步,见第1节)。文件包含的3种格式为:#include<filename>(1)、#include"filename"(2)、#include pp-tokens(3),其中第1种方式在标准包含目录查找filename(一般C++标准库头文件在此),第二种方式先查找被处理源文件所在目录,如果没找到再找标准包含目录,第3中方式的pp-tokens须是定义为<filename>或"filename"的宏,否则结果未知。注意filename可以是任何文本文件,而不必是。h、。hpp等后缀文件,例如可以是。c或。cpp文本文件(所以标题是"源文件包含"而非"头文件包含")。例子:

  // file: b.cpp

  #ifndef _B_CPP_

  #define _B_CPP_

  int b = 999;

  #endif // #ifndef _B_CPP_

  // file: a.cpp

  #include <iostream>  // 在标准包含目录查找

  #include "b.cpp"     // 在该源文件所在目录查找,找不到再到标准包含目录查找

  #define CMATH <cmath>

  #include CMATH

  int main()

  {

  std::cout 《 b 《 '\n';

  std::cout 《 std::log10(10.0) 《 '\n';

  std::cin.get();

  return 0;

  }

  注意上面例子,将a.cpp和b.cpp放在同一文件夹,只编译a.cpp.

     

首页 上一页 1 2 3 4 5 下一页 尾页 1/5/5
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
分享到: 
上一篇C++对象占用内存大小的思考 下一篇C++ 类模板

评论

帐  号: 密码: (新用户注册)
验 证 码:
表  情:
内  容:

·HTTPS 详解一:附带 (2025-12-26 02:20:37)
·TCP/IP协议到底在讲 (2025-12-26 02:20:34)
·TCP和UDP在socket编 (2025-12-26 02:20:32)
·有没有适合新手练习 (2025-12-26 01:48:47)
·用清华镜像网怎么下 (2025-12-26 01:48:44)