一般來說,我們寫的**都是在main函式內執行,main函式就是一切的核心,這的確沒錯,
main函式包含我們所寫的**的主要流程,我們會把想法灌注到其中去,寫出一段段**,最終編譯出程式,
即使物件導向的應用開發也是如此。
不過main函式是個函式,它跟其他函式有沒有什麼根本上的區別?
答案是否定的,我們用main()作為函式的開頭只是因為編譯器這樣子要求,
如果編譯器要求我們用其他函式作為我們**體的入口,那麼main函式就只是乙個單純的函式。
要用函式必須要設定棧(設定sp、ep,當然這是對於裸板程式來說的),棧是函式資料的基本表現形式,
當要呼叫某個函式的時候,我們需要儲存所呼叫函式所用到的暫存器的資料,資料儲存在棧內;
當有引數通過形參傳給函式體時,通過棧來儲存形參(某些編譯器直接通過暫存器傳資料,如果暫存器不夠用的時候再用棧儲存資料);
當我們在函式提內定義變數時,在棧內儲存該變數(這只使用與一般情形,特殊情況下面會講到);
暫存器sp是棧頂,ep是棧底,讀寫棧內資料就是通過sp跟ep來確定棧內位址,從而達到資料讀寫的目的
下面基於i386跟g++分析函式內資料存放方式
首先我們在main函式內定義了一段資料:
int index=0; unsigned
long sum=0
;
const
int a=2
;
static
int b=3
;
char * str=;
const unsigned long longnum=;
然後用objdump檢視反彙編:
4011ba: c7 45 f4 000000
00 movl $0x0,-0xc(%ebp) //
int index=0;
4011c1: c7 45 f0 00
0000
00 movl $0x0,-0x10(%ebp) //
unsigned long sum=0;
4011c8: c7 45 ec 02
0000
00 movl $0x2,-0x14(%ebp) //
const int a=2;
4011cf: c7
45 c8 80
2144
00 movl $0x442180,-0x38(%ebp) //
"hello "字串首位址
4011d6: c7 45 cc 87
2144
00 movl $0x442187,-0x34(%ebp) //
"what "字串首位址
4011dd: c7 45 d0 8d 21
4400 movl $0x44218d,-0x30(%ebp) //
"is "字串首位址
4011e4: c7 45 d4 91
2144
00 movl $0x442191,-0x2c(%ebp) //
"your "字串首位址
4011eb: c7 45 d8 97
2144
00 movl $0x442197,-0x28(%ebp) //
"name "字串首位址
4011f2: c7
45 a8 78
5634
02 movl $0x2345678,-0x58(%ebp) //
0x02345678,
4011f9: c7 45 ac 78
5634
02 movl $0x2345678,-0x54(%ebp) //
0x02345678,
401200: c7 45 b0 78
5624
13 movl $0x13245678,-0x50(%ebp) //
0x13245678,
401207: c7 45 b4 78
5634
02 movl $0x2345678,-0x4c(%ebp) //
0x02345678,
40120e: c7 45 b8 78
5634
01 movl $0x1345678,-0x48(%ebp) //
0x01345678,
401215: c7 45 bc 78
5624
01 movl $0x1245678,-0x44(%ebp) //
0x01245678,
40121c: c7 45 c0 78
5634
12 movl $0x12345678,-0x40(%ebp) //
0x12345678,
401223: c7 45 c4 78
5634
12 movl $0x12345678,-0x3c(%ebp) //
0x12345678,
00442180<.rdata>:
442180: 68
65 6c 6c 6f //
"hello "
442185: 20
00442187: 77
68//
"what "
442189: 61
44218a:
7420
44218c:
0069
73//
"is "
44218f: 20
00442191: 79 6f //
"your "
442193: 75
72442195: 20
00442197: 6e //
"name "
442198: 61
442199
: 6d
44219a:
6520
0044219d:
0000
由上面可以看出,int index =0; unsigned long sum = 0; const int a = 2; 都是在棧內自動生成(這些就是前面所說的一般情況)
而 const * str=倒是從.rodata (唯讀資料段)取資料
const unsigned longnum= 裡面的資料全部都在棧內自動生成。
因此我們可以得出這樣乙個結論:
在該編譯器,棧內儲存的都是型別化的(int,char,long,unsigned ..)的資料,並且對於重複性的型別化資料(如陣列),也是儲存在棧內。但是對於複雜性的資料(如:字串),在唯讀資料段中儲存。
然後如果我們仔細觀察上面的**會發現,static int b = 3;沒出現在上示**段中,我們可以通過要求輸出b來得到該變數的位置。
cout《反彙編:401247: a1 04
2044
00 mov 0x442004,%eax //
b的位址賦給eax暫存器
40124c: 89
4424
04 mov %eax,0x4(%esp)
401250: c7 04
24 a0 53
4400 movl $0x4453a0,(%esp)
401257: e8 74 bd 02
00 call 42cfd0 <__znsolsei>
static int b的位置:
disassembly of section .data:00442000
<__data_start__>:
442000: 45 inc %ebp
442001: 00
00 add %al,(%eax)
...00442004
<_zz4maine1b>:
442004: 03
00 add (%eax),%eax //
b的位置
從上面看出,static 變數的時候,該變數是存放在.data(可讀寫資料段)的,並不在棧內。
最後,對於不同的編譯器,上面的描述可能會有些許不同
比如在用交叉編譯工具arm-linux-gcc編譯**的時候
unsigned long 型別的陣列是存放在唯讀資料段內的,
它需要先把資料先從.rodata讀進暫存器r0-r3,然後再從r0-r3暫存器把資料寫到棧內
如果該陣列有大量資料,則會分開幾次做資料傳送。
C C 函式呼叫方式
cdecl 是c declaration的縮寫 declaration,宣告 表示c語言預設的函式呼叫方法 所有引數從右到左依次入棧,這些引數由呼叫者清除,稱為手動清棧。被呼叫函式不會要求呼叫者傳遞多少引數,呼叫者傳遞過多或者過少的引數,甚至完全不同的引數都不會產生編譯階段的錯誤。stdcall 是...
C C 函式呼叫方式
呼叫約定 cdecl fastcall與 stdcall,三者都是呼叫約定 calling convention 它決定以下內容 1 函式引數的壓棧順序,2 由呼叫者還是被呼叫者把引數彈出棧,3 以及產生函式修飾名的方法。1 stdcall呼叫約定 函式的引數自右向左通過棧傳遞,被呼叫的函式在返回前...
C C 函式呼叫方式
cdecl fastcall與 stdcall,三者都是呼叫約定 calling convention 它決定以下內容 1 函式引數的壓棧順序,2 由呼叫者還是被呼叫者把引數彈出棧,3 以及產生函式修飾名的方法。1 stdcall呼叫約定 函式的引數自右向左通過棧傳遞,被呼叫的函式在返回前清理傳送引...