一、工作中遇到的怪事
在编写代码是,发现了一个崩溃:
// XXXX.m
#if MOU_GE_HONG_DING_YI == 1
_library = // 涉密,删除代码;从路径A获取文件
#else
// 崩溃发生在这里
_library = // 涉密,删除代码;从路径B获取文件
#endif
但是没有改动到这里,却发生了崩溃!
- 经分析,初步判断可能是需要走if分支,但是却走到了else分支
- 发现在路径A确实存在文件、而路径B却不存在这个文件
- 那么问题就发生在编译XXXX.m文件时,宏MOU_GE_HONG_DING_YI尚未被定义
二、问题详解
2.1 代码展示
// 宏在此文件中定义
// Header.h
#define MOU_GE_HONG_DING_YI 1
// 宏在此文件中使用
// XXXX.m
#if MOU_GE_HONG_DING_YI == 1
_library = // 涉密,删除代码;从路径A获取文件
#else
// 崩溃发生在这里
_library = // 涉密,删除代码;从路径B获取文件
#endif
2.2 大胆猜测
根据问题表现,猜测是编译器在预处理XXXX.m文件时,其尚未了解到有MOU_GE_HONG_DING_YI的宏定义,所以就把if分支的代码去掉了,只保留了else分支的代码。
2.3 小心求证
2.3.1 复现问题
为了验证自己的猜测,编写如下测试代码;
// Person.h
@interface Person : NSObject
+ (void)testMacro;
@end
// Person.m
@implementation Person
// DEBUG 是Xcode预置的
// THIS_IS_A_TEST_MACRO 在Person的子类Student类中声明
+ (void)testMacro {
#ifdef DEBUG
NSLog(@"Person Define DEBUG");
#else
NSLog(@"Person NO Define DEBUG");
#endif
#if THIS_IS_A_TEST_MACRO == 1
NSLog(@"Person Define THIS_IS_A_TEST_MACRO");
#else
NSLog(@"Person NO THIS_IS_A_TEST_MACRO");
#endif
}
@end
// Student.h
#define THIS_IS_A_TEST_MACRO 1
@interface Student : Person
+ (void)StudentTestMacro;
@end
// Student.m
@implementation Student
+ (void)StudentTestMacro {
#ifdef DEBUG
NSLog(@"Student Define DEBUG");
#else
NSLog(@"Student NO Define DEBUG");
#endif
#if THIS_IS_A_TEST_MACRO == 1
NSLog(@"Student Define THIS_IS_A_TEST_MACRO");
#else
NSLog(@"Student NO THIS_IS_A_TEST_MACRO");
#endif
[super testMacro];
}
@end
// main.m
int main(int argc, const char * argv[]) {
[Student StudentTestMacro];
return 0;
}
/*
2022-05-19 14:51:49.164576+0800 cmdProject[75822:301165] Student Define DEBUG
2022-05-19 14:51:49.165422+0800 cmdProject[75822:301165] Student Define THIS_IS_A_TEST_MACRO
2022-05-19 14:51:49.165453+0800 cmdProject[75822:301165] Person Define DEBUG
2022-05-19 14:51:49.165474+0800 cmdProject[75822:301165] Person NO THIS_IS_A_TEST_MACRO
*/
- 由于Student继承自Person,所以编译器优先处理Person的相关代码
- 在对Person进行预处理的时候,尚未了解到THIS_IS_A_TEST_MACRO宏已被定义,所以至保留了else分支
通过代码验证了之前的猜测——某些预处理与文件的依赖有关系,如果宏定义在子孙文件里,则父文件的预处理会出乎预料
2.2.2 代码实证
2.2.2.1 XXXX.m原代码预处理
// XXXX.i
// 可见在原始代码中,由于引入了Header.h,所有走了if分支
# 1 "...../common/Header.h" 1
_library = // 涉密,删除代码;从路径A获取文件
2.2.2.2 修改XXXX.m后代码预处理
// XXXX.i
// 可见在原始代码中,由于引入了Header.h,所有走了else分支
_library = // 涉密,删除代码;从路径B获取文件
三、最佳实现
3.1 不推荐使用宏
绝大部分的程序员都听说过,不要使用宏。它有许多问题:
- 丢失类型
- 可能包含隐蔽错误(如加的小括号过少)
- 不支持调试
- 在预处理阶段被处理掉,重要信息丢失(语法、语义等后续阶段的编译处理无法获取宏信息)
- .......
宏的不好,不一而足,以上仅列举部分
3.2 不得不使用宏
宏就像“渣男、渣女”一样,除了渣,别的地方都很好!
渣男
扯远啦!
宏虽然有万般不好,但是许多地方仍然被使用!除了其他的一些宏编写的最佳时间外,推荐大家在XCode的工程配置里定义宏(除非你非常确定宏的应用范围在且只在当前文件范围内),这样就可以规避此问题。
网友评论