你的變量究竟存儲在什麼地方 && 全局內存
我相信大家都有過這樣的經曆,在麵試過程中,考官通常會給你一道題目,然後問你某個變量存儲在什麼地方,在內存中是如何存儲的等等一係列問題。不僅僅是在麵試中,學校裏麵的考試也會碰到同樣的問題。
節頭部表
|
.strtab
|
.line
|
.debug
|
.rel.data
|
.rel.text
|
.symtab
|
.bss
|
> .dataa (3)
|
.rodata
|
> .textt (1)
|
ELF頭
|
節
|
含義
|
.text
|
已編譯程序的機器代碼
|
.rodata
|
隻讀數據,如pintf和switch語句中的字符串和常量值
|
.data
|
已初始化的全局變量
|
.bss
|
未初始化的全局變量
|
.symtab
|
符號表,存放在程序中被定義和引用的函數和全局變量的信息
|
.rel.text
|
當鏈接器吧這個目標文件和其他文件結合時,.text節中的信息需修改
|
.rel.data
|
被模塊定義和引用的任何全局變量的信息
|
.debug
|
一個調試符號表。
|
.line
|
原始C程序的行號和.text節中機器指令之間的映射
|
.strtab
|
一個字符串表,其內容包含.systab和.debug節中的符號表
|
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
int z = 9;
int a;
static int b =10;
static int c;
void swap(int* x,int* y)
{
int temp;
temp=*x;
*x=*y;
*y=temp;
}
int main()
{
int x=4,y=5;
swap(&x,&y);
printf(“x=%d,y=%d,z=%d,w=%dn”,x,y,z,b);
return 0;
}
|
變量
|
存儲區域
|
a
|
.bss
|
b
|
.data
|
c
|
.bss
|
x
|
stack
|
y
|
stack
|
temp
|
stack
|
z
|
.data
|
swap
|
.text
|
main
|
.text
|
x=……
|
.rodata
|
> > .filee "var.c"
.globl z
> .dataa #數據段
> .align 4
> .typee z, @object
> .size z, 4
z:
> .longg 9
> .align 4
> .typee b, @object
> .size b, 4
b:
> .longg 10
> .textt #代碼段
.globl
swap
> .typee swap, @function
swap:
> pushll %ebp
> movll %esp, %ebp
> subl $4, %esp
> movll 8(%ebp), %eax
> movll (%eax), %eax
> movll %eax, -4(%ebp)
> movll 8(%ebp), %edx
> movll 12(%ebp), %eax
> movll (%eax), %eax
> movll %eax, (%edx)
> movll 12(%ebp), %edx
> movll -4(%ebp), %eax
> movll %eax, (%edx)
> leave
> ret
> .size swap, .-swap
> .sectionn .rodataa #隻讀段
.LC0:
> .stringg "x=%d,y=%d,z=%d,w=%dn"
>
.textt #代碼段
.globl
main
> .typee main, @function
main:
> pushll %ebp
> movll %esp, %ebp
> subl $40, %esp
> andl $-16, %esp
> movll $0, %eax
> subl %eax, %esp
> movll $4, -4(%ebp)
> movll $5, -8(%ebp)
> leall -8(%ebp), %eax
> movll %eax, 4(%esp)
> leall -4(%ebp), %eax
> movll %eax, (%esp)
> calll swap
> movll b, %eax
> movll %eax, 16(%esp)
> movll z, %eax
> movll %eax, 12(%esp)
> movll -8(%ebp), %eax
> movll %eax, 8(%esp)
> movll -4(%ebp), %eax
> movll %eax, 4(%esp)
> movll $.LC0, (%esp)
> calll printf
> movll $0, %eax
> leave
> ret
> .size main, .-main
>
.commm a,4,4
> .locall c
> .commm c,4,4
> .sectionn .note.GNU-stack,"",@progbits
> .identt "GCC: (GNU) 3.3.5 (Debian 1:3.3.5-13)"
|

字段名
|
含義
|
Num
|
序號
|
Value
|
符號地址。
可重定位目標文件:距定義目標文件的節的起始位置的偏移
可執行目標文件:一個絕對運行的地址
|
Size
|
目標的大小
|
Type
|
要麼是數據,要麼是函數,或各個節的表目
|
Bind
|
符號是全局的還是本地的
|
Vis
|
目前還沒有查到資料,待以後改正
|
Ndx
|
通過索引來表示每個節
ABS:不該被重定位的符號
UND:代表未定義的符號(在其他地方定義)
COM:未初始化的數據目標
|
Name
|
指向符號的名字
|
> ……
.commm a,4,4
> .locall c
> .commm c,4,4
> ……
|
------------------------------------------------------------------------------------------------------------------
大內高手—全局內存
轉載時請注明出處和作者聯係方式:https://blog.csdn.net/absurd
作者聯係方式:李先靜 <xianjimli at hotmail dot com>
更新時間:2007-7-9
有 人可能會說,全局內存就是全局變量嘛,有必要專門一章來介紹嗎?這麼簡單的東西,還能玩出花來?我從來沒有深究它,不一樣寫程序嗎?關於全局內存這個主題 雖然玩不出花來,但確實有些重要,了解這些知識,對於優化程序的時間和空間很有幫助。因為有好幾次這樣經曆,我才決定花一章篇幅來介紹它。
正如大家所知道的,全局變量是放在全局內存中的,但反過來卻未必成立。用static修飾的局部變量就是放在放全局內存的,它的作用域是局部的,但生命期是全局的。在有的嵌入式平台中,堆實際上就是一個全局變量,它占用相當大的一塊內存,在運行時,把這塊內存進行二次分配。
這裏我們並不強調全局變量和全局內存的差別。在本文中,全局強調的是它的生命期,而不是它的作用域,所以有時可能把兩者的概念互換。
一般來說,在一起定義的兩個全局變量,在內存的中位置是相鄰的。這是一個簡單的常識,但有時挺有用,如果一個全局變量被破壞了,不防先查查其前後相關變量的訪問代碼,看看是否存在越界訪問的可能。
在ELF格式的可執行文件中,全局內存包括三種:bss、data和rodata。其它可執行文件格式與之類似。了解了這三種數據的特點,我們才能充分發揮它們的長處,達到速度與空間的最優化。
1. bss
已經記不清bss代表Block Storage Start還是Block Started by Symbol。像這我這種沒有用過那些史前計算機的人,終究無法明白這樣怪異的名字,也就記不住了。不過沒有關係,重要的是,我們要清楚bss全局變量有什麼樣特點,以及如何利用它。
通俗的說,bss是指那些沒有初始化的和初始化為0的全局變量。它有什麼特點呢,讓我們來看看一個小程序的表現。
int bss_array[1024 * 1024] = {0};
int main(int argc, char* argv[]) { return 0; } |
[root@localhost bss]# gcc -g bss.c -o bss.exe
[root@localhost bss]# ll
total 12
-rw-r--r-- 1 root root 84 Jun 22 14:32 bss.c
-rwxr-xr-x 1 root root 5683 Jun 22 14:32 bss.exe
變量bss_array的大小為4M,而可執行文件的大小隻有5K。 由此可見,bss類型的全局變量隻占運行時的內存空間,而不占文件空間。
另外,大多數操作係統,在加載程序時,會把所有的bss全局變量全部清零,無需要你手工去清零。但為保證程序的可移植性,手工把這些變量初始化為0也是一個好習慣。
2. data
與bss相比,data就容易明白多了,它的名字就暗示著裏麵存放著數據。當然,如果數據全是零,為了優化考慮,編譯器把它當作bss處理。通俗的說,data指那些初始化過(非零)的非const的全局變量。它有什麼特點呢,我們還是來看看一個小程序的表現。
int data_array[1024 * 1024] = {1};
int main(int argc, char* argv[]) { return 0; } |
[root@localhost data]# gcc -g data.c -o data.exe
[root@localhost data]# ll
total 4112
-rw-r--r-- 1 root root 85 Jun 22 14:35 data.c
-rwxr-xr-x 1 root root 4200025 Jun 22 14:35 data.exe
僅僅是把初始化的值改為非零了,文件就變為4M多。由此可見,data類型的全局變量是即占文件空間,又占用運行時內存空間的。
3. rodata
rodata的意義同樣明顯,ro代表read only,即隻讀數據(const)。關於rodata類型的數據,要注意以下幾點:
l 常量不一定就放在rodata裏,有的立即數直接編碼在指令裏,存放在代碼段(.text)中。
l 對於字符串常量,編譯器會自動去掉重複的字符串,保證一個字符串在一個可執行文件(EXE/SO)中隻存在一份拷貝。
l rodata是在多個進程間是共享的,這可以提高空間利用率。
l 在有的嵌入式係統中,rodata放在ROM(如norflash)裏,運行時直接讀取ROM內存,無需要加載到RAM內存中。
l 在嵌入式linux係統中,通過一種叫作XIP(就地執行)的技術,也可以直接讀取,而無需要加載到RAM內存中。
由此可見,把在運行過程中不會改變的數據設為rodata類型的,是有很多好處的:在多個進程間共享,可以大大提高空間利用率,甚至不占用RAM空間。同時由於rodata在隻讀的內存頁麵(page)中,是受保護的,任何試圖對它的修改都會被及時發現,這可以幫助提高程序的穩定性。
4. 變量與關鍵字
static關鍵字用途太多,以致於讓新手模煳。不過,總結起來就有兩種作用,改變生命期和限製作用域。如:
l 修飾inline函數:限製作用域
l 修飾普通函數:限製作用域
l 修飾局部變量:改變生命期
l 修飾全局變量:限製作用域
const 關鍵字倒是比較明了,用const修飾的變量放在rodata裏,字符串默認就是常量。對const,注意以下幾點就行了。
l 指針常量:指向的數據是常量。如 const char* p = “abc”; p指向的內容是常量 ,但p本身不是常量,你可以讓p再指向”123”。
l 常量指針:指針本身是常量。如:char* const p = “abc”; p本身就是常量,你不能讓p再指向”123”。
l 指針常量 + 常量指針:指針和指針指向的數據都是常量。const char* const p =”abc”; 兩者都是常量,不能再修改。
violatile關鍵字通常用來修飾多線程共享的全局變量和IO內存。告訴編譯器,不要把此類變量優化到寄存器中,每次都要老老實實的從內存中讀取,因為它們隨時都可能變化。這個關鍵字可能比較生僻,但千萬不要忘了它,否則一個錯誤讓你調試好幾天也得不到一點線索。
最後更新:2017-04-03 07:57:10