1. 程式人生 > >iOS Switch內部原理

iOS Switch內部原理

switch特性介紹

1、假設switch語句的分支比較少的時候(例如3,少於4的時候沒有意義)沒有必要使用此結構,相當於if。
2、各個分支常量的差值較大的時候,編譯器會在效率還是記憶體進行取捨,這個時候編譯器還是會編譯成類似於if,else的結構。
3、在分支比較多的時候:在編譯的時候會生成一個表(跳轉表每個地址四個位元組)。

switch彙編程式碼案例

三個及以下case

1.建立工程在main函式頁面寫下如下程式碼:

void funA(int a){
    switch (a) {
        case 1:
            printf("向上");
            break;
        case 2:
            printf("向下");
            break;
        case 3:
            printf("向左");
            break;
        default:
            printf("原地不動");
            break;
    }
}

2.Debug -> Debug Overflow -> Always show Disassembly 調成編譯斷點模式在方法處打斷點,真機執行檢視彙編程式碼。

switch 3個case彙編程式碼

3.上圖基本流程如下(省略系統正常操作):

1.將引數a-1判斷a和1是否相等
2.相等則執行case程式碼
3.不相等則將引數 a - 2再判斷 a 和 2是否相等
4.根據case遞增判斷知道Default 結束

注:這和if else判斷相似一個一個比較,是最基本的方法。

四個case

1.輸入以下程式碼

void funA(int a){
    switch (a) {
        case 1:
            printf("向上");
            break;
        case 2:
            printf("向下");
            break;
        case 3:
            printf("向左");
            break;
        case 4:
            printf("向右");
            break;
        default:
            printf("原地不動");
            break;
    }
}

2.編譯真機執行獲得如下結果:

switch4選項以上

3.如上圖解析:

1.先將引數減一和減四(4位case的數量)。
2.先判斷是否為Default選項(上面減四就是為了判斷是否為Default的情況)。
3.在實體地址部分建立記憶體表並且將case按順序放進記憶體表中
4.獲取記憶體地址,然後根據引數減一後偏移2個單位,查詢case在表中的具體地址準確找到對應的地址記憶體並且將值賦給暫存器。
5.根據獲得的地址跳轉指定的case直接找到目標執行。

注:記憶體地址的偏移是從0開始,所以引數需要減一來適應index。

4.神奇的記憶體計算,下面附上記憶體計算過程的圖片,因為重新執行程式左邊的記憶體地址會有差異但是pc執行程式碼是一樣的:

記憶體地址計算

1.首先獲取實體記憶體地址為0x1040ce8a8
2.根據引數傳的是3,然後減一得2(10)偏移兩個單位即1000為8位得到BC FF FF FF記憶體地址是從右往左讀的
3.將獲得的實體地址0x1040ce8a8加上計算得出的偏移地址後就等到了跳轉執行地址(即在記憶體表格中確定具體記憶體位置)。
4.直接跳轉執行。

注:計算方法為將實體地址0x1040ce8a8 + 0xffffffffbc = 0x1040ce8a8 - 0x44(取反加1 補碼 詳見補碼) = 0x1040ce864 計算結果跟上圖列印結果完全一致是不是很神奇?

分部差異大的例子

1.輸入以下程式碼

void funA(int a){
    switch (a) {
        case 1:
            printf("向上");
            break;
        case 200:
            printf("向下");
            break;
        case 3000:
            printf("向左");
            break;
        case 178:
            printf("向右");
            break;
        default:
            printf("原地不動");
            break;
    }
}

2.編譯真機執行獲得如下結果:

跳躍性switch

3.解析:
當case的判斷條件跳躍性太大,編譯器就會變為if判斷一樣採用一一比較的方式進行判斷效率是不高的。

所以在寫switch語句的時候儘量要將其判斷的case連續起來這樣即減少執行時間又節省系統記憶體

執行switch時,會生成一張跳轉表,表項數為(最大case值-最小case值+1),跳轉表是一個數組,陣列是一段連續的記憶體,jt陣列中包含了7個表項(陣列索引對應值),每個都是指向對應程式碼塊的指標。
編譯器將switch值n-最小case值(100),把取值範圍移動到0至6之間,創建出一個新的程式變數index。首先判斷index是否大於6,來判斷是否在範圍之外,如果超出直接執行default,即log_def指標對應程式碼塊。
否則,根據索引的值直接跳轉不同位置。                  
                                                     

   執行if-else是逐個條件進行判斷,直到命中;與if-else語句相比,使用跳轉表的優點是執行switch語句的時間與數量無關,且讀取switch引數時只讀取一次,就可跳到對應分支;缺點是維繫了一個連續的陣列,實際時使用空間換時間。
 

差異小的部分

這邊簡單介紹下,差異小的部分編譯器會根據系統記憶體和時間之間取捨,比如4個case差值在8之間會建八個元素的表,剛才4個記憶體位,現在就是8個記憶體位,case以外的間隔預設為Default。具體的時間空間問題是編譯器決定的。

總結

  • Switch在case連續的且大於等於4個的情況向下採用建表查詢的方式,效率是大於if else語句的。
  • 小於3個case和case語句不規律差異較大建表需要耗費很大記憶體的情況下是相當於if else語句的。
  • 編譯器是根據時間和空間的消耗來決定那種方式效率更高,所以在Switch寫判斷條件的時候最好做到連續緊密,可以最大限度的節省時間和記憶體。



原文連結