1. 事出有因
今天在刷面試題的時候遇到如下一個面試題:
#include <stdio.h>
#include <stdlib.h>
int main() {
int arr[] = { 6, 7, 8, 9, 10 };
int *ptr = arr;
*(ptr++) += 123;
printf("%d, %d\n", *ptr, *(++ptr));
system("pause");
return 0;
}
本以爲這道面試題普普通通,沒什麼特別的,剛開始以爲僅僅是考察++i,但是發現答案並非是那麼簡單,很天真,以爲代碼循序是從左到右,但是沒意識到printf還有壓棧問題。提前公佈答案吧,這道題輸出
程序輸出:8,8
該題在某程序猿面試寶典中,也僅僅說了:C中的printf計算參數時是從右到左壓棧的。
但是並沒有詳細細說,剛好對其有興趣就又去網上搜索相關大牛的博文學習,發現壓棧並不是那麼簡單,還是有些學問,比如如下壓棧問題:
1 i=1; printf("%d %d\n",i,i++); //結果 2 1
2 i=1; printf("%d %d\n",i++,i); //結果 1 2
3 i=1; printf("%d %d %d\n",i,i++,i); //結果 2 1 2
4 i=1; printf("%d %d %d %d\n",i,++i,i++,i); //結果 3 3 1 3
大家可以試着去分析其中的規律。。。。。
這時候大家就發現,這根本就沒有計算順序可言,確實,表面上看無論哪個方向計算都好像無法全部滿足如上輸出,但是我還是可以十分確定的告訴你,printf依舊是從右向左壓棧,然後再從棧頂逐個輸出的。
2. 結合(i++、++i)走進printf壓棧問題
printf的壓棧問題通常出現在輸出列表中有++相關的計算,瞭解這個問題,我們應該明白兩個知識點
(1). printf壓棧順序從右到左,入棧計算;然後再從棧頂逐個輸出,出棧直接輸出。
(2). i++會拷貝一份副本,++i直接在原數據操作。
大家都知道,使用 i++ 通常被稱爲先使用後++, 這種解釋十分通俗易懂,但是卻容易讓人誤入歧途,正確的解釋應該是,i會拷貝一份副本,i真值加一,但是原來的計算操作變量變成原來的拷貝出來的副本(可以認爲是一個全新的變量)。而++i則不會出現中間變量。
所以這是個printf壓棧與++碰撞出現的問題
接下來通過一個例子分析:
int a = 1;
printf("%d %d %d %d %d %d %d\n", a++, ++a, a++, ++a, a, a++, ++a);
這裏有一個規律,上面已經說到,僅僅只有 a++會拷貝,並且把輸出變量替換,而其他則都是操作 a變量,所以很容易得出如下輸出結果:
a3 a a2 a a a1 a
接下來逐步分析其中變量的值
首先是壓棧,這裏會涉及到計算以及 ++a 拷貝副本的問題:
int a = 1;
printf("%d %d %d %d %d %d %d\n", a++, ++a, a++, ++a, a, a++, ++a);
操作次數 | 操作說明 | 壓棧變量 | 當前棧 |
1 | ++a, a值加1,壓棧 a = 2 | a = 2 |
a |
2 | a++, 拷貝副本a1 = 2,將副本壓棧,a值加1, a = 3 | a1 = 2 |
a1 a |
3 | a, 無計算操作,直接壓棧 a = 3 | a = 3 |
a a1 a |
4 | ++a, a值加1,壓棧 a = 4 | a = 4 |
a a a1 a |
5 | a++, 拷貝副本a2 =4,將副本壓棧,a值加1, a = 5 | a2 = 4 |
a2 a a a1 a |
6 | ++a, a值加1,壓棧 a = 6 | a = 6 |
a a2 a a a1 a |
7 | a++, 拷貝副本a3 =6,將副本壓棧,a值加1, a = 7 | a3 = 6 |
a3 a a2 a a a1 a |
所以看出,最後輸出
a3 a a2 a a a1 a
通過表可以看出:a = 7; a3 = 6; a2 = 4; a1 = 2
所以輸出就是
6 7 4 7 7 2 7