爲什麼這個宏給出輸出144,而不是121?#define SQR(x)x * x。意外的答案
#include<iostream>
#define SQR(x) x*x
int main()
{
int p=10;
std::cout<<SQR(++p);
}
爲什麼這個宏給出輸出144,而不是121?#define SQR(x)x * x。意外的答案
#include<iostream>
#define SQR(x) x*x
int main()
{
int p=10;
std::cout<<SQR(++p);
}
與此宏平方的方法有兩個問題:
首先,對於參數++p
,增量操作進行兩次。這當然不是有意的。 (作爲一般的經驗法則,不要在「一行」中做幾件事情,將它們分成更多的陳述)。它甚至不會停止增加兩次:這些增量的順序沒有定義,所以沒有保證這個操作的結果!
其次,即使您沒有++p
作爲參數,您的宏中仍然存在一個錯誤!考慮輸入1 + 1
。預期的輸出是4
。 1+1
沒有副作用,所以應該沒問題吧?不,因爲SQR(1 + 1)
轉換爲1 + 1 * 1 + 1
,其計算結果爲3
。
爲了至少部分地解決這個宏,使用括號:
#define SQR(x) (x) * (x)
總之,你應該簡單地通過一個函數替換它(添加類型安全!)
int sqr(int x)
{
return x * x;
}
你能想到的使其成爲模板
template <typename Type>
Type sqr(Type x)
{
return x * x; // will only work on types for which there is the * operator.
}
和可能添加一個constexpr
(C++ 11),這是有用的如果您曾經計劃在模板中使用正方形:
constexpr int sqr(int x)
{
return x * x;
}
因爲您使用的是未定義的行爲。當宏展開時,你的代碼變成這樣:
std::cout<<++p*++p;
使用在同一個語句相同的變量多次在遞增/遞減運算是不確定的行爲。
你有這方面的參考嗎?如何/爲什麼這是未定義的?謝謝。 – jia103
如果您閱讀了關於預處理器的任何信息,您已經知道這是預處理器宏的一個缺陷。問題在於++p
這個表達式使用了兩次,因爲預處理器幾乎是逐字地將宏「調用」替換爲主體。
所以宏展開後編譯器看到的是什麼
std::cout<<++p*++p;
根據宏,你也可以用運算符優先級的問題,如果你不小心把括號需要的地方。
就拿宏如
// Macro to shift `a` by `b` bits
#define SHIFT(a, b) a << b
...
std::cout << SHIFT(1, 4);
這會導致代碼
std::cout << 1 << 4;
可能不被什麼被通緝或預期。
如果你想避免這種情況,然後使用內聯函數來代替:
inline int sqr(const int x)
{
return x * x;
}
這有兩件事情去爲它:首先是表達++p
將只進行評估一次。另一件事是,現在你不能通過int
值以外的任何東西到函數。使用預處理器宏,您可以像調用SQR("A")
那樣「調用」它,並且預處理器不會在意,而是從編譯器中獲取(有時)編譯錯誤。
此外,編號爲inline
的編譯器可能會完全跳過實際函數調用,並將(正確)x*x
直接放在調用位置,從而使其與宏擴展一樣「優化」。
您可以將''A''或任何其他數值傳遞給此函數(假定您的參數類型爲int);更糟的是,如果你傳遞了一個浮點值,它會默默地給出一個意想不到的結果。模板可能更適合,尤其是作爲宏的替代品。 –
@MikeSeymour當然你是對的,但我認爲模板可能稍微超過OP的頭部。 –
因爲SQR(++ p)擴展爲++ p ++ ++ p,它在C/C++中有未定義的行爲。在這種情況下,它在評估乘法之前將p遞增兩次。但你不能依賴這一點。它可能是121(甚至42)與不同的C/C++編譯器。
而且,更重要的是,它在C++中也有未定義的行爲(關於這個問題)。 – Angew
@Angew:當然可以。謝謝。我在我的文本中將C更改爲C/C++。 – CliffordVienna
感謝您的編輯。有一個upvote :-) – Angew
如果您正在嘗試學習C++,爲什麼不使用內聯函數?正如你所看到的,宏可能會導致評估問題... – crashmstr
請參閱Bjarne Stroustrups [FAQ](http://www.stroustrup.com/bs_faq2.html#macro)。它解釋了使用宏時出了什麼問題,並給出了與您遇到問題時完全相同的示例。 – user2079303