首页 > 代码库 > SDWebImage源码阅读前的准备(一)预处理条件编译
SDWebImage源码阅读前的准备(一)预处理条件编译
阅读前的扩展:
条件编译:#ifdef #elif #ifndef #if #else #endif 的用法,及 #define #undef 的用法。
#ifdef/#elif/#ifndef/#if/#else/#endif 这几个条件编译指令是在进行条件编译的时候使用的。
学习条件编译首先对C语言的预处理进行学习,C语言由源代码生成的各阶段如下:
C源程序->编译预处理->编译->优化程序->汇编程序->链接程序->可执行文件
其中 编译预处理阶段,读取c源程序,对其中的伪指令(以#开头的指令)和特殊符号进行处理。或者说是扫描源代码,对其进行初步的转换,产生新的源代码提供给编译器。预处理过程先于编译器对源代码进行处理。且条件编译是预处理程序的功能,并不是编译器的功能。
在C 语言中,并没有任何内在的机制来完成如下一些功能:在编译时包含其他源文件、定义宏、根据条件决定编译时是否包含某些代码(条件编译)。要完成这些工作,就需要使用预处理程序。
尽管在目前绝大多数编译器都包含了预处理程序,但通常认为它们是独立于编译器的。预处理过程读入源代码,检查包含预处理指令的语句和宏定义,并对源代码进行相应的转换。
预处理过程还会删除程序中的注释和多余的空白字符。
预处理指令(伪指令)定义:
预处理指令是以#号开头的代码行。#号必须是该行除了任何空白字符外的第一个字符。#后是指令关键字,在关键字和#号之间允许存在任意个数的空白字符。整行语句构成了一条预处理指令,该指令将在编译器进行编译之前对源代码做某些转换。
下面是部分预处理指令:
1 指令 用途 2 # 空指令,无任何效果 3 #include 包含一个源代码文件,用于文件引用 4 #import (OC中文件引用,可防止重复包含) 5 #define 定义宏 6 #undef 取消已定义的宏 7 #if 如果给定条件为真,则编译下面代码 8 #ifdef 如果宏已经定义,则编译下面代码 9 #ifndef 如果宏没有定义,则编译下面代码 10 #elif 如果前面的#if给定条件不为真,当前条件为真,则编译下面的代码,其实就是else if的简写 11 #else 如果前面的#if给定条件不为真,则编译下面的代码 12 #endif 结束一个#if……#else条件编译块 13 #error 停止编译并显示错误信息
预处理指令主要包括4个方面:
1.宏定义指令
宏定义了一个代表特定内容的标识符。预处理过程会把源代码中出现的宏标识符替换成宏定义时的值。
宏最常见的用法是定义代表某个值的全局符号。宏的第二种用法是定义带参数的宏(宏函数),这样的宏可以象函数一样被调用,但它是在调用语句处展开宏,并用调用时的实际参数来代替定义中的形式参数。
1.1#define 指令
1.1.1#define 预处理指令用来定义宏。该指令最简单的格式是:声明一个标识符,给出这个标识符代表的代码(比如像圆周率这样的数)。在后面的源代码中,我们就可以使用定义的宏取代要使用的代码,举例如下:
1 #define kPI 3.1415926 2 3 #define kMAX_NUM 10 4 int array[kMAX_NUM] 5 for(int i = 0; i < kMAX_NUM; i++)
在这个例子中,对于阅读该程序的人来说,符号kMAX_NUM命名就有了一定的含义,它代表的值给出了数组所能容纳的最大元素数目。
程序中可以多次使用这个值。
作为一种约定,习惯上总是以小写字母k开头后面全部用大写字母来定义宏,这样易于把程序的宏标识符和一般变量标识符区别开来。
1.1.2使用宏定义的好处
一是书写方便。
二是定义的宏命名代表了它表达的意义,可读性更强。
三是容易修改,比如程序代码中多处用到了同一个宏,当需要修改宏的值的时候只需在宏定义处做一次修改,即可改变所有宏的值。
1.2#运算符
出现在宏定义中的#运算符会把跟在其后的参数转换成一个字符串。有时把这种用法的#称为字符串化运算符,宏定义中的#运算符告诉预处理程序,把源代码中任何传递给该宏的参数转换成一个字符串。
使用如下:
1 定义:#define kMONTAGE(str) "CHM"#str 2 3 打印:NSLog(@"%s", kMONTAGE(HML)); 4 5 输出:2017-04-17 20:39:03.340 ddddddd[6557:1081988] CHMHML
1.3##运算符
##运算符用于把参数连接到一起。预处理程序把出现在##两侧的参数合并成一个符号。
定义:#define kNUM(a, b, c) a##b##c 打印: NSLog(@"%d", kNUM(1, 2, 3)); 输出:2017-04-17 20:47:35.865 ddddddd[6598:1087951] 123
2.头文件包含指令
采用头文件的目的主要是为了使某些定义可以供多个不同的C源程序使用。因为在需要用到这些定义的C源程序中,只需加上一条#include语句即可(OC 中使用#import),而不必再在此文件中将这些定义重复一遍。预编译程序将把头文件中的定义统统都加入到它所产生的输出文件中,以供编译程序对之进行处理。
#include预处理指令的作用是在指令处展开被包含的文件。包含可以是多重的,也就是说一个被包含的文件中还可以包含其他文件。标准C编译器至少支持八重嵌套包含。预处理过程不检查在转换单元中是否已经包含了某个文件并阻止对它的多次包含。(#import 可以防止重复包含)
在程序中包含头文件有两种格式:
#include <my.h>
#include "my.h"
第一种方法是用尖括号把头文件括起来。这种格式告诉预处理程序在编译器自带的或外部库的头文件中搜索被包含的头文件。首先会搜索编译器自带的头文件。
第二种方法是用双引号把头文件括起 来。这种格式告诉预处理程序在当前被编译的应用程序的源代码文件中搜索被包含的头文件,如果找不到,再搜索编译器自带的头文件。
采用两种不同包含格式的理由在于,编译器是安装在公共子目录下的,而被编译的应用程序是在它们自己的私有子目录下的。一个应用程序既包含编译器提供的公共头文件,也包含自定义的私有头文件。采用两种不同的包含格式使得编译器能够在很多头文件中区别出一组公共的头文件。
3.特殊符号
预编译程序可以识别一些特殊的符号。预编译程序对于在源程序中出现的这些串将用合适的值进行替换。
#error指令将使编译器显示一条错误信息,然后停止编译。
#line指令改变_LINE_与_FILE_的内容,它们是在编译程序中预先定义的标识符。
#pragma指令没有正式的定义。编译器可以自定义其用途。典型的用法是禁止或允许某些烦人的警告信息。
1 注意,是双下划线,而不是单下划线 。 2 __FILE__ 包含当前程序文件名的字符串 3 __LINE__ 表示当前行号的整数 4 __DATE__ 包含当前日期的字符串 5 __STDC__ 如果编译器遵循ANSI C标准,它就是个非零值 6 __TIME__ 包含当前时间的字符串 7 8 NSLog(@"%s", __FILE__); 9 NSLog(@"%d", __LINE__); 10 NSLog(@"%s", __DATE__); 11 NSLog(@"%d", __STDC__); 12 NSLog(@"%s", __TIME__); 13 14 打印: 15 2017-04-17 21:00:17.764 ddddddd[6631:1095088] /Users/jay/Desktop/Practise/ddddddd/ddddddd/AppDelegate.m 16 2017-04-17 21:00:17.765 ddddddd[6631:1095088] 31 17 2017-04-17 21:00:17.765 ddddddd[6631:1095088] Apr 17 2017 18 2017-04-17 21:00:17.765 ddddddd[6631:1095088] 1 19 2017-04-17 21:00:17.765 ddddddd[6631:1095088] 21:00:10
//在文件顶部定义 #line 100 ,下面__FILE__ 和 __LINE__ 打印所发生的变化可与上面做对比
1 #line 100 2 3 NSLog(@"%s", __FILE__); 4 NSLog(@"%d", __LINE__); 5 6 打印: 7 2017-04-17 21:06:43.373 ddddddd[6652:1099248] /Users/jay/Desktop/Practise/ddddddd/ddddddd/AppDelegate.m 8 2017-04-17 21:06:43.373 ddddddd[6652:1099248] 120
预编译程序所完成的基本上是对源程序的“替代”工作。经过此种替代,生成一个没有宏定义、没有条件编译指令、没有特殊符号的输出文件。这个文件的含义同没有经过预处理的源文件是相同的,但内容有所不同。下一步,此输出文件将作为编译程序的输出而被翻译成为机器指令。
下面讲解预编译指令中较为常用的第4部分--条件编译指令:
4.条件编译指令
4.1条件编译定义:
一般情况下,源程序中所有的行都参加编译。但有时希望对其中一部分内容只在满足一定条件下才进行编译,即对一部分内容指定编译条件,这就是“条件编译”(conditional compile)。(摘自百度百科“条件编译”词条)
可以通过定义不同的宏来决定编译程序对哪些代码进行处理,条件编译指令将决定那些代码被编译,而哪些是不被编译的。也可以根据表达式的值或者某个特定的宏是否被定义来确定编译条件。
4.2条件编译指令的使用
#ifdef/#else/#endif 或 #ifndef/#else/#endif
1 #ifdef macro_name 2 程序段1 3 #else 4 程序段2 5 #endif // 如果在此前定义了macro_name 宏,则编译程序段1,否则编译程序段2 6 7 #ifndef macro_name 8 程序段2 9 #else 10 程序段1 11 #endif // 如果在此前定义了macro_name 宏,则编译程序段1,否则编译程序段2 12 // 其中#else 也可以没有 13 #ifdef macro_name 14 程序段 15 #endif 16 17 #ifdef macro_name 18 其它程序段 19 #endif
它们分别表示“如果有定义”及“如果无定义”。有定义是指在编译此段代码时是否有某个宏通过 #define 指令定义,#ifndef指令指找不到通过#define定义的某宏,该宏可以是在当前文件此条指令的前面定义的,也可以是在其它文件中,但在此指令之前包含到该文件中的。
在SDWebImage 中条件编译主要使用在SDWebImageCompat.h 和.m 中用于判断不同的开发平台MAC/iOS:
1 // 如果是MAC 开发 2 #import <AppKit/AppKit.h> 3 #ifndef UIImage 4 #define UIImage NSImage 5 #endif 6 #ifndef UIImageView 7 #define UIImageView NSImageView 8 #endif 9 #ifndef UIView 10 #define UIView NSView 11 #endif 12 13 // 定义两类枚举 14 #ifndef NS_ENUM 15 #define NS_ENUM(_type, _name) enum _name : _type _name; enum _name : _type 16 #endif 17 18 #ifndef NS_OPTIONS 19 #define NS_OPTIONS(_type, _name) enum _name : _type _name; enum _name : _type 20 #endif 21 22 // 安全的异步调用主线程
// 我们来看看这个宏,按理说我使用dispatch_async就可以了,为什么要加入safe呢?那么这个safe主要是解决那些不安全的问题呢?
// 我们可以像这样在定义宏的时候使用换行,但需要添加 \ 操作符
// 如果当前线程已经是主线程了,那么在调用dispatch_async(dispatch_get_main_queue(), block)有可能会出现crash
// 如果当前线程是主线程,直接调用,如果不是,调用dispatch_async(dispatch_get_main_queue(), block)
// dispatch_queue_get_label()获取队列的名字,如果队列没有名字,返回NULL,如果队列有名字,则返回类型是 const char 所以这里用strcmp 进行字符串比较
// strcmp(dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL), dispatch_queue_get_label(dispatch_get_main_queue())) == 0 获取当前队列的名字与主线程的名字比较,看是当前线程是否是主线程
// 参考链接: http://blog.csdn.net/litlelee1005/article/details/54709109
23 #ifndef dispatch_main_async_safe 24 #define dispatch_main_async_safe(block)25 if (strcmp(dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL), dispatch_queue_get_label(dispatch_get_main_queue())) == 0) {26 block();27 } else {28 dispatch_async(dispatch_get_main_queue(), block);29 } 30 #endif
在调试程序时,常常希望输出一些所需的信息,而在调试完成后不再输出这些信息。
1 #ifdef DEBUG 2 输出调试信息 3 #endif 4 5 // 在调试程序时可在它前面定义 #define DEBUG 调试结束把宏 DEBUG 注释掉 只需一个DEBUG 就可以控制所有调试信息的有效和无效
用于防止文件重复包含。可以在.h 文件前面加上这样一段代码:
1 //头文件防止重复包含 2 //funcA.h 3 #ifndef FUNCA_H 4 #define FUNCA_H 5 //头文件内容 6 #end if
还有一个注意点: #ifdef 和 #ifndef 后面跟的只能是一个宏名,不能是其他的。
且在 #ifdef 和 #ifndef 的后面:
1 #define macro_name 2 3 #define macro_name 0 4 5 #define macro_name 1 6 // 三者代表的含义是相同的,就是都定义了macro_name , #ifdef 下面的语句都会执行,#define macro_name 和#define macro_name 0 并没有区别。例如在调试程序时定义 #define DEBUG 在不需要调试信息时需要把#define DEBUG 注释掉,而不是#define DEBUG 0 ,这里与宏定义的值是否为真并没有关系,只与这个宏有没有没定义有关。
#if/#elif/#else/#endif
1 #if 表达式 1 2 程序段 1 3 #elif 表达式 2 4 程序段 2 5 ... 6 #elif 表达式 n 7 程序段 n 8 #else 9 程序段 n+1 10 #endif 11 12 // #if 和 #elif 后面跟的表达式只能是“整型常量表达式” #if指令检测跟在改指令关键字后的常量表达式。如果表达式为真,则编译后面的代码,直到出现#else、#elif或#endif为止;否则就不编译。
#endif用于终止#if预处理指令。
#else指令用于某个#if指令之后,当前面的#if指令的条件不为真时,就编译#else后面的代码。
当指定的表达式值为真(非零)时就编译程序段,(#if 和 #elif 后面跟的表达式只能是“整型常量表达式,需要注意的是,#if 命令要求判断条件为“整型常量表达式”,也就是说,表达式中不能包含变量,而且结果必须是整数;而 if 后面的表达式没有限制,只要符合语法就行。这是 #if 和 if 的一个重要区别。”)否则继续往下判断编译相应的程序段。可以事先给定一定条件,使程序在不同的条件下执行不同的功能。
注意事项:在OC 中如果#if 后面的表达式是一个宏名,如果该宏名没有用#define 定义过则和定义该宏为 0 和NO代表的含义是一样的,#if 下面跟的程序段不会执行。而如果只是定义宏没有给相应代表的值(例如:#define macro_name ),则OC 会直接报错,这点和#ifdef 和#ifndef 是有很大区别的。
不用条件编译处理,目标程序更长(因为所有语句都编译),而采用条件编译,可以减少被编译的语句,从而减少目标的长度。当条件编译段比较多时,目标程序长度可以大大减少。
在SDWebImage 中条件编译主要使用在SDWebImageCompat.h 和.m 中用于判断不同的开发平台MAC/iOS:
1 // 苹果的TARGET 宏定义都来自 TargetConditionals.h 是有一点怪 2 // TARGET_OS_MAC 好像总是定义在所有的平台中 MAC Iphone Watch TV 3 // 为确定当前是运行在 OSX 平台,我们只能除了TARGET_OS_MAC,其它所有平台的都是0 4 #if !TARGET_OS_IPHONE && !TARGET_OS_IOS && !TARGET_OS_TV && !TARget_OS_WATCH 5 #define SD_MAC 1 6 #else 7 #define SD_MAC 0 8 #endif 9 10 // iOS 和 tvOS 是非常相像的,UIKit 是存在两个平台上 11 // watchOS 也有UIKit ,但是它是非常有限的 12 #if TARGET_OS_IOS || TARGET_OS_TV 13 #define SD_UIKIT 1 14 #else 15 #define SD_UIKIT 0 16 #endif 17 18 #if TARGET_OS_IOS 19 #define SD_IOS 1 20 #else 21 #define SD_IOS 0 22 #endif 23 24 #if TARGET_OS_TV 25 #define SD_TV 1 26 #else 27 #define SD_TV 0 28 #endif 29 30 #if TARGET_OS_WTACH 31 #define SD_WATCH 1 32 #else 33 #define SD_WATCH 0 34 #endif 35 36 #if OS_OBJECT_USE_OBJC 37 #undef SDDispatchQueueRelease 38 #undef SDDispatchQueueSetterSementics 39 #define SDDispatchQueueRelease(q) 40 #define SDDispatchQueueSetterSementics strong 41 #else 42 #undef SDDispatchQueueRelease 43 #undef SDDispatchQueueSetterSementics 44 #define SDDispatchQueueRelease(q) (dispatch_release(q)) 45 #define SDDispatchQueueSetterSementics assign 46 #endif 47 48 #if __IPHONE_OS_VERSION_MIN_REQUIRED != 20000 && __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_5_0 49 #error SDWebImage doesn‘t support Deployment Target version < 5.0 50 #endif 51 52 // 系统在TargetConditionals.h 中的宏定义 53 54 #define TARGET_OS_MAC 1 55 #define TARGET_OS_WIN32 0 56 #define TARGET_OS_UNIX 0 57 #define TARGET_OS_OSX 0 58 #define TARGET_OS_IPHONE 1 59 #define TARGET_OS_IOS 1 60 #define TARGET_OS_WATCH 0 61 #define TARGET_OS_BRIDGE 0 62 #define TARGET_OS_TV 0
宏函数 defined(macro_name) 这里比#define 多了一个d 少了一个#,多了一对括号
参数为宏名(无需加""),如果该macro_name定义过则返回真,否则返回假,用该函数则可以写比较复杂的条件编译指令如:
1 #if defined(macro1) || (!defined(macro2) && defined(macro3)) 2 ... 3 #else 4 ... 5 #endif
#ifdef 可以认为是 #if defined(macro_name) 的缩写。
条件编译的技巧:
(1):#ifdef 和 #if defined() 比较
首先比较一下这两种方法,第一种方法只能判断一个宏,如果条件比较复杂实现起来比较烦锁,用后者就比较方便。如有两个宏MACRO_1,MACRO_2只有两个宏都定义过才会编译代码段A,分别实现如下:
1 #ifdef MACRO_1 2 #ifdef MACRO_2 3 代码段 A 4 #endif 5 #endif 6 7 或者 8 #if defined(MACRO_1) && defined(MACRO_2) 9 代码段 A 10 #endif
同样,要实现更复杂的条件用#ifdef更麻烦,所以推荐使用后者,因为即使当前代码用的是简单的条件编译,以后在维护、升级时可能会增加,用后者可维护性较强。旧的编译器可能没有实现#defined()指令,C99已经加为标准。要兼容老的编译器,还需用#ifdef指令。
(2):#if 与 #ifdef 或 #if defined() 比较
比如自己写了一个printf函数,想通过一个宏MY_PRINTF_EN 实现条件编译,用#if 可实现如下
C语言的条件编译。
1 #define MY_PRINTF_EN 1 2 3 #if MY_PRINTF_EN == 1 4 5 int printf(char* fmt, char* args, ...) 6 { 7 ... 8 } 9 10 #endif
如果宏MY_PRINTF_EN 定义为1则编译这段代码,如果宏定义不为1或者没有定义该宏,则不编译这段代码。同样也可以通过#ifdef 或者#defined()实现,如:
1 #define MY_PRINTF_EN 1 2 3 #if defined(MY_PRINTF_EN) 4 5 int printf(char* fmt, char* args, ...) 6 { 7 ... 8 } 9 10 #endif
在这种情况下两种方法具有异曲同工之妙,但试想如果你为了节约代码写了两个printf函数,在不同情况下使用不同的printf函数,一个是精简版一个是全功能标准版,如:
1 #define MY_PRINTF_SIMPLE 2 3 #ifdef MY_PRINTF_SIMPLE 4 void printf(*str) // 向终端简单地输出一个字符串 5 { 6 ... 7 } 8 #endif 9 10 #ifdef MY_PRINTF_STANDARD 11 int printf(char* fmt, char* args, ...) 12 { 13 ... 14 } 15 #endif 16 17 // 同样可以用#if defined()实现 18 #define MY_PRINTF_SIMPLE 19 20 #if defined(MY_PRINTF_SIMPLE) 21 void printf(*str) // 向终端简单地输出一个字符串 22 { 23 ... 24 } 25 #elif defined(MY_PRINTF_STANDARD) 26 int printf(char* fmt, char* args, ...) 27 { 28 ... 29 } 30 #endif
两种方法都可以实现,但可见后者更方便。但试想如果你有三个版本,用前者就更麻烦了,但方法相似,用后者就更方便,但仍需三个宏进行控制,你要用三个宏,改进一下就用#if 可以用一个宏直接控制N种情况如:
1 #define MY_PRINTF_VERSION 1 2 3 #if MY_PRINTF_VERSION == 1 4 void printf(*str) // 向终端简单地输出一个字符串 5 { 6 ... 7 } 8 #elif MY_PRINTF_VERSION == 2 9 int printf(char* fmt, char* args, ...) 10 { 11 ... 12 } 13 #elif MY_PRINTF_VERSION == 3 14 int printf(unsigned char com_number, char* str) 15 { 16 ... 17 } 18 #else 19 默认版本 20 #endif
这样,你只需修改一下数字就可以完成版本的选择了
看来好像用#if 比较好了,试想如下情况:你写了一个配置文件叫做config.h用来配置一些宏,通过这些宏来控制代码,如你在config.h的宏
1 #define MY_PRINTF_EN 1
来控制是否需要编译自己的printf函数,而在你的源代码文件printf.c中有如下指令:
1 #include "config.h" 2 3 #if MY_PRINTF_EN == 1 4 int printf(char* fmt, char* args, ...) 5 { 6 ... 7 } 8 #endif
但这样也会有一个问题,就是如果你忘了在config.h中添加宏MY_PRINTF_EN,那么自己写的printf 函数也不会被编译,有些编译器会给出警告:MY_PRINTF_EN 未定义。如果你有两个版本的想有一个默认版本,可以在printf.c中这样实现:
1 #incldue "config.h" 2 3 #if !defined(MY_PRINTF_VERSION) 4 #define MY_PRINTF_VERSION 1 5 #endif 6 7 #if MY_PRINTF_VERSION == 1 8 void printf(*str) // 向终端简单地输出一个字符串 9 { 10 ... 11 } 12 #elif MY_PRINTF_VERSION == 2 13 int printf(char* fmt, char* args, ...) 14 { 15 ... 16 } 17 #elif MY_PRINTF_VERSION == 3 18 int printf(unsigned char com_number, char* str) 19 { 20 ... 21 } 22 #endif
这种情况下还得用到#ifdef或#if defined(),你可以不用动主体的任何代码,只需要修改printf.c文件中MY_RPINTF_VERSION宏的数字就可以改变了,如果用前面那种方法还得拖动代码,在拖动中就有可能造成错误。
再试想,如果软件升级了,或者有了大的改动,原来有三个版本,现在只剩下两个版本了,如:
1 #if MY_PRINTF_VERSION == 2 2 int printf(char* fmt, char* args, ...) 3 { 4 ... 5 } 6 #elif MY_PRINTF_VERSION == 3 7 int printf(unsigned char com_number, char* str) 8 { 9 ... 10 } 11 #endif
因为这些核心代码不想让使用这些代码的人关心,他们只需要修改config.h文件,那就要在printf.c中实现兼容性。如果以前有人在config.h配置宏MY_PRINTF_VERSION 为1,即有:
1 #define MY_PRINTF_VERSION 1
而现在没有1版本了,要想兼容怎么办?那当然可以用更复杂的条件实现如:
1 #if MY_PRINTF_VERSION == 2 || MY_PRINTF_VERSION == 1 2 int printf(char* fmt, char* args, ...) 3 { 4 ... 5 } 6 #elif MY_PRINTF_VERSION == 3 7 int printf(unsigned char com_number, char* str) 8 { 9 ... 10 } 11 #endif
不过还有另外一种方法,即使用#undef命令:
1 #if MY_PRINTF_VERSION == 1 2 #undef MY_PRINTF_VERSION 3 #define MY_PRINTF_VERSION 2 4 #endif 5 6 #if MY_PRINTF_VERSION == 2 7 int printf(char* fmt, char* args, ...) 8 { 9 ... 10 } 11 #elif MY_PRINTF_VERSION == 3 12 int printf(unsigned char com_number, char* str) 13 { 14 ... 15 } 16 #endif
用#if 还有一个好处,如果你把宏名记错了,把MY_PRINTF_EN 定义成了MY_PRINT_EN,那么你用#ifdef MY_PRINTF_EN 或者#if defined(MY_PRINTF_EN)控制的代码就不能被编译,查起来又不好查,用#if MY_PRINTF_EN ==1控制就很好查,因为你把MY_PRINTF_EN定义成MY_PRINT_EN,则MY_PRINTF_EN实际上没有定义,那么编译器会给出警告#if MY_PRINTF_EN == 1中的MY_PRINTF_EN没有定义,找错就比较快。
参考链接:
http://blog.csdn.net/jiaozhentang/article/details/9493653
http://www.cnblogs.com/wengzilin/archive/2012/04/26/2471018.html
http://c.biancheng.net/cpp/html/68.html
http://www.cnblogs.com/rusty/archive/2011/03/27/1996806.html
http://blog.csdn.net/dqjyong/article/details/7668967
SDWebImage源码阅读前的准备(一)预处理条件编译