C語言的棧是靜態的
C語言有了可變參數之後,我們可以傳任意個數的參數了, 似乎挺動態的了,但是可變參數函數還是不夠動態。
一、鞭長莫及
我們可以在 main 中寫出好幾條參數個數不同的調用 sum 的語句, 但是具體到某一條語句,sum 的參數個數是一定的, 比如上一篇中的 sum(2, 3, 4) 的參數個數是 3。 如果程序運行中調用 sum 函數的時候, 參數個數根據用戶輸入而定,那就不能用可變參數來實現了。 也就是說不能用 sum 來實現以下這個函數的功能:
// 將數組 a 的所有元素(個數為 n)求和後返回
int d_sum(int n, int a[]);
當然,這個函數不用 sum 來做是很好實現的。 我再換一個問題,下面這個函數怎麼用 printf 來實現:
// fmt 存的是格式串,它描述了 n 個整數(數組 a 中) // 的格式,某次調用如下: // int a[] = {1, 2, 3}; // d_printf("%d+%d=%d", 3, a);
void d_printf(const char *fmt, int n, int a[]);
這就沒法做了吧!
二、尋根究底
d_printf 沒法實現的原因是這樣的代碼真沒法寫: 傳給 printf 的參數的個數到運行的時候才知道, 而調用 printf 的語句又必須明確的列出所有參數。
其根本原因是C語言的棧是靜態的, 上一篇的 va.c 編譯後的彙編代碼如下:
main:
pushl %ebp
movl %esp, %ebp
andl $-16, %esp
subl $16, %esp # 給main幀分配棧空間
movl $4, 8(%esp)
movl $3, 4(%esp)
movl $2, (%esp)
call sum # 調用變參函數 sum
movl $.LC0, (%esp)
movl %eax, 4(%esp)
call printf # 調用變參函數 printf
xorl %eax, %eax
leave
ret
可以看到雖然 main 函數中調用了兩個變參函數, 但是棧卻沒有一點動態可變的意思,居然是用一條 subl $16, %esp 分配了固定的 16 字節的棧空間 (編譯的時候計算得出需要12字節,取整吧,16字節!)。
而在 d_printf 的實現中需要分配 4+4*n 字節的棧空間, 用於存傳給 printf 的 格式串指針 和 n個整數, 用C語言是沒法實現囉。
三、另闢蹊徑
C語言不能直接使用寄存器,但是彙編可以, 如果我們在 d_printf 中嵌入一段彙編來修改 esp 寄存器, 達到動態分配棧空間的效果,然後存入參數,call printf, 就可以完成任務了。
接下來的兩篇就來實現 d_printf 囉!