# 第11章 選擇結構switch()/case/default
## 11.1 一些例子
```
#!bash
void f (int a)
{
switch (a)
{
case 0: printf ("zero
"); break;
case 1: printf ("one
"); break;
case 2: printf ("two
"); break;
default: printf ("something unknown
"); break;
};
};
```
### 11.1.1 X86
反匯編結果如下(MSVC 2010):
清單11.1: MSVC 2010
```
#!bash
tv64 = -4 ; size = 4
_a$ = 8 ; size = 4
_f PROC
push ebp
mov ebp, esp
push ecx
mov eax, DWORD PTR _a$[ebp]
mov DWORD PTR tv64[ebp], eax
cmp DWORD PTR tv64[ebp], 0
je SHORT $LN4@f
cmp DWORD PTR tv64[ebp], 1
je SHORT $LN3@f
cmp DWORD PTR tv64[ebp], 2
je SHORT $LN2@f
jmp SHORT $LN1@f
$LN4@f:
push OFFSET $SG739 ; ’zero’, 0aH, 00H
call _printf
add esp, 4
jmp SHORT $LN7@f
$LN3@f:
push OFFSET $SG741 ; ’one’, 0aH, 00H
call _printf
add esp, 4
jmp SHORT $LN7@f
$LN2@f:
push OFFSET $SG743 ; ’two’, 0aH, 00H
call _printf
add esp, 4
jmp SHORT $LN7@f
$LN1@f:
push OFFSET $SG745 ; ’something unknown’, 0aH, 00H
call _printf
add esp, 4
$LN7@f:
mov esp, ebp
pop ebp
ret 0
_f ENDP
```
輸出函數的switch中有一些case選擇分支,事實上,它是和下面這個形式等價的:
```
#!cpp
void f (int a)
{
if (a==0)
printf ("zero
");
else if (a==1)
printf ("one
");
else if (a==2)
printf ("two
");
else
printf ("something unknown
");
};
```
當switch()中有一些case分支時,我們可以看到此類代碼,雖然不能確定,但是,事實上switch()在機器碼級別上就是對if()的封裝。這也就是說,switch()其實只是對有一大堆類似條件判斷的if()的一個語法糖。
在生成代碼時,除了編譯器把輸入變量移動到一個臨時本地變量tv64中之外,這塊代碼對我們來說并無新意。
如果是在GCC 4.4.1下編譯同樣的代碼,我們得到的結果也幾乎一樣,即使你打開了最高優化(-O3)也是如此。
讓我們在微軟VC編譯器中打開/Ox優化選項: cl 1.c /Fa1.asm /Ox
清單11.2: MSVC
```
#!bash
_a$ = 8 ; size = 4
_f PROC
mov eax, DWORD PTR _a$[esp-4]
sub eax, 0
je SHORT $LN4@f
sub eax, 1
je SHORT $LN3@f
sub eax, 1
je SHORT $LN2@f
mov DWORD PTR _a$[esp-4], OFFSET $SG791 ; ’something unknown’, 0aH, 00H
jmp _printf
$LN2@f:
mov DWORD PTR _a$[esp-4], OFFSET $SG789 ; ’two’, 0aH, 00H
jmp _printf
$LN3@f:
mov DWORD PTR _a$[esp-4], OFFSET $SG787 ; ’one’, 0aH, 00H
jmp _printf
$LN4@f:
mov DWORD PTR _a$[esp-4], OFFSET $SG785 ; ’zero’, 0aH, 00H
jmp _printf
_f ENDP
```
我們可以看到瀏覽器做了更多的難以閱讀的優化(Dirty hacks)。
首先,變量的值會被放入EAX,接著EAX減0。聽起來這很奇怪,但它之后是需要檢查先前EAX寄存器的值是否為0的,如果是,那么程序會設置上零標志位ZF(這也表示了減去0之后,結果依然是0),第一個條件跳轉語句JE(Jump if Equal 或者同義詞 JZ - Jump if Zero)會因此觸發跳轉。如果這個條件不滿足,JE沒有跳轉的話,輸入值將減去1,之后就和之前的一樣了,如果哪一次值是0,那么JE就會觸發,從而跳轉到對應的處理語句上。
(譯注:SUB操作會重置零標志位ZF,但是MOV不會設置標志位,而JE將只有在ZF標志位設置之后才會跳轉。如果需要基于EAX的值來做JE跳轉的話,是需要用這個方法設置標志位的)。
并且,如果沒有JE語句被觸發,最終,printf()函數將收到“something unknown”的參數。
其次:我們看到了一些不尋常的東西——字符串指針被放在了變量里,然后printf()并沒有通過CALL,而是通過JMP來調用的。 這個可以很簡單的解釋清楚,調用者把參數壓棧,然后通過CALL調用函數。CALL通過把返回地址壓棧,然后做無條件跳轉來跳到我們的函數地址。我們的函數在執行時,不管在任何時候都有以下的棧結構(因為它沒有任何移動棧指針的語句):
```
· ESP —— 指向返回地址
· ESP+4 —— 指向變量a (也即參數)
```
另一方面,當我們這兒調用printf()函數的時候,它也需要有與我們這個函數相同的棧結構,不同之處只在于printf()的第一個參數是指向一個字符串的。 這也就是你之前看到的我們的代碼所做的事情。
我們的代碼把第一個參數的地址替換了,然后跳轉到printf(),就像第一個沒有調用我們的函數f()而是先調用了printf()一樣。 printf()把一串字符輸出到stdout 中,然后執行RET語句, 這一句會從棧上彈出返回地址,因此,此時控制流會返回到調用f()的函數上,而不是f()上。
這一切之所以能發生,是因為printf()在f()的末尾。在一些情況下,這有些類似于longjmp()函數。當然,這一切只是為了提高執行速度。
ARM編譯器也有類似的優化,請見5.3.2節“帶有多個參數的printf()函數調用”。
### 11.1.2 ARM: 優化后的 Keil + ARM 模式
```
#!bash
.text:0000014C f1
.text:0000014C 00 00 50 E3 CMP R0, #0
.text:00000150 13 0E 8F 02 ADREQ R0, aZero ; "zero
"
.text:00000154 05 00 00 0A BEQ loc_170
.text:00000158 01 00 50 E3 CMP R0, #1
.text:0000015C 4B 0F 8F 02 ADREQ R0, aOne ; "one
"
.text:00000160 02 00 00 0A BEQ loc_170
.text:00000164 02 00 50 E3 CMP R0, #2
.text:00000168 4A 0F 8F 12 ADRNE R0, aSomethingUnkno ; "something unknown
"
.text:0000016C 4E 0F 8F 02 ADREQ R0, aTwo ; "two
"
.text:00000170
.text:00000170 loc_170 ; CODE XREF: f1+8
.text:00000170 ; f1+14
.text:00000170 78 18 00 EA B __2printf
```
我們再一次看看這個代碼,我們不能確定的說這就是源代碼里面的switch()或者說它是if()的封裝。
但是,我們可以看到這里它也在試圖預測指令(像是ADREQ(相等)),這里它會在R0=0的情況下觸發,并且字符串“zero”的地址將被加載到R0中。如果R0=0,下一個指令BEQ將把控制流定向到loc_170處。順帶一說,機智的讀者們可能會文,之前的ADREQ已經用其他值填充了R0寄存器了,那么BEQ會被正確觸發嗎?答案是“是”。因為BEQ檢查的是CMP所設置的標記位,但是ADREQ根本沒有修改標記位。
還有,在ARM中,一些指令還會加上-S后綴,這表明指令將會根據結果設置標記位。如果沒有-S的話,表明標記位并不會被修改。比如,ADD(而不是ADDS)將會把兩個操作數相加,但是并不會涉及標記位。這類指令對使用CMP設置標記位之后使用標記位的指令,例如條件跳轉來說非常有用。
其他指令對我們來說已經很熟悉了。這里只有一個調用指向printf(),在末尾,我們已經知道了這個小技巧(見5.3.2節)。在末尾處有三個指向printf()的地址。 還有,需要注意的是如果a=2但是a并不在它的選擇分支給定的常數中時,“CMP R0, #2”指令在這個情況下就需要知道a是否等于2。如果結果為假,ADRNE將會讀取字符串“something unknown ”到R0中,因為a在之前已經和0、1做過是否相等的判斷了,這里我們可以假定a并不等于0或者1。并且,如果R0=2,a指向的字符串“two ”將會被ADREQ載入R0。
### 11.1.3 ARM: 優化后的 Keil + thumb 模式
```
#!bash
.text:000000D4 f1
.text:000000D4 10 B5 PUSH {R4,LR}
.text:000000D6 00 28 CMP R0, #0
.text:000000D8 05 D0 BEQ zero_case
.text:000000DA 01 28 CMP R0, #1
.text:000000DC 05 D0 BEQ one_case
.text:000000DE 02 28 CMP R0, #2
.text:000000E0 05 D0 BEQ two_case
.text:000000E2 91 A0 ADR R0, aSomethingUnkno ; "something unknown
"
.text:000000E4 04 E0 B default_case
.text:000000E6 ;
-------------------------------------------------------------------------
.text:000000E6 zero_case ; CODE XREF: f1+4
.text:000000E6 95 A0 ADR R0, aZero ; "zero
"
.text:000000E8 02 E0 B default_case
.text:000000EA ;
-------------------------------------------------------------------------
.text:000000EA one_case ; CODE XREF: f1+8
.text:000000EA 96 A0 ADR R0, aOne ; "one
"
.text:000000EC 00 E0 B default_case
.text:000000EE ;
-------------------------------------------------------------------------
.text:000000EE two_case ; CODE XREF: f1+C
.text:000000EE 97 A0 ADR R0, aTwo ; "two
"
.text:000000F0 default_case ; CODE XREF: f1+10
.text:000000F0 ; f1+14
.text:000000F0 06 F0 7E F8 BL __2printf
.text:000000F4 10 BD POP {R4,PC}
.text:000000F4 ; End of function f1
```
正如我之前提到的,在thumb模式下并沒有什么功能來連接預測結果,所以這里的thumb代碼有點像容易理解的x86 CISC代碼。
## 11.2 許多例子
在有許多case分支的switch()語句中,對編譯器來說,轉換出一大堆JE/JNE語句并不是太方便。
```
#!cpp
void f (int a)
{
switch (a)
{
case 0: printf ("zero
"); break;
case 1: printf ("one
"); break;
case 2: printf ("two
"); break;
case 3: printf ("three
"); break;
case 4: printf ("four
"); break;
default: printf ("something unknown
"); break;
};
};
```
### 11.2.1 x86
反匯編結果如下(MSVC 2010):
清單11.3: MSVC 2010
```
#!bash
tv64 = -4 ; size = 4
_a$ = 8 ; size = 4
_f PROC
push ebp
mov ebp, esp
push ecx
mov eax, DWORD PTR _a$[ebp]
mov DWORD PTR tv64[ebp], eax
cmp DWORD PTR tv64[ebp], 4
ja SHORT $LN1@f
mov ecx, DWORD PTR tv64[ebp]
jmp DWORD PTR $LN11@f[ecx*4]
$LN6@f:
push OFFSET $SG739 ; ’zero’, 0aH, 00H
call _printf
add esp, 4
jmp SHORT $LN9@f
$LN5@f:
push OFFSET $SG741 ; ’one’, 0aH, 00H
call _printf
add esp, 4
jmp SHORT $LN9@f
$LN4@f:
push OFFSET $SG743 ; ’two’, 0aH, 00H
call _printf
add esp, 4
jmp SHORT $LN9@f
$LN3@f:
push OFFSET $SG745 ; ’three’, 0aH, 00H
call _printf
add esp, 4
jmp SHORT $LN9@f
$LN2@f:
push OFFSET $SG747 ; ’four’, 0aH, 00H
call _printf
add esp, 4
jmp SHORT $LN9@f
$LN1@f:
push OFFSET $SG749 ; ’something unknown’, 0aH, 00H
call _printf
add esp, 4
$LN9@f:
mov esp, ebp
pop ebp
ret 0
npad 2
$LN11@f:
DD $LN6@f ; 0
DD $LN5@f ; 1
DD $LN4@f ; 2
DD $LN3@f ; 3
DD $LN2@f ; 4
_f ENDP
```
好的,我們可以看到這兒有一組不同參數的printf()調用。 它們不僅有內存中的地址,編譯器還給它們帶上了符號信息。順帶一提,這些符號標簽也都存在于$LN11@f內部函數表中。
在函數最開始,如果a大于4,控制流將會被傳遞到標簽$LN1@f上,這兒會有一個參數為“something unknown”的printf()調用。
如果a值小于等于4,然后我們把它乘以4,加上$LN1@f的函數地址。這就是在函數表內部構造地址的方法,這樣可以正好指向我們需要的元素。比如a等于2。 那么,2×4=8(在32位進程下,所有的函數表元素的長度都只有4字節),$LN11@f的函數表地址+8——這樣就能取得$LN4@f標簽的位置。 JMP將從函數表中獲得$LN4@f的地址,然后跳轉向它。
這個函數表,有時候也叫做跳轉表(jumptable)。
然后,對應的,printf()的參數就是“two”了。 字面意思, JMP DWORD PTR $LN11@f[ECX*4] 指令意味著“ 跳轉到存儲在$LN11@f + ecx * 4 地址上的雙字”。 npad(64)是一個編譯時語言宏,它用于對齊下一個標簽,這樣存儲的地址就會按照4字節(或者16字節)對齊。這個對于處理器來說是十分合適的,因為通過內存總線、緩存從內存中獲取32位的值是非常方便而且有效率的。
讓我們看看GCC 4.4.1 生成的代碼:
清單11.4: GCC 4.4.1
```
#!bash
public f
f proc near ; CODE XREF: main+10
var_18 = dword ptr -18h
arg_0 = dword ptr 8
push ebp
mov ebp, esp
sub esp, 18h ; char *
cmp [ebp+arg_0], 4
ja short loc_8048444
mov eax, [ebp+arg_0]
shl eax, 2
mov eax, ds:off_804855C[eax]
jmp eax
loc_80483FE: ; DATA XREF: .rodata:off_804855C
mov [esp+18h+var_18], offset aZero ; "zero"
call _puts
jmp short locret_8048450
loc_804840C: ; DATA XREF: .rodata:08048560
mov [esp+18h+var_18], offset aOne ; "one"
call _puts
jmp short locret_8048450
loc_804841A: ; DATA XREF: .rodata:08048564
mov [esp+18h+var_18], offset aTwo ; "two"
call _puts
jmp short locret_8048450
loc_8048428: ; DATA XREF: .rodata:08048568
mov [esp+18h+var_18], offset aThree ; "three"
call _puts
jmp short locret_8048450
loc_8048436: ; DATA XREF: .rodata:0804856C
mov [esp+18h+var_18], offset aFour ; "four"
call _puts
jmp short locret_8048450
loc_8048444: ; CODE XREF: f+A
mov [esp+18h+var_18], offset aSomethingUnkno ; "something unknown"
call _puts
locret_8048450: ; CODE XREF: f+26
; f+34...
leave
retn
f endp
off_804855C dd offset loc_80483FE ; DATA XREF: f+12
dd offset loc_804840C
dd offset loc_804841A
dd offset loc_8048428
dd offset loc_8048436
```
基本和VC生成的相同,除了少許的差別:參數arg_0的乘以4操作被左移2位替換了(這集合和乘以4一樣)(見17.3.1節)。 然后標簽地址從off_804855C處的數組獲取,地址計算之后存儲到EAX中,然后通過JMP EAX跳轉到實際的地址上。
### 11.2.2 ARM: 優化后的 Keil + ARM 模式
```
#!bash
00000174 f2
00000174 05 00 50 E3 CMP R0, #5 ; switch 5 cases
00000178 00 F1 8F 30 ADDCC PC, PC, R0,LSL#2 ; switch jump
0000017C 0E 00 00 EA B default_case ; jumptable 00000178 default case
00000180 ; -------------------------------------------------------------------------
00000180
00000180 loc_180 ; CODE XREF: f2+4
00000180 03 00 00 EA B zero_case ; jumptable 00000178 case 0
00000184 ; -------------------------------------------------------------------------
00000184
00000184 loc_184 ; CODE XREF: f2+4
00000184 04 00 00 EA B one_case ; jumptable 00000178 case 1
00000188 ; -------------------------------------------------------------------------
00000188
00000188 loc_188 ; CODE XREF: f2+4
00000188 05 00 00 EA B two_case ; jumptable 00000178 case 2
0000018C ; -------------------------------------------------------------------------
0000018C
0000018C loc_18C ; CODE XREF: f2+4
0000018C 06 00 00 EA B three_case ; jumptable 00000178 case 3
00000190 ; -------------------------------------------------------------------------
00000190
00000190 loc_190 ; CODE XREF: f2+4
00000190 07 00 00 EA B four_case ; jumptable 00000178 case 4
00000194 ; -------------------------------------------------------------------------
00000194
00000194 zero_case ; CODE XREF: f2+4
00000194 ; f2:loc_180
00000194 EC 00 8F E2 ADR R0, aZero ; jumptable 00000178 case 0
00000198 06 00 00 EA B loc_1B8
0000019C ; -------------------------------------------------------------------------
0000019C
0000019C one_case ; CODE XREF: f2+4
0000019C ; f2:loc_184
0000019C EC 00 8F E2 ADR R0, aOne ; jumptable 00000178 case 1
000001A0 04 00 00 EA B loc_1B8
000001A4 ; -------------------------------------------------------------------------
000001A4
000001A4 two_case ; CODE XREF: f2+4
000001A4 ; f2:loc_188
000001A4 01 0C 8F E2 ADR R0, aTwo ; jumptable 00000178 case 2
000001A8 02 00 00 EA B loc_1B8
000001AC ; -------------------------------------------------------------------------
000001AC
000001AC three_case ; CODE XREF: f2+4
000001AC ; f2:loc_18C
000001AC 01 0C 8F E2 ADR R0, aThree ; jumptable 00000178 case 3
000001B0 00 00 00 EA B loc_1B8
000001B4 ; -------------------------------------------------------------------------
000001B4
000001B4 four_case ; CODE XREF: f2+4
000001B4 ; f2:loc_190
000001B4 01 0C 8F E2 ADR R0, aFour ; jumptable 00000178 case 4
000001B8
000001B8 loc_1B8 ; CODE XREF: f2+24
000001B8 ; f2+2C
000001B8 66 18 00 EA B __2printf
000001BC ; -------------------------------------------------------------------------
000001BC
000001BC default_case ; CODE XREF: f2+4
000001BC ; f2+8
000001BC D4 00 8F E2 ADR R0, aSomethingUnkno ; jumptable 00000178 default case
000001C0 FC FF FF EA B loc_1B8
000001C0 ; End of function f2
```
這個代碼利用了ARM的特性,這里ARM模式下所有指令都是4個字節。
讓我們記住a的最大值是4,任何更大額值都會導致它輸出“something unknown ”。
最開始的“CMP R0, #5”指令將a的值與5比較。
下一個“ADDCC PC, PC, R0, LSL#2”指令將僅在R0<5的時候執行(CC = Carry clear , 小于)。所以,如果ADDCC并沒有觸發(R0>=5時),它將會跳轉到default _case標簽上。
但是,如果R0<5,而且ADDCC觸發了,將會發生下列事情:
R0中的值會乘以4,事實上,LSL#2代表著“左移2位”,但是像我們接下來(見17.3.1節)要看到的“移位”一樣,左移2位代表乘以4。
然后,我們得到了R0 * 4的值,這個值將會和PC中現有的值相加,因此跳轉到下述其中一個B(Branch 分支)指令上。
在ADDCC執行時,PC中的值(0x180)比ADDCC指令的值(0x178)提前8個字節,換句話說,提前2個指令。
這也就是為ARM處理器通道工作的方式:當ADDCC指令執行的時候,此時處理器將開始處理下一個指令,這也就是PC會指向這里的原因。
如果a=0,那么PC將不會和任何值相加,PC中實際的值將寫入PC中(它相對之領先8個字節),然后跳轉到標簽loc_180處。這就是領先ADDCC指令8個字節的地方。
在a=1時,PC+8+a_4 = PC+8+1_4 = PC+16= 0x184 將被寫入PC中,這是loc_184標簽的地址。
每當a上加1,PC都會增加4,4也是ARM模式的指令長度,而且也是B指令的長度。這組里面有5個這樣的指令。
這5個B指令將傳遞控制流,也就是傳遞switch()中指定的字符串和對應的操作等等。
### 11.2.3 ARM: 優化后的 Keil + thumb 模式
```
#!bash
000000F6 EXPORT f2
000000F6 f2
000000F6 10 B5 PUSH {R4,LR}
000000F8 03 00 MOVS R3, R0
000000FA 06 F0 69 F8 BL __ARM_common_switch8_thumb ; switch 6 cases
000000FA ;
-------------------------------------------------------------------------
000000FE 05 DCB 5
000000FF 04 06 08 0A 0C 10 DCB 4, 6, 8, 0xA, 0xC, 0x10 ; jump table for switch
statement
00000105 00 ALIGN 2
00000106
00000106 zero_case ; CODE XREF: f2+4
00000106 8D A0 ADR R0, aZero ; jumptable 000000FA case 0
00000108 06 E0 B loc_118
0000010A ;
-------------------------------------------------------------------------
0000010A
0000010A one_case ; CODE XREF: f2+4
0000010A 8E A0 ADR R0, aOne ; jumptable 000000FA case 1
0000010C 04 E0 B loc_118
0000010E ;
-------------------------------------------------------------------------
0000010E
0000010E two_case ; CODE XREF: f2+4
0000010E 8F A0 ADR R0, aTwo ; jumptable 000000FA case 2
00000110 02 E0 B loc_118
00000112 ;
-------------------------------------------------------------------------
00000112
00000112 three_case ; CODE XREF: f2+4
00000112 90 A0 ADR R0, aThree ; jumptable 000000FA case 3
00000114 00 E0 B loc_118
00000116 ;
-------------------------------------------------------------------------
00000116
00000116 four_case ; CODE XREF: f2+4
00000116 91 A0 ADR R0, aFour ; jumptable 000000FA case 4
00000118
00000118 loc_118 ; CODE XREF: f2+12
00000118 ; f2+16
00000118 06 F0 6A F8 BL __2printf
0000011C 10 BD POP {R4,PC}
0000011E ;
-------------------------------------------------------------------------
0000011E
0000011E default_case ; CODE XREF: f2+4
0000011E 82 A0 ADR R0, aSomethingUnkno ; jumptable 000000FA default
case
00000120 FA E7 B loc_118
000061D0 EXPORT __ARM_common_switch8_thumb
000061D0 __ARM_common_switch8_thumb ; CODE XREF: example6_f2+4
000061D0 78 47 BX PC
000061D0 ;
---------------------------------------------------------------------------
000061D2 00 00 ALIGN 4
000061D2 ; End of function __ARM_common_switch8_thumb
000061D2
000061D4 CODE32
000061D4
000061D4 ; =============== S U B R O U T I N E
=======================================
000061D4
000061D4
000061D4 __32__ARM_common_switch8_thumb ; CODE XREF:
__ARM_common_switch8_thumb
000061D4 01 C0 5E E5 LDRB R12, [LR,#-1]
000061D8 0C 00 53 E1 CMP R3, R12
000061DC 0C 30 DE 27 LDRCSB R3, [LR,R12]
000061E0 03 30 DE 37 LDRCCB R3, [LR,R3]
000061E4 83 C0 8E E0 ADD R12, LR, R3,LSL#1
000061E8 1C FF 2F E1 BX R12
000061E8 ; End of function __32__ARM_common_switch8_thumb
```
一個不能確定的事實是thumb、thumb-2中的所有指令都有同樣的大小。甚至可以說是在這些模式下,指令的長度是可變的,就像x86一樣。
所以這一定有一個特別的表單,里面包含有多少個case(除了默認的case),然后和它們的偏移,并且給他們每個都加上一個標簽,這樣控制流就可以傳遞到正確的位置。 這里有一個特別的函數來處理表單和處理控制流,被命名為__ARM_common_switch8_thumb。它由“BX PC”指令開始,這個函數用來將處理器切換到ARM模式,然后你就可以看到處理表單的函數。不過對我們來說,在這里解釋它太復雜了,所以我們將省去一些細節。
但是有趣的是,這個函數使用LR寄存器作為表單的指針。還有,在這個函數調用后,LR將包含有緊跟著“BL __ARM_common_switch8_thumb”指令的地址,然后表單就由此開始。
當然,這里也不值得去把生成的代碼作為單獨的函數,然后再去重用它們。因此在switch()處理相似的位置、相似的case時編譯器并不會生成相同的代碼。
IDA成功的發覺到它是一個服務函數以及函數表,然后給各個標簽加上了合適的注釋,比如jumptable 000000FA case 0。
- 第一章 CPU簡介
- 第二章 Hello,world!
- 第三章? 函數開始和結束
- 第四章 棧
- Chapter 5 printf() 與參數處理
- Chapter 6 scanf()
- CHAPER7 訪問傳遞參數
- Chapter 8 一個或者多個字的返回值
- Chapter 9 指針
- Chapter 10 條件跳轉
- 第11章 選擇結構switch()/case/default
- 第12章 循環結構
- 第13章 strlen()
- Chapter 14 Division by 9
- chapter 15 用FPU工作
- Chapter 16 數組
- Chapter 17 位域
- 第18章 結構體
- 19章 聯合體
- 第二十章 函數指針
- 第21章 在32位環境中的64位值
- 第二十二章 SIMD
- 23章 64位化
- 24章 使用x64下的SIMD來處理浮點數
- 25章 溫度轉換
- 26章 C99的限制
- 27章 內聯函數
- 第28章 得到不正確反匯編結果
- 第29章 花指令
- 第30章 16位Windows
- 第31章 類
- 三十二 ostream