成都 企业 网站建设,单页网站模板wap,wordpress主题摘要字数,南京500元做网站文章目录 一、概述二、格式2.1 条件编译2.2 源文件包含2.3 宏替换2.3.1 语法2.3.2 C标准内置的预定义宏 2.4 重定义行号和文件名2.5 错误信息2.6 编译器预留指令 三、应用场景 C的 Build 可分为4个步骤#xff1a;预处理、编译、汇编、链接。
预处理就是本文要详细说的宏替换… 文章目录 一、概述二、格式2.1 条件编译2.2 源文件包含2.3 宏替换2.3.1 语法2.3.2 C标准内置的预定义宏 2.4 重定义行号和文件名2.5 错误信息2.6 编译器预留指令 三、应用场景 C的 Build 可分为4个步骤预处理、编译、汇编、链接。
预处理就是本文要详细说的宏替换、头文件包含等编译是指对预处理后的代码进行语法和语义分析最终得到汇编代码或接近汇编的其他中间代码汇编是指将上一步得到的汇编或中间代码转换为目标机器的二进制指令一般是每个源文件生成一个二进制文件VS是.objGCC是.o链接是对上一步得到的多个二进制文件“链接”成可执行文件或库文件等。
一、概述
Preprocessor预处理包含 4 个阶段
Trigraph replacement字符映射将系统相关的字符映射到C标准定义的相应字符但语义不变如对不同操作系统上的不同的换行符统一换成规定字符设为newlineLine splicing续行符处理对于“\”紧跟newline的删去“\”和newline该过程只进行1遍如果是“\”后有两个换行只会删去一个“\”Tokenization字串分割源代码作为一个串被分为如下串Token的连接注释、whitespace、preprocessing tokens标示符等这时都是preprocessing tokens因为此时不知道谁是标示符经过下一步之后真正的预处理符会被处理执行Preprocessor对#include指令做递归进行该1-4步此步骤时候源代码中不再含有任何预处理语句#开头的那些。
预处理之后的效果是条件编译的测试不通过部分被删去、宏被替换、头文件被插入等。
预处理是以 translation unit 为单位进行的一个 translation unit 就是一个源文件连同由#include包含或间接包含的所有文本文件的全体。一般编译器对一个 translation unit 生成一个二进制文件VS是.objGCC是.o。
二、格式
Preprocessor指令一般格式如下
# preprocessing_instruction [arguments] newline指令如下除了以上所列的Preprocessor指令外其他指令是不被C标准支持的尽管有些编译器实现了自己的预处理指令。很据“可移植性比效率更重要”的原则应该尽量仅适用C标准的Preprocessor
Null一个 # 后跟 newline 不产生任何影响类似于空语句条件编译由 #if, #ifdef, #ifndef, #else, #elif, #endif 定义源文件包含由 #include 定义宏替换由 #define, #undef, #, ## 定义重定义行号和文件名由 #line 定义错误信息由 #error 定义编译器预留指令由 #pragma 定义。
2.1 条件编译
条件编译由 #if, #ifdef, #ifndef 开始后跟 0-n 个 #elif 后跟 0-1 个 #else 后跟 #endif 。
#include iostream#define ABCD 2int main() {
#ifdef ABCDstd::cout 1: yes\n;
#elsestd::cout2:no\n;
#endif#ifndef ABCDstd::cout 2: no1\n;
#elif ABCD 2std::cout 2: yes\n;
#elsestd::cout 2: no2\n;
#endif#if !defined(DCBA) (ABCD 2 * 4 - 3) // todo: 不知道为什么ABCD 2 * 4 - 3成立std::cout 3: yes\n;
# endifstd::cin.get();return 0;
}// code result:
1: yes
2: yes
3: yes条件编译被大量用于依赖于系统又需要跨平台的代码这些代码一般会通过检测某些宏定义来识别操作系统、处理器架构、编译器进而条件编译不同代码以和系统兼容。
PS但话又说回来C标准的最大价值就是让所有版本的C实现都一致所以除非调用系统功能否则不应该对系统做出任何假设。
2.2 源文件包含
源文件包含指示将某个文件的内容插入到该#include处这里“某个文件”将被递归预处理1-4步见第1节。文件包含的3种格式为
#includefilename在标准包含目录查找filename一般C标准库头文件在此#includefilename先查找被处理源文件所在目录如果没找到再找标准包含目录#include pp-tokens其其中pp-tokens须是定义为或filename的宏否则结果未知。注意filename可以是任何文本文件而不必是.h、.hpp等后缀文件例如可以是.c或.cpp文本文件所以标题是“源文件包含”而非“头文件包含”。
#ifndef B_CPP
#define B_CPP
int b 999;
#endif // B_CPP// file: a.cpp
#includeiostream // 在标准库目录找
#includeb.cpp// 先在源文件所在目录找, 再再标准库找#define CMATH cmath // 如下两行效果即为 #includecmath, 这是一个标准库
#include CMATH // 同上int main() {std::cout b \n; // 是在b.cpp定义的std::cout std::log10(10.0) \n;std::cin.get();return 0;
}// code result: 注意将a.cpp和b.cpp放在同一文件夹只编译a.cpp(命令为g a.cpp ./a.out)
999
12.3 宏替换
2.3.1 语法
#define 定义宏替换#define 之后的宏都将被替换为宏的定义直到用 #undef 解除该宏的定义。
宏定义分为不带参数的常量宏Object-like macros和带参数的函数宏Function-like macros。其格式如下
#define identifier replacement-list
#define identifier( parameters ) replacement-list
#define identifier( parameters, ... ) replacement-list
#define identifier( ... ) replacement-list
#undef identifier对于有参数的函数宏在replacement-list中“#”置于identifier面前表示将identifier变成字符串字面值“##”连接下面的例子来自cppreference.com
#includeiostream// make function factory
#define FUNCTION(name, a) int fun_##name() {return a;} // “#”置于identifier面前表示将identifier变成字符串字面值“##”连接
FUNCTION(abcd, 12); // 定义func_abc()函数其无参数, 返回 12
FUNCTION(fff, 2);// 定义func_fff()函数其无参数, 返回 2
FUNCTION(kkk, 23);// 定义func_kkk()函数其无参数, 返回 23
#undef FUNCTION#define FUNCTION 34 // 之前已定义过的 fun_abcd()、fun_fff()、fun_kkk() 已定义好, 现在可以重新宏定义了#define OUTPUT(a) std::cout #a \nint main() {std::cout abcd: fun_abcd() std::endl; // use function factorystd::cout fff: fun_fff() std::endl;std::cout kkk: fun_kkk() std::endl;std::cout FUNCTION std::endl; // 新的宏定义是34OUTPUT(million);std::cin.get();return 0;
}// code result:
abcd: 12
fff: 2
kkk: 23
34
million可变参数宏是C11新增部分来自C99使用时用__VA_ARGS__指代参数“…”一个摘自C标准2011的例子如下标准举的例子就是不一样啊
#includeiostream
#define debug(...) fprintf(stderr, __VA_ARGS__) // __VA_ARGS__指代参数“...”
#define showlist(...) puts(#__VA_ARGS__)
#define report(test, ...) ((test) ? puts(#test) : printf(__VA_ARGS__))
int main() {int x 1;int y 2;debug(Flag);debug(X %d\n, x);showlist(The first, second, and third items.);report(xy, x is %d but y is %d, x, y);
}// 这段代码在预处理后产生如下代码:
fprintf(stderr, Flag);
fprintf(stderr, X %d\n, x);
puts(The first, second, and third items.);
((xy) ? puts(xy) : printf(x is %d but y is %d, x, y));// code result:
FlagX 1
The first, second, and third items.
x is 1 but y is 22.3.2 C标准内置的预定义宏
// 其中上面5个宏一定会被定义下面从__STDC__开始的宏不一定被定义这些预定义宏不能被 #undef
// 这些宏经常用于输出调试信息。预定义宏一般以“__”作为前缀所以用户自定义宏应该避开“__”开头
// 现代的C程序设计原则不推荐适用宏定义常量或函数宏应该尽量少的使用 #define 如果可能用 const 变量或 inline 函数代替
__cplusplus: 在C98中定义为199711LC11中定义为201103L
__LINE__: 指示所在的源代码行数从1开始十进制常数
__FILE__: 指示源文件名字符串字面值
__DATE__: 处理时的日期字符串字面值格式“Mmm dd yyyy”
__TIME__: 处理时的时刻字符串字面值格式“hh:mm:ss”__STDC__: 指示是否符合Standard C可能不被定义
__STDC_HOSTED__: 若是Hosted Implementation定义为1否则为0
__STDC_MB_MIGHT_NEQ_WC__: 见ISO/IEC 14882:2011
__STDC_VERSION__: 见ISO/IEC 14882:2011
__STDC_ISO_10646__: 见ISO/IEC 14882:2011
__STDCPP_STRICT_POINTER_SAFETY__: 见ISO/IEC 14882:2011
__STDCPP_THREADS__: 见ISO/IEC 14882:2011// 示例如下:
#include iostream
int main() {
#define PRINT(arg) std::cout #arg: arg \nPRINT(__cplusplus);PRINT(__LINE__);PRINT(__FILE__);PRINT(__DATE__);PRINT(__TIME__);
#ifdef __STDC__PRINT(__STDC__);
#endifstd::cin.get();return 0;
}
// code result:
__cplusplus: 201703
__LINE__: 6
__FILE__: /cppcodes/run/a.cpp
__DATE__: Aug 26 2023
__TIME__: 21:11:36
__STDC__: 12.4 重定义行号和文件名
从 #line number [filename] 的下一行源代码开始 __LINE__ 被重定义为从 number 开始__FILE__ 被重定义 filename可选一个例子如下
#include iostream
int main() {
#define PRINT(arg) std::cout #arg: arg \n
#line 999 WOPRINT(__LINE__);PRINT(__FILE__);std::cin.get();return 0;
}// code result:
__LINE__: 999
__FILE__: WO2.5 错误信息
#error [message] 指示编译器报告错误一般用于系统相关代码例如检测操作系统类型用条件编译里 #error 报告错误。例子如下
int main(){
#error wreturn 0;
#error
}// code result:
/cppcodes/run/a.cpp:2:2: error: w
#error w^
/cppcodes/run/a.cpp:4:2: error:
#error^
2 errors generated.2.6 编译器预留指令
#pragma预处理指令是C标准给特定C实现预留的标准所以在不同的编译器上 #pragma 的参数及意义可能不同如
VC2010 提供 #pragma once 来指示源文件只被处理一遍参见MSDN相关条目OpenMP 作为一个共享内存并行编程模型使用 #pragma omp 指导语句详见OpenMP共享内存并行编程详解。GCC 的 #pragma 指令参见GCC文档相关条目。
三、应用场景
预处理的常见使用有
Include guard见wikipedia条目该技术用来保证头文件仅被同一文件包含一次准确地说头文件内容在一个 translation unit 中仅出现一次以防止违反C的“一次定义”原则用 #ifdef 和特殊宏识别操作系统、处理器架构、编译器条件编译进而实现针对特定平台的功能多用于可移植性代码定义函数宏以简化代码或是方便修改某些配置用 #pragma 设定和实现相关的配置见上一节最后给出的链接。 sourceforge.net上有一个项目是关于用宏检测操作系统、处理器架构、编译器请点链接或见参考文献。下面是一个例子来自这里
关于用宏检测的定义如下链接代码示例如下
操作系统宏处理器架构宏编译器宏
#ifdef _WIN64//define something for Windows (64-bit)
#elif _WIN32//define something for Windows (32-bit)
#elif __APPLE__#include TargetConditionals.h#if TARGET_OS_IPHONE TARGET_IPHONE_SIMULATOR// define something for simulator #elif TARGET_OS_IPHONE// define something for iphone #else#define TARGET_OS_OSX 1// define something for OSX#endif
#elif __linux// linux
#elif __unix // all unices not caught above// Unix
#elif __posix// POSIX
#endif