預處理(宏)——想說愛你不容易 一、工作中遇到的怪事 二、問題詳解 三、最佳實現

一、工作中遇到的怪事

在編寫代碼是,發現了一個崩潰:

// 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的工程配置裏定義宏(除非你非常確定宏的應用範圍在且只在當前文件範圍內),這樣就可以規避此問題。

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