2014-02-17 35 views
-2

爲什麼這個宏給出輸出144,而不是121?#define SQR(x)x * x。意外的答案

#include<iostream> 
#define SQR(x) x*x 

int main() 
{ 
    int p=10; 
    std::cout<<SQR(++p); 
} 
+3

如果您正在嘗試學習C++,爲什麼不使用內聯函數?正如你所看到的,宏可能會導致評估問題... – crashmstr

+0

請參閱Bjarne Stroustrups [FAQ](http://www.stroustrup.com/bs_faq2.html#macro)。它解釋了使用宏時出了什麼問題,並給出了與您遇到問題時完全相同的示例。 – user2079303

回答

5

與此宏平方的方法有兩個問題:

首先,對於參數++p,增量操作進行兩次。這當然不是有意的。 (作爲一般的經驗法則,不要在「一行」中做幾件事情,將它們分成更多的陳述)。它甚至不會停止增加兩次:這些增量的順序沒有定義,所以沒有保證這個操作的結果!

其次,即使您沒有++p作爲參數,您的宏中仍然存在一個錯誤!考慮輸入1 + 1。預期的輸出是41+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; 
} 
5

因爲您使用的是未定義的行爲。當宏展開時,你的代碼變成這樣:

std::cout<<++p*++p; 

使用在同一個語句相同的變量多次在遞增/遞減運算是不確定的行爲。

+0

你有這方面的參考嗎?如何/爲什麼這是未定義的?謝謝。 – jia103

9

如果您閱讀了關於預處理器的任何信息,您已經知道這是預處理器宏的一個缺陷。問題在於++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直接放在調用位置,從而使其與宏擴展一樣「優化」。

+0

您可以將''A''或任何其他數值傳遞給此函數(假定您的參數類型爲int);更糟的是,如果你傳遞了一個浮點值,它會默默地給出一個意想不到的結果。模板可能更適合,尤其是作爲宏的替代品。 –

+0

@MikeSeymour當然你是對的,但我認爲模板可能稍微超過OP的頭部。 –

4

因爲SQR(++ p)擴展爲++ p ++ ++ p,它在C/C++中有未定義的行爲。在這種情況下,它在評估乘法之前將p遞增兩次。但你不能依賴這一點。它可能是121(甚至42)與不同的C/C++編譯器。

+1

而且,更重要的是,它在C++中也有未定義的行爲(關於這個問題)。 – Angew

+0

@Angew:當然可以。謝謝。我在我的文本中將C更改爲C/C++。 – CliffordVienna

+0

感謝您的編輯。有一個upvote :-) – Angew

相關問題