全局變量

實驗品

小白鼠(global.c):

#include <stdio.h>

int i = 1;

int main()
{
    ++i;

    printf("%d\n",i);
    return 0;
}

i 的定義被放在了函數體的外邊, i 就成為了全局變量,程序運行的結果我不關心, 我關心的是 i 最後變成了什麼。

反彙編

  這次悟空的火眼金睛也不給力了(後面我們會看到), 我們得反彙編可執行文件才能看到最終結果:

[lqy@localhost temp]$ gcc -o global global.c
[lqy@localhost temp]$ objdump -s -d global > global.txt
[lqy@localhost temp]$
  • gcc -o global global.c 是編譯 global.c 生成可執行文件 global
  • objdump -s -d global > global.txt 是反彙編 global
    • -s 參數可以將所有段的內容以十六進制的方式打印出來
    • -d 參數可以將所有包含指令的段反彙編
    • global.txt 是將標準輸出輸出到 global.txt 文件 (專業點的話,叫"輸出重定向")

objdump 是 linux 下一款反彙編工具, 能夠反彙編目標文件、可執行文件

  這樣操作後,global 文件的反彙編結果輸出到了 global.txt 文件中,打開 global.txt(文件比較長,我的有 357 行), 定位到 main 函數:

080483c4 <main>:
 80483c4:    55                       push   %ebp
 80483c5:    89 e5                    mov    %esp,%ebp
 80483c7:    83 e4 f0                 and    $0xfffffff0,%esp
 80483ca:    83 ec 10                 sub    $0x10,%esp
 80483cd:    a1 64 96 04 08           mov    0x8049664,%eax
 80483d2:    83 c0 01                 add    $0x1,%eax
 80483d5:    a3 64 96 04 08           mov    %eax,0x8049664
 80483da:    8b 15 64 96 04 08        mov    0x8049664,%edx
 80483e0:    b8 c4 84 04 08           mov    $0x80484c4,%eax
 80483e5:    89 54 24 04              mov    %edx,0x4(%esp)
 80483e9:    89 04 24                 mov    %eax,(%esp)
 80483ec:    e8 03 ff ff ff           call   80482f4 
 80483f1:    b8 00 00 00 00           mov    $0x0,%eax
 80483f6:    c9                       leave
 80483f7:    c3                       ret

  粗體部分標出了 ++i 的反彙編結果: 全局變量 i 最後變成了絕對地址為 0x8049664 的內存塊(大小為4字節)。 注意這裡的 0x8049664 不是指立即數 0x8049664, 如果要表示值為 0x8049664 的立即數,應該寫為 $0x8049664。

悟空的弱點

通過火眼金睛:

gcc -S -o global.s global.c

我們看到 ++i 的彙編形式是:

movl    i, %eax
addl    $1, %eax
movl    %eax, i

悟空,你太讓我們失望了!

目標文件中的全局變量

目標文件也可以用 objdump 來反彙編:

[lqy@localhost temp]$ gcc -c -o global.o global.c
[lqy@localhost temp]$ objdump -s -d global.o > global.txt
[lqy@localhost temp]$

global.o 反彙編出來的 global.txt 就沒那麼長了, 只有 35 行,其中 ++i 部分的結果是:

9:    a1 00 00 00 00           mov    0x0,%eax
e:    83 c0 01                 add    $0x1,%eax
11:    a3 00 00 00 00           mov    %eax,0x0

怎麼會這樣?怎麼不是 0x8049664 呢? 這就是目標文件 和 可執行文件的區別了: 全局變量在目標文件中只有一個冒牌地址, 在鏈接後(各目標文件協商(為自己的全局變量爭一塊地盤) 完畢)才填入最終的絕對地址。

目標文件和可執行文件

  目標文件是編譯後的產物, 已經完成了 C語言 到 機器碼 的轉變, 但是部分機器碼的操作數還需要在鏈接過程中修正。

  可執行文件是由多個目標文件和 C 運行庫鏈接而成的, C 運行庫提供了諸如 printf 等函數的實現。

  linux 下目標文件(默認擴展名是.o)和可執行文件都是 ELF 格式(文件內容按照一定格式進行組織)的二進制文件; 類似的,Windows 下 VISUAL C++ 編譯出來的目標文件 (擴展名是.obj)採用 COFF 格式,而可執行文件 (擴展名是.exe)採用 PE 格式, ELF 和 PE 都是從 COFF 發展而來的。

  因為 linux 下目標文件和可執行文件的內容格式是一樣的, 所以 objdump 既可以反彙編可執行文件也可以反彙編目標文件。

小結

  全局變量也變成無名無姓的內存地址了, 而且還是常量!

  這次又介紹了一個工具:objdump。 所有後面要用到的工具和使用方法都介紹完了:gcc、objdump, ……嗯……(好像還不夠)……至少主要的都介紹完了O(∩_∩)O~……