预处理(宏)——想说爱你不容易 一、工作中遇到的怪事 二、问题详解 三、最佳实现

一、工作中遇到的怪事

在编写代码是,发现了一个崩溃:

// XXXX.m

#if MOU_GE_HONG_DING_YI == 1
        _library = // 涉密,删除代码;从路径A获取文件
#else
        // 崩溃发生在这里
        _library = // 涉密,删除代码;从路径B获取文件
#endif

但是没有改动到这里,却发生了崩溃!

  1. 经分析,初步判断可能是需要走if分支,但是却走到了else分支
  2. 发现在路径A确实存在文件、而路径B却不存在这个文件
  3. 那么问题就发生在编译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
*/
  1. 由于Student继承自Person,所以编译器优先处理Person的相关代码
  2. 在对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 不推荐使用宏

绝大部分的程序员都听说过,不要使用宏。它有许多问题:

  1. 丢失类型
  2. 可能包含隐蔽错误(如加的小括号过少)
  3. 不支持调试
  4. 在预处理阶段被处理掉,重要信息丢失(语法、语义等后续阶段的编译处理无法获取宏信息)
  5. .......
    宏的不好,不一而足,以上仅列举部分

3.2 不得不使用宏

宏就像“渣男、渣女”一样,除了渣,别的地方都很好!



扯远啦!
宏虽然有万般不好,但是许多地方仍然被使用!除了其他的一些宏编写的最佳时间外,推荐大家在XCode的工程配置里定义宏(除非你非常确定宏的应用范围在且只在当前文件范围内),这样就可以规避此问题。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章