字符串聲明有兩種方式
char str1[] = “hello” ①
char *str2 = “world” ②
在有些函數中,如strtok,strrep中,只能使用①,為什么?
Str1和str2的區別在哪里?先說結論!
Str1的值”hello”存儲在可讀可寫區,而str2的值”world”存儲在只讀區。
某些函數會修改字符串,如果存放在只讀區,當然就會報錯了。
請看以下分析:
我們從底層入手,定義如下函數
文件名:main.c
#include <stdio.h>
char format[] = "Hello, %s !\n";
char* name = "yahoo!";
int main(int argc, char* argv[])
{
printf(format, name);
return 0;
}
Linux下編譯產生可執行文件
gcc -g -o main main.c
這時候得到可執行的elf文件main
對elf文件進行分析,首先獲取elf文件的符號表
$ readelf -s main
可以得到elf文件中的變量、函數、文件等的信息,我們只需要關注變量format和name兩個變量,首先分析format變量,從符號表中篩選出format變量的信息:
$ readelf -s main | grep format
結果如下:
這些信息是什么意思呢?結合符號表各列的定義,我們主要關注以下幾項:
Value:0804a014 (變量的值的內存起始位置)
Size:13 (變量的大小)
Ndx:24 (變量屬於哪個section)
而每個section存放的內容是不同的,常用的section定義如下:
- .text: 已編譯程序的機器代碼
- .rodata(read only data):只讀數據
- .data:已初始化的全局c變量,可讀可寫
- .bss:未初始化的全局c變量,不占用實際空間,作占位符使用。
- .symtab:符號表,存放程序中被定義和引用的函數和全局變量信息,不包含局部變量
Format變量存儲在編號為24的區域,我們再看這個是什么區域:
$ readelf -S main | grep 24
可見format變量的值存儲在.data區域,正是可讀可寫區!
再進入到.data中驗證一下,
$ readelf -x .data main
看圖片右邊,正是format這個字符串。
我們再看name這個變量,用類似的方法,
$ readelf -s main | grep name
結果如下
注意觀察,name這個變量也在24區,也在.data中,但長度只有4字節,而內存位置為0x0804a024
我們進入.data中一探究竟
我們需要計算內存位置,第二列的第一個字節內存位置為0x0804a01c,而name的內存位置是0x0804a024,相差8個字節,我們往后數8字節,name的值就出來了。就是圖上用紅框標記的部分,name的值不是一個字符串嗎,為什么變成了這樣一串數字?因為這串數字是內存地址,指向字符串的真正位置!那這個地址到底在哪里呢?考慮我是inter 89386架構,采用小端存儲數據。所以,圖中的e0840408應該從后往前,一個個字節讀,正是0x080484e0,那這個地址又指向哪里呢?我們需要查看各個section的內存范圍:
$ readelf -S main
在Addr這一列可以看到,內存范圍,觀察得到.rodata的范圍,起始是0x080484d8,結束是0x080484e8,而name變量指向的地址0x080484e0正在.rodata區間(不同機器范圍可能不同,重點在於該地址是在哪個區間內)。
我們到.rodata驗證一下:
$ readelf -x .rodata main
圖片右邊正是name對應的字符串!而該字符串在.rodata中,可讀不可寫。