<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??一站式輕松地調用各大LLM模型接口,支持GPT4、智譜、豆包、星火、月之暗面及文生圖、文生視頻 廣告
                # 第18章 結構體 C/C++的結構體可以這么定義:它是一組存儲在內存中的變量的集合,成員變量類型不要求相同。 ## 18.1 SYSTEMTIME 的例子 讓我們看看Win32結構體SYSTEMTIME的定義: 清單18.1: WinBase.h ``` #!cpp typedef struct _SYSTEMTIME { WORD wYear; WORD wMonth; WORD wDayOfWeek; WORD wDay; WORD wHour; WORD wMinute; WORD wSecond; WORD wMilliseconds; } SYSTEMTIME, *PSYSTEMTIME; ``` 讓我們寫一個獲取當前時間的C程序: ``` #!cpp #include <windows.h> #include <stdio.h> void main() { SYSTEMTIME t; GetSystemTime (&t); printf ("%04d-%02d-%02d %02d:%02d:%02d ", t.wYear, t.wMonth, t.wDay, t.wHour, t.wMinute, t.wSecond); return; }; ``` 反匯編結果如下(MSVC 2010): 清單18.2: MSVC 2010 ``` #!bash _t$ = -16 ; size = 16 _main PROC push ebp mov ebp, esp sub esp, 16 ; 00000010H lea eax, DWORD PTR _t$[ebp] push eax call DWORD PTR __imp__GetSystemTime@4 movzx ecx, WORD PTR _t$[ebp+12] ; wSecond push ecx movzx edx, WORD PTR _t$[ebp+10] ; wMinute push edx movzx eax, WORD PTR _t$[ebp+8] ; wHour push eax movzx ecx, WORD PTR _t$[ebp+6] ; wDay push ecx movzx edx, WORD PTR _t$[ebp+2] ; wMonth push edx movzx eax, WORD PTR _t$[ebp] ; wYear push eax push OFFSET $SG78811 ; ’%04d-%02d-%02d %02d:%02d:%02d’, 0aH, 00H call _printf add esp, 28 ; 0000001cH xor eax, eax mov esp, ebp pop ebp ret 0 _main ENDP ``` 在本地棧上程序為這個結構體分配了16個字節:這正是sizeof(WORD)*8的大小(因為結構體里有8個WORD)。 請注意結構體是由wYear開始的,因此,我們既可以說這是“傳給GetSystemTime()函數的,一個指向SYSTEMTIME結構體的指針”,也可以說是它“傳遞了wYear的指針”。這兩種說法是一樣的!GetSystemTime()函數會把當前的年份寫入指向的WORD指針中,然后把指針向后移動2個字節(譯注:WORD大小為2字節),再寫入月份,以此類推。 事實上,結構體的成員其實就是一個個緊貼在一起的變量。我可以用下面的方法來訪問SYSTEMTIME結構體,代碼如下: ``` #!cpp #include <windows.h> #include <stdio.h> void main() { WORD array[8]; GetSystemTime (array); printf ("%04d-%02d-%02d %02d:%02d:%02d ", array[0] /* wYear */, array[1] /* wMonth */, array[3] /* wDay */, array[4] /* wHour */, array[5] /* wMinute */, array[6] /* wSecond */); return; }; ``` 編譯器會稍稍給出一點警告: ``` #!cpp systemtime2.c(7) : warning C4133: ’function’ : incompatible types - from ’WORD [8]’ to ’LPSYSTEMTIME’ ``` 不過至少,它會產生如下代碼: 清單18.3: MSVC 2010 ``` #!bash $SG78573 DB ’%04d-%02d-%02d %02d:%02d:%02d’, 0aH, 00H _array$ = -16 ; size = 16 _main PROC push ebp mov ebp, esp sub esp, 16 ; 00000010H lea eax, DWORD PTR _array$[ebp] push eax call DWORD PTR __imp__GetSystemTime@4 movzx ecx, WORD PTR _array$[ebp+12] ; wSecond push ecx movzx edx, WORD PTR _array$[ebp+10] ; wMinute push edx movzx eax, WORD PTR _array$[ebp+8] ; wHoure push eax movzx ecx, WORD PTR _array$[ebp+6] ; wDay push ecx movzx edx, WORD PTR _array$[ebp+2] ; wMonth push edx movzx eax, WORD PTR _array$[ebp] ; wYear push eax push OFFSET $SG78573 call _printf add esp, 28 ; 0000001cH xor eax, eax mov esp, ebp pop ebp ret 0 _main ENDP ``` 當然,它也能一樣正常工作! 一個很有趣的情況是這兩次編譯結果居然一樣,所以光看編譯結果,我們還看不出來到底別人用的結構體還是單單用的變量數組。 不過,沒幾個人會這么無聊的用這種方法寫代碼,因為這太麻煩了。還有,結構體也有可能會被開發者更改,交換,等等,所以還是用結構體方便。 ## 18.2 讓我們用malloc()為結構體分配空間 但是,有時候把結構體放在堆中而不是棧上卻更簡單: ``` #!cpp #include <windows.h> #include <stdio.h> void main() { SYSTEMTIME *t; t=(SYSTEMTIME *)malloc (sizeof (SYSTEMTIME)); GetSystemTime (t); printf ("%04d-%02d-%02d %02d:%02d:%02d ", t->wYear, t->wMonth, t->wDay, t->wHour, t->wMinute, t->wSecond); free (t); return; }; ``` 讓我們用優化/Ox編譯一下它,看看我們得到什么東西 清單18.4: 優化的MSVC ``` #!bash _main PROC push esi push 16 ; 00000010H call _malloc add esp, 4 mov esi, eax push esi call DWORD PTR __imp__GetSystemTime@4 movzx eax, WORD PTR [esi+12] ; wSecond movzx ecx, WORD PTR [esi+10] ; wMinute movzx edx, WORD PTR [esi+8] ; wHour push eax movzx eax, WORD PTR [esi+6] ; wDay push ecx movzx ecx, WORD PTR [esi+2] ; wMonth push edx movzx edx, WORD PTR [esi] ; wYear push eax push ecx push edx push OFFSET $SG78833 call _printf push esi call _free add esp, 32 ; 00000020H xor eax, eax pop esi ret 0 _main ENDP ``` 所以,sizeof(SYSTEMTIME) = 16, 這正是malloc所分配的字節數。它返回了剛剛分配的地址空間,這個指針存在EAX寄存器里。然后,這個指針會被移動到ESI結存器中, GetSystemTime()會用它來存儲返回值,這也就是為什么這里分配完之后并沒有把EAX放到某個地方保存起來,而是直接使用它的原因。 新指令:MOVZX(Move with Zero eXtent, 0擴展移動)。它可以說是和MOVSX基本一樣(13.1.1節),但是,它把其他位都設置為0。這是因為printf()需要一個32位的整數,但是我們的結構體里面是WORD,這只有16位廠。這也就是為什么從WORD復制到INT時第16~31位必須清零的原因了。因為,如果不清除的話,剩余位可能有之前操作留下來的干擾數據。 在下面這個例子里面,我可以用WORD數組來重現這個結構: ``` #!cpp #include <windows.h> #include <stdio.h> void main() { WORD *t; t=(WORD *)malloc (16); GetSystemTime (t); printf ("%04d-%02d-%02d %02d:%02d:%02d ", t[0] /* wYear */, t[1] /* wMonth */, t[3] /* wDay */, t[4] /* wHour */, t[5] /* wMinute */, t[6] /* wSecond */); free (t); return; }; ``` 我們得到: ``` #!bash $SG78594 DB ’%04d-%02d-%02d %02d:%02d:%02d’, 0aH, 00H _main PROC push esi push 16 ; 00000010H call _malloc add esp, 4 mov esi, eax push esi call DWORD PTR __imp__GetSystemTime@4 movzx eax, WORD PTR [esi+12] movzx ecx, WORD PTR [esi+10] movzx edx, WORD PTR [esi+8] push eax movzx eax, WORD PTR [esi+6] push ecx movzx ecx, WORD PTR [esi+2] push edx movzx edx, WORD PTR [esi] push eax push ecx push edx push OFFSET $SG78594 call _printf push esi call _free add esp, 32 ; 00000020H xor eax, eax pop esi ret 0 _main ENDP ``` 同樣,我們可以看到編譯結果和之前一樣。個人重申一次,你不應該在寫代碼的時候用這么晦澀的方法來表達它。 ## 18.3 結構體tm ### 18.3.1 linux 在Linux下,我們看看time.h中的tm結構體是什么樣子的: ``` #!cpp #include <stdio.h> #include <time.h> void main() { struct tm t; time_t unix_time; unix_time=time(NULL); localtime_r (&unix_time, &t); printf ("Year: %d ", t.tm_year+1900); printf ("Month: %d ", t.tm_mon); printf ("Day: %d ", t.tm_mday); printf ("Hour: %d ", t.tm_hour); printf ("Minutes: %d ", t.tm_min); printf ("Seconds: %d ", t.tm_sec); }; ``` 在GCC 4.4.1下編譯得到: 清單18.6:GCC 4.4.1 ``` #!bash main proc near push ebp mov ebp, esp and esp, 0FFFFFFF0h sub esp, 40h mov dword ptr [esp], 0 ; first argument for time() call time mov [esp+3Ch], eax lea eax, [esp+3Ch] ; take pointer to what time() returned lea edx, [esp+10h] ; at ESP+10h struct tm will begin mov [esp+4], edx ; pass pointer to the structure begin mov [esp], eax ; pass pointer to result of time() call localtime_r mov eax, [esp+24h] ; tm_year lea edx, [eax+76Ch] ; edx=eax+1900 mov eax, offset format ; "Year: %d " mov [esp+4], edx mov [esp], eax call printf mov edx, [esp+20h] ; tm_mon mov eax, offset aMonthD ; "Month: %d " mov [esp+4], edx mov [esp], eax call printf mov edx, [esp+1Ch] ; tm_mday mov eax, offset aDayD ; "Day: %d " mov [esp+4], edx mov [esp], eax call printf mov edx, [esp+18h] ; tm_hour mov eax, offset aHourD ; "Hour: %d " mov [esp+4], edx mov [esp], eax call printf mov edx, [esp+14h] ; tm_min mov eax, offset aMinutesD ; "Minutes: %d " mov [esp+4], edx mov [esp], eax call printf mov edx, [esp+10h] mov eax, offset aSecondsD ; "Seconds: %d " mov [esp+4], edx ; tm_sec mov [esp], eax call printf leave retn main endp ``` 可是,IDA并沒有為本地棧上變量建立本地變量名。但是因為我們已經學了匯編了,我們也不需要在這么簡單的例子里面如此依賴它。 請也注意一下lea edx, [eax+76ch],這個指令把eax的值加上0x76c,但是并不修改任何標記位。請也參考LEA的相關章節(B.6.2節) 為了表現出結構體只是一個個的變量連續排列的東西,讓我們重新測試一下這個例子,我們看看time.h: 清單18.7 time.h ``` #!cpp struct tm { int tm_sec; int tm_min; int tm_hour; int tm_mday; int tm_mon; int tm_year; int tm_wday; int tm_yday; int tm_isdst; }; #include <stdio.h> #include <time.h> void main() { int tm_sec, tm_min, tm_hour, tm_mday, tm_mon, tm_year, tm_wday, tm_yday, tm_isdst; time_t unix_time; unix_time=time(NULL); localtime_r (&unix_time, &tm_sec); printf ("Year: %d ", tm_year+1900); printf ("Month: %d ", tm_mon); printf ("Day: %d ", tm_mday); printf ("Hour: %d ", tm_hour); printf ("Minutes: %d ", tm_min); printf ("Seconds: %d ", tm_sec); }; ``` 注:指向tm_sec的指針會傳遞給localtime_r,或者說第一個“結構體”元素。 編譯器會這么警告我們 清單18.8 GCC4.7.3 ``` #!bash GCC_tm2.c: In function ’main’: GCC_tm2.c:11:5: warning: passing argument 2 of ’localtime_r’ from incompatible pointer type [ enabled by default] In file included from GCC_tm2.c:2:0: /usr/include/time.h:59:12: note: expected ’struct tm *’ but argument is of type ’int *’ ``` 但是至少,它會生成這段代碼: 清單18.9 GCC 4.7.3 ``` #!bash main proc near var_30 = dword ptr -30h var_2C = dword ptr -2Ch unix_time = dword ptr -1Ch tm_sec = dword ptr -18h tm_min = dword ptr -14h tm_hour = dword ptr -10h tm_mday = dword ptr -0Ch tm_mon = dword ptr -8 tm_year = dword ptr -4 push ebp mov ebp, esp and esp, 0FFFFFFF0h sub esp, 30h call __main mov [esp+30h+var_30], 0 ; arg 0 mov [esp+30h+unix_time], eax lea eax, [esp+30h+tm_sec] mov [esp+30h+var_2C], eax lea eax, [esp+30h+unix_time] mov [esp+30h+var_30], eax call localtime_r mov eax, [esp+30h+tm_year] add eax, 1900 mov [esp+30h+var_2C], eax mov [esp+30h+var_30], offset aYearD ; "Year: %d " call printf mov eax, [esp+30h+tm_mon] mov [esp+30h+var_2C], eax mov [esp+30h+var_30], offset aMonthD ; "Month: %d " call printf mov eax, [esp+30h+tm_mday] mov [esp+30h+var_2C], eax mov [esp+30h+var_30], offset aDayD ; "Day: %d " call printf mov eax, [esp+30h+tm_hour] mov [esp+30h+var_2C], eax mov [esp+30h+var_30], offset aHourD ; "Hour: %d " call printf mov eax, [esp+30h+tm_min] mov [esp+30h+var_2C], eax mov [esp+30h+var_30], offset aMinutesD ; "Minutes: %d " call printf mov eax, [esp+30h+tm_sec] mov [esp+30h+var_2C], eax mov [esp+30h+var_30], offset aSecondsD ; "Seconds: %d " call printf leave retn main endp ``` 這個代碼和我們之前看到的一樣,依然無法分辨出源代碼是用了結構體還是只是數組而已。 當然這樣也是可以運行的,但是實際操作中還是不建議用這種晦澀的方法。因為通常,編譯器會在棧上按照聲明順序分配變量空間,但是并不能保證每次都是這樣。 還有,其他編譯器可能會警告tm_year,tm_mon, tm_mday, tm_hour, tm_min變量而不是tm_sec使用時未初始化。事實上,計算機并不知道調用localtime_r()的時候他們會被自動填充上。 我選擇了這個例子來解釋是因為他們都是int類型的,而SYSTEMTIME的所有成員是16位的WORD,如果把它們作為本地變量來聲明的話,他們會按照32位的邊界值來對齊,因此什么都用不了了(因為由于數據對齊,此時GetSystemTime()會把它們錯誤的填充起來)。請繼續讀下一節的內容:“結構體的成員封裝”。 所以,結構體只是把一組變量封裝到一個位置上,數據是一個接一個的。我可以說結構體是一個語法糖,因為它只是用來讓編譯器把一組變量保存在一個地方。但是,我不是編程方面的專家,所以更有可能的是,我可能會誤讀這個術語。還有,在早期(1972年以前)的時候,C是不支持結構體的。 ### 18.3.2 ARM+優化Keil+thumb模式 同樣的例子: 清單18.10: 優化Keil+thumb模式 ``` #!bash var_38 = -0x38 var_34 = -0x34 var_30 = -0x30 var_2C = -0x2C var_28 = -0x28 var_24 = -0x24 timer = -0xC PUSH {LR} MOVS R0, #0 ; timer SUB SP, SP, #0x34 BL time STR R0, [SP,#0x38+timer] MOV R1, SP ; tp ADD R0, SP, #0x38+timer ; timer BL localtime_r LDR R1, =0x76C LDR R0, [SP,#0x38+var_24] ADDS R1, R0, R1 ADR R0, aYearD ; "Year: %d " BL __2printf LDR R1, [SP,#0x38+var_28] ADR R0, aMonthD ; "Month: %d " BL __2printf LDR R1, [SP,#0x38+var_2C] ADR R0, aDayD ; "Day: %d " BL __2printf LDR R1, [SP,#0x38+var_30] ADR R0, aHourD ; "Hour: %d " BL __2printf LDR R1, [SP,#0x38+var_34] ADR R0, aMinutesD ; "Minutes: %d " BL __2printf LDR R1, [SP,#0x38+var_38] ADR R0, aSecondsD ; "Seconds: %d " BL __2printf ADD SP, SP, #0x34 POP {PC} ``` ### 18.3.3 ARM+優化Xcode(LLVM)+thumb-2模式 IDA“碰巧知道”tm結構體(因為IDA“知道”例如localtime_r()這些庫函數的參數類型),所以他把這里的結構變量的名字也顯示出來了。 ``` #!bash var_38 = -0x38 var_34 = -0x34 PUSH {R7,LR} MOV R7, SP SUB SP, SP, #0x30 MOVS R0, #0 ; time_t * BLX _time ADD R1, SP, #0x38+var_34 ; struct tm * STR R0, [SP,#0x38+var_38] MOV R0, SP ; time_t * BLX _localtime_r LDR R1, [SP,#0x38+var_34.tm_year] MOV R0, 0xF44 ; "Year: %d " ADD R0, PC ; char * ADDW R1, R1, #0x76C BLX _printf LDR R1, [SP,#0x38+var_34.tm_mon] MOV R0, 0xF3A ; "Month: %d " ADD R0, PC ; char * BLX _printf LDR R1, [SP,#0x38+var_34.tm_mday] MOV R0, 0xF35 ; "Day: %d " ADD R0, PC ; char * BLX _printf LDR R1, [SP,#0x38+var_34.tm_hour] MOV R0, 0xF2E ; "Hour: %d " ADD R0, PC ; char * BLX _printf LDR R1, [SP,#0x38+var_34.tm_min] MOV R0, 0xF28 ; "Minutes: %d " ADD R0, PC ; char * BLX _printf LDR R1, [SP,#0x38+var_34] MOV R0, 0xF25 ; "Seconds: %d " ADD R0, PC ; char * BLX _printf ADD SP, SP, #0x30 POP {R7,PC} ... 00000000 tm struc ; (sizeof=0x2C, standard type) 00000000 tm_sec DCD ? 00000004 tm_min DCD ? 00000008 tm_hour DCD ? 0000000C tm_mday DCD ? 00000010 tm_mon DCD ? 00000014 tm_year DCD ? 00000018 tm_wday DCD ? 0000001C tm_yday DCD ? 00000020 tm_isdst DCD ? 00000024 tm_gmtoff DCD ? 00000028 tm_zone DCD ? ; offset 0000002C tm ends ``` 清單18.11: ARM+優化Xcode(LLVM)+thumb-2模式 ## 18.4 結構體的成員封裝 結構體做的一個重要的事情就是封裝了成員,讓我們看看簡單的例子: ``` #!bash #include <stdio.h> struct s { char a; int b; char c; int d; }; void f(struct s s) { printf ("a=%d; b=%d; c=%d; d=%d ", s.a, s.b, s.c, s.d); }; ``` 如我們所看到的,我們有2個char成員(每個1字節),和兩個int類型的數據(每個4字節)。 ### 18.4.1 x86 編譯后得到: ``` #!bash _s$ = 8 ; size = 16 ?f@@YAXUs@@@Z PROC ; f push ebp mov ebp, esp mov eax, DWORD PTR _s$[ebp+12] push eax movsx ecx, BYTE PTR _s$[ebp+8] push ecx mov edx, DWORD PTR _s$[ebp+4] push edx movsx eax, BYTE PTR _s$[ebp] push eax push OFFSET $SG3842 call _printf add esp, 20 ; 00000014H pop ebp ret 0 ?f@@YAXUs@@@Z ENDP ; f _TEXT ENDS ``` 如我們所見,每個成員的地址都按4字節對齊了,這也就是為什么char也會像int一樣占用4字節。為什么?因為對齊后對CPU來說更容易讀取數據。 但是,這么看明顯浪費了一些空間。 讓我們能用/Zp1(/Zp[n]代表結構體邊界值為n字節)來編譯它: 清單18.12: MSVC /Zp1 ``` #!bash _TEXT SEGMENT _s$ = 8 ; size = 10 ?f@@YAXUs@@@Z PROC ; f push ebp mov ebp, esp mov eax, DWORD PTR _s$[ebp+6] push eax movsx ecx, BYTE PTR _s$[ebp+5] push ecx mov edx, DWORD PTR _s$[ebp+1] push edx movsx eax, BYTE PTR _s$[ebp] push eax push OFFSET $SG3842 call _printf add esp, 20 ; 00000014H pop ebp ret 0 ?f@@YAXUs@@@Z ENDP ; f ``` 現在,結構體只用了10字節,而且每個char都占用1字節。我們得到了最小的空間,但是反過來看,CPU卻無法用最優化的方式存取這些數據。 可以容易猜到的是,如果這個結構體在很多源代碼和對象中被使用的話,他們都需要用同一種方式來編譯起來。 除了MSVC /Zp選項,還有一個是#pragma pack編譯器選項可以在源碼中定義邊界值。這個語句在MSVC和GCC中均被支持。 回到SYSTEMTIME結構體中的16位成員,我們的編譯器怎么才能把它們按1字節邊界來打包? WinNT.h有這么個代碼: 清單18.13:WINNT.H ``` #!cpp #include "pshpack1.h" ``` 和這個: 清單18.14:WINNT.H ``` #!cpp #include "pshpack4.h" // 4 byte packing is the default ``` 文件PshPack1.h看起來像 清單18.15: PSHPACK1.H ``` #!bash #if ! (defined(lint) || defined(RC_INVOKED)) #if ( _MSC_VER >= 800 && !defined(_M_I86)) || defined(_PUSHPOP_SUPPORTED) #pragma warning(disable:4103) #if !(defined( MIDL_PASS )) || defined( __midl ) #pragma pack(push,1) #else #pragma pack(1) #endif #else #pragma pack(1) #endif #endif /* ! (defined(lint) || defined(RC_INVOKED)) */ ``` 這就是#pragma pack處理結構體大小的方法。 ### 18.4.2 ARM+優化Keil+thumb模式 清單18.16 ``` #!bash .text:0000003E exit ; CODE XREF: f+16 .text:0000003E 05 B0 ADD SP, SP, #0x14 .text:00000040 00 BD POP {PC} .text:00000280 f .text:00000280 .text:00000280 var_18 = -0x18 .text:00000280 a = -0x14 .text:00000280 b = -0x10 .text:00000280 c = -0xC .text:00000280 d = -8 .text:00000280 .text:00000280 0F B5 PUSH {R0-R3,LR} .text:00000282 81 B0 SUB SP, SP, #4 .text:00000284 04 98 LDR R0, [SP,#16] ; d .text:00000286 02 9A LDR R2, [SP,#8] ; b .text:00000288 00 90 STR R0, [SP] .text:0000028A 68 46 MOV R0, SP .text:0000028C 03 7B LDRB R3, [R0,#12] ; c .text:0000028E 01 79 LDRB R1, [R0,#4] ; a .text:00000290 59 A0 ADR R0, aADBDCDDD ; "a=%d; b=%d; c=%d; d=%d " .text:00000292 05 F0 AD FF BL __2printf .text:00000296 D2 E6 B exit ``` 我們可以回憶到的是,這里它直接用了結構體而不是指向結構體的指針,而且因為ARM里函數的前4個參數是通過寄存器傳遞的,所以結構體其實是通過R0-R3寄存器傳遞的。 LDRB指令將內存中的一個字節載入,然后把它擴展到32位,同時也考慮它的符號。這和x86架構的MOVSX(參考13.1.1節)基本一樣。這里它被用來傳遞結構體的a、c兩個成員。 還有一個我們可以容易指出來的是,在函數的末尾處,這里它沒有使用正常的函數尾該有的指令,而是直接跳轉到了另一個函數的末尾! 的確,這是一個相當不同的函數,而且跟我們的函數沒有任何關聯。但是,他卻有著相同的函數結尾(也許是因為他也有5個本地變量(5 x 4 = 0x14))。而且他就在我們的函數附近(看看地址就知道了)。事實上,函數結尾并不重要,只要函數好好執行就行了嘛。顯然,Keil決定要重用另一個函數的一部分,原因就是為了優化代碼大小。普通函數結尾需要4字節,而跳轉指令只要2個字節。 ### 18.4.3 ARM+優化XCode(LLVM)+thumb-2模式 清單18.17: 優化的Xcode (LLVM)+thumb-2模式 ``` #!bash var_C = -0xC PUSH {R7,LR} MOV R7, SP SUB SP, SP, #4 MOV R9, R1 ; b MOV R1, R0 ; a MOVW R0, #0xF10 ; "a=%d; b=%d; c=%d; d=%d " SXTB R1, R1 ; prepare a MOVT.W R0, #0 STR R3, [SP,#0xC+var_C] ; place d to stack for printf() ADD R0, PC ; format-string SXTB R3, R2 ; prepare c MOV R2, R9 ; b BLX _printf ADD SP, SP, #4 POP {R7,PC} ``` SXTB(Singned Extend Byte,有符號擴展字節)和x86的MOVSX(見13.1.1節)差不多,但是它不是對內存操作的,而是對一個寄存器操作的,至于剩余的——都一樣。 ## 18.5 嵌套結構 如果一個結構體里定義了另一個結構體會怎么樣? ``` #!cpp #include <stdio.h> struct inner_struct { int a; int b; }; struct outer_struct { char a; int b; struct inner_struct c; char d; int e; }; void f(struct outer_struct s) { printf ("a=%d; b=%d; c.a=%d; c.b=%d; d=%d; e=%d ", s.a, s.b, s.c.a, s.c.b, s.d, s.e); }; ``` 在這個例子里,我們把inner_struct放到了outer_struct的abde中間。 讓我們在MSVC 2010中編譯: 清單18.18: MSVC 2010 ``` #!bash _s$ = 8 ; size = 24 _f PROC push ebp mov ebp, esp mov eax, DWORD PTR _s$[ebp+20] ; e push eax movsx ecx, BYTE PTR _s$[ebp+16] ; d push ecx mov edx, DWORD PTR _s$[ebp+12] ; c.b push edx mov eax, DWORD PTR _s$[ebp+8] ; c.a push eax mov ecx, DWORD PTR _s$[ebp+4] ; b push ecx movsx edx, BYTE PTR _s$[ebp] ;a push edx push OFFSET $SG2466 call _printf add esp, 28 ; 0000001cH pop ebp ret 0 _f ENDP ``` 一個令我們好奇的事情是,看看這個反匯編代碼,我們甚至不知道它的體內有另一個結構體!因此,我們可以說,嵌套的結構體,最終都會轉化為線性的或者一維的結構。 當然,如果我們把struct inner_struct c;換成struct inner_struct *c(因此這里其實是定義個了一個指針),這個情況下狀況則會大為不同。 ## 18.6 結構體中的位 ### 18.6.1 CPUID 的例子 C/C++中允許給結構體的每一個成員都定義一個準確的位域。如果我們想要節省空間的話,這個對我們來說將是非常有用的。比如,對BOOL來說,1位就足矣了。但是當然,如果我們想要速度的話,必然會浪費點空間。 讓我們以CPUID指令為例,這個指令返回當前CPU的信息和特性。 如果EAX在指令執行之前就設置為了1,CPUID將會返回這些內容到EAX中。 ![enter image description here](https://box.kancloud.cn/2015-12-28_5680ec4642328.png) MSVC 2010有CPUID的宏,但是GCC 4.4.1沒有,所以,我們就手動的利用它的內聯匯編器為GCC寫一個吧。 ``` #!cpp #include <stdio.h> #ifdef __GNUC__ static inline void cpuid(int code, int *a, int *b, int *c, int *d) { asm volatile("cpuid":"=a"(*a),"=b"(*b),"=c"(*c),"=d"(*d):"a"(code)); } #endif #ifdef _MSC_VER #include <intrin.h> #endif struct CPUID_1_EAX { unsigned int stepping:4; unsigned int model:4; unsigned int family_id:4; unsigned int processor_type:2; unsigned int reserved1:2; unsigned int extended_model_id:4; unsigned int extended_family_id:8; unsigned int reserved2:4; }; int main() { struct CPUID_1_EAX *tmp; int b[4]; #ifdef _MSC_VER __cpuid(b,1); #endif #ifdef __GNUC__ cpuid (1, &b[0], &b[1], &b[2], &b[3]); #endif tmp=(struct CPUID_1_EAX *)&b[0]; printf ("stepping=%d ", tmp->stepping); printf ("model=%d ", tmp->model); printf ("family_id=%d ", tmp->family_id); printf ("processor_type=%d ", tmp->processor_type); printf ("extended_model_id=%d ", tmp->extended_model_id); printf ("extended_family_id=%d ", tmp->extended_family_id); return 0; }; ``` 之后CPU會填充EAX,EBX,ECX,EDX,這些寄存器的值會通過b[]數組顯現出來。接著我們用一個指向CPUID_1_EAX結構體的指針,把它指向b[]數組的EAX值。 換句話說,我們將把32位的INT類型的值當作一個結構體來看。 然后我們就能從結構體中讀取數據。 讓我們在MSVC 2008用/Ox編譯一下: 清單18.19: MSVC 2008 ``` #!bash _b$ = -16 ; size = 16 _main PROC sub esp, 16 ; 00000010H push ebx xor ecx, ecx mov eax, 1 cpuid push esi lea esi, DWORD PTR _b$[esp+24] mov DWORD PTR [esi], eax mov DWORD PTR [esi+4], ebx mov DWORD PTR [esi+8], ecx mov DWORD PTR [esi+12], edx mov esi, DWORD PTR _b$[esp+24] mov eax, esi and eax, 15 ; 0000000fH push eax push OFFSET $SG15435 ; ’stepping=%d’, 0aH, 00H call _printf mov ecx, esi shr ecx, 4 and ecx, 15 ; 0000000fH push ecx push OFFSET $SG15436 ; ’model=%d’, 0aH, 00H call _printf mov edx, esi shr edx, 8 and edx, 15 ; 0000000fH push edx push OFFSET $SG15437 ; ’family_id=%d’, 0aH, 00H call _printf mov eax, esi shr eax, 12 ; 0000000cH and eax, 3 push eax push OFFSET $SG15438 ; ’processor_type=%d’, 0aH, 00H call _printf mov ecx, esi shr ecx, 16 ; 00000010H and ecx, 15 ; 0000000fH push ecx push OFFSET $SG15439 ; ’extended_model_id=%d’, 0aH, 00H call _printf shr esi, 20 ; 00000014H and esi, 255 ; 000000ffH push esi push OFFSET $SG15440 ; ’extended_family_id=%d’, 0aH, 00H call _printf add esp, 48 ; 00000030H pop esi xor eax, eax pop ebx add esp, 16 ; 00000010H ret 0 _main ENDP ``` SHR指令將EAX寄存器的值右移位,移出去的值必須被忽略,例如我們會忽略右邊的位。 AND指令將清除左邊不需要的位,換句話說,它處理過后EAX將只留下我們需要的值。 讓我們在GCC4.4.1下用-O3編譯。 清單18.20: GCC 4.4.1 ``` #!cpp main proc near ; DATA XREF: _start+17 push ebp mov ebp, esp and esp, 0FFFFFFF0h push esi mov esi, 1 push ebx mov eax, esi sub esp, 18h cpuid mov esi, eax and eax, 0Fh mov [esp+8], eax mov dword ptr [esp+4], offset aSteppingD ; "stepping=%d " mov dword ptr [esp], 1 call ___printf_chk mov eax, esi shr eax, 4 and eax, 0Fh mov [esp+8], eax mov dword ptr [esp+4], offset aModelD ; "model=%d " mov dword ptr [esp], 1 call ___printf_chk mov eax, esi shr eax, 8 and eax, 0Fh mov [esp+8], eax mov dword ptr [esp+4], offset aFamily_idD ; "family_id=%d " mov dword ptr [esp], 1 call ___printf_chk mov eax, esi shr eax, 0Ch and eax, 3 mov [esp+8], eax mov dword ptr [esp+4], offset aProcessor_type ; "processor_type=%d " mov dword ptr [esp], 1 call ___printf_chk mov eax, esi shr eax, 10h shr esi, 14h and eax, 0Fh and esi, 0FFh mov [esp+8], eax mov dword ptr [esp+4], offset aExtended_model ; "extended_model_id=%d " mov dword ptr [esp], 1 call ___printf_chk mov [esp+8], esi mov dword ptr [esp+4], offset unk_80486D0 mov dword ptr [esp], 1 call ___printf_chk add esp, 18h xor eax, eax pop ebx pop esi mov esp, ebp pop ebp retn main endp ``` 幾乎一樣。只有一個需要注意的地方就是GCC在調用每個printf()之前會把extended_model_id和extended_family_id的計算聯合到一塊去,而不是把它們分開計算。 ### 18.6.2 將浮點數當作結構體看待 我們已經在FPU(15章)中注意到了float和double兩個類型都是有符號的,他們分為符號、有效數字和指數部分。但是我們能直接用上這些位嘛?讓我們試一試float。 ![enter image description here](https://box.kancloud.cn/2015-12-28_5680ec464f81c.png) ``` #!cpp #include <stdio.h> #include <assert.h> #include <stdlib.h> #include <memory.h> struct float_as_struct { unsigned int fraction : 23; // fractional part unsigned int exponent : 8; // exponent + 0x3FF unsigned int sign : 1; // sign bit }; float f(float _in) { float f=_in; struct float_as_struct t; assert (sizeof (struct float_as_struct) == sizeof (float)); memcpy (&t, &f, sizeof (float)); t.sign=1; // set negative sign t.exponent=t.exponent+2; // multiple d by 2^n (n here is 2) memcpy (&f, &t, sizeof (float)); return f; }; int main() { printf ("%f ", f(1.234)); }; ``` float_as_struct結構占用了和float一樣多的內存空間,也就是4字節,或者說,32位。 現在我們給輸入值設置一個負值,然后指數加2,這樣我們就能把整個數按照22的值來倍乘,也就是乘以4。 讓我們在MSVC2008無優化模式下編譯它。 清單18.21: MSVC 2008 ``` #!bash _t$ = -8 ; size = 4 _f$ = -4 ; size = 4 __in$ = 8 ; size = 4 ?f@@YAMM@Z PROC ; f push ebp mov ebp, esp sub esp, 8 fld DWORD PTR __in$[ebp] fstp DWORD PTR _f$[ebp] push 4 lea eax, DWORD PTR _f$[ebp] push eax lea ecx, DWORD PTR _t$[ebp] push ecx call _memcpy add esp, 12 ; 0000000cH mov edx, DWORD PTR _t$[ebp] or edx, -2147483648 ; 80000000H - set minus sign mov DWORD PTR _t$[ebp], edx mov eax, DWORD PTR _t$[ebp] shr eax, 23 ; 00000017H - drop significand and eax, 255 ; 000000ffH - leave here only exponent add eax, 2 ; add 2 to it and eax, 255 ; 000000ffH shl eax, 23 ; 00000017H - shift result to place of bits 30:23 mov ecx, DWORD PTR _t$[ebp] and ecx, -2139095041 ; 807fffffH - drop exponent or ecx, eax ; add original value without exponent with new calculated exponent mov DWORD PTR _t$[ebp], ecx push 4 lea edx, DWORD PTR _t$[ebp] push edx lea eax, DWORD PTR _f$[ebp] push eax call _memcpy add esp, 12 ; 0000000cH fld DWORD PTR _f$[ebp] mov esp, ebp pop ebp ret 0 ?f@@YAMM@Z ENDP ; f ``` 有點多余。如果用/Ox編譯的話,這里就沒有memcpy調用了。f變量會被直接使用,但是沒有優化的版本看起來會更容易理解一點。 GCC 4.4.1的-O3選項會怎么做? 清單18.22: Gcc 4.4.1 ``` #!bash ; f(float) public _Z1ff _Z1ff proc near var_4 = dword ptr -4 arg_0 = dword ptr 8 push ebp mov ebp, esp sub esp, 4 mov eax, [ebp+arg_0] or eax, 80000000h ; set minus sign mov edx, eax and eax, 807FFFFFh ; leave only significand and exponent in EAX shr edx, 23 ; prepare exponent add edx, 2 ; add 2 movzx edx, dl ; clear all bits except 7:0 in EAX shl edx, 23 ; shift new calculated exponent to its place or eax, edx ; add new exponent and original value without exponent mov [ebp+var_4], eax fld [ebp+var_4] leave retn _Z1ff endp public main main proc near push ebp mov ebp, esp and esp, 0FFFFFFF0h sub esp, 10h fld ds:dword_8048614 ; -4.936 fstp qword ptr [esp+8] mov dword ptr [esp+4], offset asc_8048610 ; "%f " mov dword ptr [esp], 1 call ___printf_chk xor eax, eax leave retn main endp ``` F()函數基本可以理解,但是有趣的是,GCC可以在編譯階段就通過我們這堆大雜燴一樣的代碼計算出f(1.234)的值,從而會把他當作參數直接給printf()。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看