2014-02-17 3 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. Ожидаемый результат: 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; 
} 
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

@ Ангел: да, конечно. Спасибо. Я изменил C в C/C++ в своем тексте. – CliffordVienna

+0

Спасибо за редактирование. Имейте upvote :-) – Angew

Смежные вопросы