Linux C存取效率對照——堆、棧、常量區

來源:互聯網
上載者:User

標籤:動態   程式   編譯   代碼   ...   round   工具   storage   堆和棧   

本文主要探討堆和棧在使用中的存取效率。利用宏彙編指令分析訪存情況來進行簡單推斷。

實驗環境及使用工具:i686,32位Ubuntu Linux。gcc (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3,gdb

首先,引用一道題的代碼和“答案”,這是“比較堆和棧存取效率”的。可是其實,他給的兩種方式都用的棧,個人試出來的佔用堆空間的情況,僅僅能是malloc()和new()等系統調用產生的。

#include<stdio.h>  

main(){  

        char a = 1;  

        char c[] = "1234567890";  

        char *p = "1234567890";  

        a = c[1];  

        a = p[1];  


“答案”:存取效率的比較

chars1[]="aaaaaaaaaaaaaaa";
char *s2="bbbbbbbbbbbbbbbbb";
aaaaaaaaaaa是在執行時刻賦值的;
而bbbbbbbbbbb是在編譯時間就確定的。

主要疑問是,兩者都在棧中儲存,確定"aaa...."是執行時刻賦值的嗎?棧中是執行時“賦值”麼。


那麼,既然看到了這段代碼。還是對照一下吧。能夠先比較一下以“數組”和"指標"(後邊會解釋詳細含義)形式初始化的兩段字串的存取效率。


宏彙編指令運行過程:

Breakpoint 1, main () at efficiencyOfStorage.c:4  

4       char a = 1;  

1: x/i $pc  

=> 0x8048419 <main+21>:    movb   $0x1,0x10(%esp)  

 

5        char c[] = "1234567890";  

0x804841e <main+26>:   movl   $0x34333231,0x11(%esp)  

0x8048426 <main+34>:   movl   $0x38373635,0x15(%esp)  

0x804842e <main+42>:   movw   $0x3039,0x19(%esp)  

0x8048435 <main+49>:   movb   $0x0,0x1b(%esp)  

6        char *p = "1234567890";  

0x804843a <main+54>:  movl   $0x8048540,0xc(%esp)  

7        a = c[1];  

0x8048442 <main+62>:  movzbl 0x12(%esp),%eax  

0x8048447 <main+67>:   mov    %al,0x10(%esp)  

8        a = p[1];  

0x804844b <main+71>: mov 0xc(%esp),%eax  

0x804844f <main+75>: movzbl 0x1(%eax),%eax 

0x8048453 <main+79>: mov %al,0x10(%esp)

10    }

0x8048457 <main+83>: mov 0x1c(%esp),%edx 

0x804845b <main+87>: xor %gs:0x14,%edx 

0x8048462 <main+94>: je 

0x8048469 <main+101> 

0x8048464 <main+96>: call 

0x8048320 <[email protected]> 

0x8048469 <main+101>: leave 

0x804846a <main+102>: ret  

(依據變數聲明的先後順序能夠看到。在linux棧位移地址是增長的)

首先,它是字元數組,數字字元0-9轉換成ascii碼是0x30-0x39。 


char c[] = "1234567890";  

0x804841e <main+26>:   movl   $0x34333231,0x11(%esp)  

0x8048426 <main+34>:   movl   $0x38373635,0x15(%esp)  

0x804842e <main+42>:   movw   $0x3039,0x19(%esp)  

0x8048435 <main+49>:   movb   $0x0,0x1b(%esp)

整個數組c包含結束符應該佔用11個地址空間(能夠用sizeof驗證),為0x11至0x1b。

小端模式,字元數組“01234567890” 從低地址0x11開始排列。到0x1b結束(結束符ascii值0x00):  

棧中位移地址:0x11 0x12 0x13 0x14 0x15 0x16 0x17 0x18 0x19 0x1a 0x1b 

對應記憶體內容:0x31 0x32 0x33 0x34 0x35 0x36 0x37 0x38 0x39 0x30 0x00

PS:雖然第三次僅僅壓入word長的資料(兩位元組),但還是單獨用了一行指令壓大小為byte的結束符。


 

6        char *p = "1234567890";  

0x804843a <main+54>:  movl   $0x8048540,0xc(%esp)  

p指標本身肯定在棧,直接讓p指標指向字串常量的地址(0x8048540),“1234567890”被存入該地址的過程被省略了,自己主動的

 

7           a = c[1];  

0x8048442 <main+62>:  movzbl 0x12(%esp),%eax  

0x8048447 <main+67>:   mov    %al,0x10(%esp)  

從地址0x12取出值0x32。傳給eax寄存器。


關於movzbl。文章底部有具體解釋,說通俗點就是把(8位)byte長度的值0x32移到(32位)long長度的某地址儲存空間中(此例為eax)寄存器了——此時eax中值0x00000032(前24位應該補0。由於“zero”。能夠肯定後八位是0x32,即可了)

mov al把eax的低8位值0x32,即數字2。存到棧位移地址0x10(即變數a的地址)。

賦值完畢

假設這些簡單彙編看不懂,還感興趣。請移步我的通俗的彙編貼


8          a = p[1];  

0x804844b <main+71>: mov 0xc(%esp),%eax  

0x804844f <main+75>: movzbl 0x1(%eax),%eax  

0x8048453 <main+79>: mov %al,0x10(%esp)    

將棧位移地址0xc中儲存的指標p(內容為指向的地址)移到eax寄存器中。  

第二句較難:

從eax中取出指標,位移1。讀取字串中第二個字元’2’,把該(八位)地址相應的值(0x32,即數字2)存到棧位移地址0x10(即變數a的地址)。

將eax寄存器中低8位。即0x32。傳給棧位移地址0x10中,即為給a賦值。

賦值完畢



結論:能夠明顯看出,前者直接有目的地從棧中讀取資料到寄存器eax中,後者則要先把指標值讀出來,再通過指標加位移去找須要的地址的值,依據我們關於電腦群組成原理的常識。多了一次訪問記憶體,顯然效率低了。


能夠看到的是,兩個字串在讀取的時候相同是用數組下標的操作形式,所以和操作方式無關?

感覺不夠嚴謹,也測試了用指標的操作形式,例如以下(多次測試。和上邊過程變數地址恐有變化。原理同樣就可以):

11        a = *(c + 1);
1: x/i $pc
=> 0x8048487 <main+83>:    lea    0x21(%esp),%eax
(gdb)
0x0804848b    11        a = *(c + 1);
1: x/i $pc
=> 0x804848b <main+87>:    movzbl 0x1(%eax),%eax
(gdb)
0x0804848f    11        a = *(c + 1);
1: x/i $pc
=> 0x804848f <main+91>:    mov    %al,0x20(%esp)

取出C的地址

取出——C的地址+1位移量所指向的——值

將該值傳遞給變數a

(gdb)
12        a = *(p + 1);
1: x/i $pc
=> 0x8048493 <main+95>:    mov    0x1c(%esp),%eax
(gdb)
0x08048497    12        a = *(p + 1);
1: x/i $pc
=> 0x8048497 <main+99>:    movzbl 0x1(%eax),%eax
(gdb)
0x0804849b    12        a = *(p + 1);
1: x/i $pc
=> 0x804849b <main+103>:    mov    %al,0x20(%esp)

取出p指向的“字串常量”的首地址

取出——p指向的“字串常量”的首地址+1位移量所指向的——值

將該值傳遞給變數a

兩者唯一差別就是指令lea和mov,原因就是p指向的是“常量區”,僅僅須要p的內容(即目標地址)就可以,而c。要取自身的地址。


 


PS:沒有對照堆空間的存取問題,由於涉及系統調用。指令許多。過程很慢。堆比棧存取慢許多是顯然的了




附:


文中所謂“棧位移地址0x10”之類,非絕對位址。皆指位移地址。%esp是一個固定位置,位移多少就是固定位置加多少位移量。

=> 0x8048456 <main+34>:  movl   $0x38373635,0x25(%esp)

(gdb) print $esp

$2 = (void *) 0xbffff230

(gdb) si

0x0804845e 5      char c[] = "1234567890";

=> 0x804845e <main+42>:  movw   $0x3039,0x29(%esp)

(gdb) print $esp

$3 = (void *) 0xbffff230

0x08048465 5      char c[] = "1234567890";

=> 0x8048465 <main+49>:  movb   $0x0,0x2b(%esp)

(gdb) print $esp

$4 = (void *) 0xbffff230

 

 

movzbl:

在AT&T文法中,符號擴充和零擴充指令的格式為。基本部分"movs"和"movz"(相應Intel文法的為movsx和movzx,movzx為零擴充,即高位補零。movsx為符號擴充,即高位補符號位)

後面跟上源運算元長度和目的運算元長度。movsbl意味著movs (from)byte (to)long;movbw意味著movs (from)byte (to)word;movswl意味著movs (from)word (to)long。

對於movz指令也一樣。比方指令“movsbl   %al, %edx”意味著將al寄存器的內容進行符號擴充後放置到edx寄存器中。

movzx是將源運算元的內容複寫到目的運算元。並將該值0擴充至16位或者32位。

可是它僅僅適用於不帶正負號的整數。

 他大致分為以下的三種格式:

movzx 32位通用寄存器,8位通用寄存器/記憶體單元

movzx 32位通用寄存器,16位通用寄存器/記憶體單元

movzx 16位通用寄存器, 8位通用寄存器/記憶體單元 

 


堆空間是程式執行時動態申請的,系統維護一個關於空暇地區的鏈表,從小到大按容量找。找到第一個符合要求(大於等於所需空間)的結點,分配之。

那麼也不一定就全用鏈表,在WINDOWS下,最好的方式是用VirtualAlloc分配記憶體。他不是在堆,也不是在棧,而是直接在進程的地址空間中保留一塊記憶體,儘管用起來最不方便。可是速度快,也最靈活。

那麼刪除怎麼刪?怎麼知道刪多少?這個大小是系統記錄的。不是問題,僅僅管free()、delete()就成了。

假設申請的少,不巧沒有非常合適的,分配多了的部分,系統還會釋放掉,免得浪費。


Linux C存取效率對照——堆、棧、常量區

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.