C C 函式內的資料存放方式

2022-04-03 10:06:26 字數 4792 閱讀 1432

一般來說,我們寫的**都是在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 00

0000

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呼叫約定 函式的引數自右向左通過棧傳遞,被呼叫的函式在返回前清理傳送引...