2011年12月9日 星期五

c runtime - array vs pointer

這主題不算是 c runtime, 只是我自己想這樣分類。

From wish list books

程式設計師面試寶典 (已經絕版, 連這種書都會絕版, 真讓我驚訝, 這可算是電腦界的高普考書籍耶!): p 7-6 提到的考題:
char c[]="hello";
char *c="hello";
有什麼不同?

書上花了近兩頁的篇幅在解釋, 不過沒有告訴你為什麼?也許你和我一樣, 會想知道為什麼? 看看翻出來的組合語言就知道了。

a.c
1 int main()
2 {
3 char str[]="hello";
4 return 0;
5 }

gcc -S a.c 得到 a.s

9 movl $1819043176, -6(%ebp)
10 movw $111, -2(%ebp)

1819043176 = 0x6C6C6568
111 = 0x6f
"hello" = {0x68, 0x65, 0x6c, 0x6c, 0x6f}
所以第 9, 10 行就是把 hello 放到 %ebp 指的 stack 中。

a.s
1 .file "a.c"
2 .text
3 .globl main
4 .type main, @function
5 main:
6 pushl %ebp
7 movl %esp, %ebp
8 subl $16, %esp
9 movl $1819043176, -6(%ebp)
10 movw $111, -2(%ebp)
11 movl $0, %eax
12 leave
13 ret
14 .size main, .-main
15 .ident "GCC: (Ubuntu 4.4.3-4ubuntu5) 4.4.3"
16 .section .note.GNU-stack,"",@progbits
17



a-ro.c
1 int main()
2 {
3 char *str="hello";
4 return 0;
5 }

gcc -S a-ro.c 得到 a-ro.s

2 .section .rodata
3 .LC0:
4 .string "hello"


"hello" 是放在 .rodata section, 這會佔用執行檔案的空間, 所以執行檔會大一些。不過編譯後的執行檔案大小一樣, obj 檔倒是有大小的差異。可能還有我沒搞懂的地方。


a-ro.s
1 .file "a-ro.c"
2 .section .rodata
3 .LC0:
4 .string "hello"
5 .text
6 .globl main
7 .type main, @function
8 main:
9 pushl %ebp
10 movl %esp, %ebp
11 subl $16, %esp
12 movl $.LC0, -4(%ebp)
13 movl $0, %eax
14 leave
15 ret
16 .size main, .-main
17 .ident "GCC: (Ubuntu 4.4.3-4ubuntu5) 4.4.3"
18 .section .note.GNU-stack,"",@progbits
19


看來差異不大, 但題目是如果把 c return 回去的話會怎樣?

char c[]="hello";
char *c="hello";
return c;

c[] 放在 stack 裡, 在 return 時, stack 會被清掉不應該這麼描述, 應該說: stack 的位址很多 code 會使用 (ex: call a function), function return 時, 若要存取這個 stack 位址的時候沒有其他 code 來蓋掉這個 stack 位址, 那就不會有問題, 但這是無法保證的。所以無法預期 c[] 的值還會是 "hello", *c 則是在 .rodata, 在執行環境記憶體已經保留出一個位置存放 "hello", 所以沒有問題。

若程式執行起來沒問題, 那是運氣好, 不過, 相信我, 你不會一直有這樣的運氣, 這種 bug 很難找。

至於 *c 存在 .rodata, 所以 *c 指到的 "hello" 是無法修改的, 無法使用 c[0]='z' 來修改。既然唯讀, 也難怪大部份都是看到 const char *c="hello"; 這樣的寫法, 因為我讀過的書說要這樣寫,直覺的寫法就應該是這樣, 現在我知道為什麼了。這也很讓我驚訝, C 是這麼隱晦不清的語言, char *c 從語法來看, 完全看不出來有 const 的意思, 也難怪在 The C Programming Language 之後, 會有那麼多的書籍來討論 C 語言了。

20120312 補充:
完全是我自己搞錯了。
float f=0.1;
int i=1;
f=i;
這通常很容易看出會有問題, 因為 f, i type 是不同的。

char *c="hello" 也是:
"hello" 是 const char pointer
c 是 char pointer,
兩個 type 也是不同, 把他們用 = 在一起, 會發生什麼事情, 自然是寫 code 的人要負責。

在 gcc 的測試下,
char *c="hello";
c[0]='Q';
在執行的時候會得到 Segmentation fault, 因為改變唯讀記憶體的位址, 所以有這樣的行為, 在編譯期間, compiler 沒有抓到這樣的警告, 我有點納悶。當然, 若是用 const char *c="hello";
編譯器就會警告了。

static array vs static pointer


測試讀寫的情形。str[2]='q';
static array 不會放到 .rodata, 所以可以讀寫。

a-static-array.c
1 int main()
2 {
3 //char str[]="hello";
4 static char str[]="hello";
5 str[2]='q';
6 return 0;
7 }


a-static-array.s
1 .file "a.c"
2 .text
3 .globl main
4 .type main, @function
5 main:
6 pushl %ebp
7 movl %esp, %ebp
8 movb $113, str.1247+2
9 movl $0, %eax
10 popl %ebp
11 ret
12 .size main, .-main
13 .data
14 .type str.1247, @object
15 .size str.1247, 6
16 str.1247:
17 .string "hello"
18 .ident "GCC: (Ubuntu 4.4.3-4ubuntu5) 4.4.3"
19 .section .note.GNU-stack,"",@progbits
20




pointer 的版本似乎都會將 "hello" 放到 .rodata (不管有沒 static), 所以看來 pointer 的版本應該都只能讀 (執行結果會得到 Segmentation fault), 不能寫。

a-static-pointer.c
1 int main()
2 {
3 //char str[]="hello";
4 static char *str="hello";
5 str[2]='q';
6 return 0;
7 }


a-static-pointer.s
1 .file "a.c"
2 .text
3 .globl main
4 .type main, @function
5 main:
6 pushl %ebp
7 movl %esp, %ebp
8 movl str.1247, %eax
9 addl $2, %eax
10 movb $113, (%eax)
11 movl $0, %eax
12 popl %ebp
13 ret
14 .size main, .-main
15 .section .rodata
16 .LC0:
17 .string "hello"
18 .data
19 .align 4
20 .type str.1247, @object
21 .size str.1247, 4
22 str.1247:
23 .long .LC0
24 .ident "GCC: (Ubuntu 4.4.3-4ubuntu5) 4.4.3"
25 .section .note.GNU-stack,"",@progbits
26


size 差異:
7145 2011-12-09 16:00 a-static-array
7143 2011-12-09 16:00 a-static-pointer

20120117 補充:

在 C 程式語言 (第二版) p C-3 看到 16. 字串常數不再可被更改 ..., 也許就是描述這個情形。
英文版本在 p 260 Strings are no longer modifiable, and so may be placed inread-only memory.

沒有留言:

張貼留言

使用 google 的 reCAPTCHA 驗證碼, 總算可以輕鬆留言了。

我實在受不了 spam 了, 又不想讓大家的眼睛花掉, 只好放棄匿名留言。這是沒辦法中的辦法了。留言的朋友需要有 google 帳號。