使用预处理程序的提示和技巧

  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

后一个例子会打印出 NAME,而不是 NAME 所定义的值,这不是原先想做的。因此,我们能采取什么措 施呢?幸运的是,该问题有一个标准的解决方案: #define STR_HELPER(x) #x #define STR(x) STR_HELPER(x) 这个稀奇古怪的结构背后的想法就是当 STR(NAME)扩展后,就得到 STR_HELPER(NAME),且在 STR_HELPER 扩展之前,所有目标宏如 NAME 都会被先替换(只要有可替换的宏)。 当函数宏 STR_HELPER 被调用时,传递给它的参数就是 Anders。
ቤተ መጻሕፍቲ ባይዱ
♦ 宏(产生表达式的)应当被包含在括号中
假设我们有下列宏: #define PLUS1(x) (x) + 1 这里,我们已经在参数 x 上加上了括号。这个宏在有些情况下起作用。例如,下例会打印出 11,正如我们 所预期的: printf("%d\n", PLUS1(10)); 然而,在其他一些情况下,结果可能会吓你一跳;下例会打印出 21 而不是 22: printf("%d\n", 2 * PLUS1(10)); 那么,这里发生了什么事情呢?这里还是由于预处理程序只是简单的用一段源代码来代替宏调用。编译器 看到的是: printf("%d\n", 2 * (10) + 1);
♦ 条件编译
预处理程序最强大的功能之一是条件编译—也就是说某些条件下,有些代码可能被排除在实际编译之外。 这意味着如果你的源代码中包含如 ARM 处理器的特殊代码,使用条件编译就可以在编译其他所有处理器 时忽视这部分代码。 预处理指令 #ifdef, #ifndef, #if, #elif, 和 #else 是用来控制源代码的。 假如已经定义了预处理符号, 则#ifdef 指令将会包含一部分源代码,#ifndef 反之。如: #ifdef ARM_BUILD __ARM_do_something(); #else generic_do_something(); #endif #if 指令可以处理任何类型的整型和逻辑表达式,如: #if (NUMBER_OF_PROCESSES > 1) && (LOCKING == TRUE) lock_process(); #endif #elif 指令类似于 #else 和 #if 指令的结合。 #if 和 #elif 可以和 defined 结合使用来检查某一符号是否定义。 在复杂测试的情况下这是很有用的, 如: #if defined(VERSION) && (VERSION > 2) ... #endif 在第二部分中我们会重新遇到条件编译,并讨论在程序中该偏向于#if: 还是 #ifdef:。
♦ Error 指令
#error 指令可用来产生编译器错误信息。在进行连续检查时,这是很有用的,如:
#if USE_COLORS && !HAVE_DISPLAY #error "You cannot use colors unless you have a display" #endif
♦ #pragma 指令
显然,这不是我们想要的,乘法先于加法执行。 解决方法是把整个宏定义放在括号里: #define PLUS1(x) ((x) + 1) 预处理程序将此扩展如下,打印出我们预期的结果"22"。 printf("%d\n", 2 * ((10) + 1));
♦ 有副作用的宏和参数
考虑以下宏,及其典型使用方法: #define SQUARE(x) ((x) * (x)) printf("%d\n", SQUARE(++i)); 该宏的用户可能想要在每一步增加 i 并打印其增加后的平方值。但是,该宏扩展为: printf("%d\n", ((++i) * (++i))); 问题是每次使用参数时都会产生副作用。(另外需要注意的是,所得表达式甚至不是一个良好定义的 C,因 为该表达式包含对"i"的两次修改。) 最重要的是,如果可能的话,每个参数应该被赋值一次。如果不可能,应当纪录在文档中,这样以后的用 户不会感到迷惑。 因此, 如果你不能编写每个参数只使用一次的宏, 我们能做什么呢?最直接的答案就是避免使用宏。 现在, 所有的 C++和大多数 C 编译器都支持内联函数, 它们能很好的完成与宏相同的工作而没有其副作用。 此外, 使用函数的话,编译器也更容易汇报错误,因为它们不像宏那样是无类型的。
#define A_MACRO_THAT_DOES_SOMETHING_TEN_TIMES \ for (i = 0; i < 10; ++i)\ { \ do_something(); \ } 除了用户可定义的宏以外,C 标准中还有很多预先定义好的存于库中的宏可以供使用。如,__FILE__包 含现有源文件名字,以字符串的形式。 如果你想使一个宏不被定义,可以用 #undef 指令。
使用预处理程序的提示和技巧(2)
本文是有关预处理程序的文章的第二部分。在前一篇文章中,我们讨论了一些基本的对象宏和函数宏,包 括指令、条件编译,并在结尾处讨论了两个特殊指令:#error 和 #pragma。 在本文中,我们将讨论一些高级的预处理程序的话题。首先,我们会深入探讨函数宏,集中讨论如何避免 一些常见的错误。我们提出了# 和 ##预处理程序算子,并描述在定义宏时如何使用它们。我们也提出了 称为"do { ... } while(0)"的古老技巧。文章结尾,我们会讨论#if 和 #ifdef 哪个更适用于条件编译。
♦ 特殊的宏特点 • 创建一个使用"#"算子的字符串
函数宏中使用"#"算子可以将参数转化成字符串。起初,这看起来很直接,但假如你很简单的使用这个天真 的方法并在一个宏中直接使用它,那很不幸,你会感到吃惊的。 例如: #define NAIVE_STR(x) #x puts(NAIVE_STR(10)); /* This will print "10". */ 与预期不太一样的例子如: #define NAME Anders printf("%s", NAIVE_STR(NAME)); /* Will print NAME. */
♦ 对象宏
对象宏可以用来使用一些源代码来替代源码中的某个用户标识符。 典型地,宏可以用来声明某一位置处配置的常数。而且,常数可以令代码更具可读性,即使其值并不需要 改变。例如: #define SQUARE_ROOT_OF_TWO 1.4142135623730950488016887 double convert_side_to_diagonal(double x) { return x * SQUARE_ROOT_OF_TWO; } double convert_diagonal_to_side(double x) { return x / SQUARE_ROOT_OF_TWO; } 预处理程序可以用来解决非常奇怪的事情,因为预处理程序所做的就是以抽象的源代码来代替用户标识 符。如,以下是合法的代码(尽管假如你要写出类似的代码,还需要向老板解释): #define BLA_BLA ); int test(int x) { printf("%d", x BLA_BLA }
使用预处理程序的提示和技巧(1)
任何读过 C 源代码的人都看到过预处理指令。例如,你会在大多数源文件的开头看到 include 指令 (#include)。预处理程序是在实际编译器看到源代码之前将其重新编写的一种系统。显然,它是一个功能非 常强大的工具。缺点是可能会不小心砸到自己的脚。 有关预处理程序的文章有两部分,本文是其中的第一部分。本文中,我们引人预处理器,并包含了基本的 内容,包括对象和函数类宏、include 指令、条件编译、以及以两条特殊指令 #error 和 #pragma。 下一篇文章中将涉及到常见的预处理程序错误和标准 C 文献中不常见的高级预处理程序主题。 #include 指令 #include 是最直接的预处理程序指令。 当预处理程序发现了这条指令, 它就会打开指定的文件并插入内容, 而文件内容是写在指令所处的位置处。 可以采用两种形式,如: #include <systemfile.h> #include "myfile.h" 第一个用于包含标准头文件,如 stdio.h.。第二个用于用户自定义头文件。
另一个预处理程序指令是#pragma。该指令允许编程人员控制编译器的行为,并给编译器供应商提供完成 C 语言扩展的机会。 #pragma 指令不在本文中多加阐述,因为它对整个预处理程序的任务影响很小。
♦ 结论
有关预处理程序的文章有两部分,本文是其中的第一部分。本文中,我们引人预处理器,介绍了基本的内 容,包括对象和函数类宏、include 指令、条件编译、以及以两条特殊指令 #error 和 #pragma。 下一篇文章中将涉及到常见的预处理程序错误和标准 C 文献中不常见的高级预处理主题。
♦ 宏
预处理程序最有用的特点之一就是允许用户自己定义宏,也就是映射到一段代码的用户标识符。预处理程 序无论何时在应用程序源代码中发现了宏,它就会用定义来代替宏。 基本上有两类宏,对象宏和函数宏。区别是函数宏有参数。 按照贯例,宏的名字只用大写来书写。唯一的特例是因为效率的原因用一个宏来代替一个函数。 指令 #define 是用来定义一个宏的。下例中,我们把 NUMBER_OF_PLAYERS 定义为常数 2 。这是一 个对象宏。 #define NUMBER_OF_PLAYERS 2 int current_score[NUMBER_OF_PLAYERS]; 这里,函数宏 PRINT_PLAYER 映射到一段更复杂的代码。 #define PRINT_PLAYER(no) printf("Player %d is named %s", no, names[no]) 从技术上来说,宏的定义必须出现在同一行源代码中。幸运的是,标准 C 允许你用反斜杠来作为一行的结 尾然后在下一行中继续。如:
♦ 函数宏的问题
乍一看,函数宏似乎是一个简单而直接的结构。然而,进一步研究你就会发现其很多非常恼人的缺点。我 会给出很多例子,其中每个问题都显而易见,我还会提出一些解决问题的方法。
♦ 总是在定义中的参数周围加上括号
考虑如下一个看似简单的宏: #define TIMES_TWO(x) x * 2 在平常的运算中,这段代码会完成所需的功能。例如,TIMES_TWO(4) 扩展为 4 * 2,值为 8。另一方面, 你会希望 TIMES_TWO(4 + 5)的值为 18,对吗?然而,其实不是这样的,因为宏仅以简单的方式来用参 数代替"x",正如所写的那样。也就是说,编译器看到的是"4 + 5 * 2",值为 14。 解决方法是总是在参数上加上括号,如: #define TIMES_TWO(x) (x) * 2
♦ 包含保护头(Include guard)
条件编译的典型应用之一是保证包含文件中的内容只被访问一次。这不仅会使编译速度加快,也能保证头 文件在被包含两次的情况下不产生错误(如 struct 的再次声明)。 典型的包含头如下: #ifndef MYHEADER_H #define MYHEADER_H /* The content of the header file goes here. */ #endif 很明显,头文件第一次被包含时,符号 MYHEADER_H 是未定义的且内容包含在编译中。第二次读头文 件时该符号已定义且内容排除在外。
♦ 函数宏
函数宏是有参数的宏。当你使用它们时,就像是函数调用。函数宏如下列代码: #define SEND(x) output_array[output_index++] = x 当预处理程序发现应用程序源代码中有函数宏,它就会用定义来代替它。宏参数会在参数变量位置处插入 最终的源代码。 因此,如果你写了下列代码: SEND(10) 编译器会看到: output_array[output_index++] = 10 在第二部分中我们还会提到函数宏,并讨论其中可能的陷阱。
相关文档
最新文档