從printf說開去(四)
(接上文)
對於函式:
float sumfunf(int num, …)
{
char* args = (char*)(&num)+sizeof(num);
double result = 0;
for(int n = 0; n < num; n++)
{
result += *(double*)(args + n*sizeof(double));
}
return (float)result;
}
第一個固定引數傳入2,代表累加的數字個數為2,不定引數部分傳入1和2作為引數,呼叫函式看看結果:
float r = sumfunf(2, 1, 2),此時的r值也是不確定的。(如果用vc7以上做一個最簡的控制檯測試,大部分情況下r的值返回應該是0。)
sumfunf函式在處理時,首先取得不定引數部分的偏移量,接下來按照“傳入的引數都是double”這個假定進行處理。而sizeof(double)為8,也即是說,
函式期望以8個位元組為單位讀取堆疊資料,此時的堆疊情況如下:
棧頂
———————————————————————————————
第一個引數num(用於表示不定引數個數),佔用空間4位元組。值為0×00000002
不定引數起始地址–> ———————————————————————————————-
0×00000001 (4位元組)
————————–
0×00000002 (4位元組)
————————–
…其他已經入棧的資料
通過看上面這個堆疊示意,問題應該已經很明顯了,sumfunf函式在取引數的時候是期望以double為單位獲取資料的,機器並不知道這個資料是什麼“型別”,
機器按double(8個位元組)佔用的位元組空間讀取,這樣,傳入的1、2這兩個值被當成了一個數據讀出來,按照double的規則進行處理,讀出的值為:0×0000000200000001 (此例中使用Big endian序)。
但是,問題出現了,這才讀出一個引數,我們還有一個引數到哪裡去讀呢?
sumfunf函式在讀出第一個引數後,將其值累加至result變數中,在讀取第二個引數時,取值的地址範圍已經超出了我們傳入的資料,然而程式並不會就此出錯,恰好在這幾個引數之後的堆疊地址中,資料是有效的!
這些資料可被正確的訪問,因此,函式在處理第二個引數會繼續順序讀取8個位元組!這8個位元組的值作為第二個引數進行處理,回顧一下雙精度浮點數的計算方式,對於0×0000000200000001這樣一個數來說,是近乎於0的,因此,最後返回的值是不是為0,就取決於函式從堆疊中順序讀取出來的第二個引數,而這個值是不確定的!我們之所以看到返回值為0.000000,是因為對於這個簡單程式而言,在特定的環境下,大部分情況下,讀取出來的第二個不定引數在轉換為double後,也是接近於0的。
回到printf(“%f”,10/3);的問題來說,產生大家看到的不確定結果也是這個原理。
對於printf(“%f”, 10/3, 0×40080000);這個能獲得3.000000的結果,不妨嘗試先自行分析一下。
(未完待續)