1. 程式人生 > >從printf說開去(四)

從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的結果,不妨嘗試先自行分析一下。

(未完待續)