Почему этот макрос дает вывод 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
@ Ангел: да, конечно. Спасибо. Я изменил C в C/C++ в своем тексте. – CliffordVienna
Спасибо за редактирование. Имейте upvote :-) – Angew
Если вы пытаетесь изучить C++, почему бы не встроить функцию? Как вы можете видеть, макросы могут вызывать проблемы с оценкой ... – crashmstr
См. Bjarne Stroustrups [FAQ] (http://www.stroustrup.com/bs_faq2.html#macro). В нем объясняется, что не так с использованием макросов и дает точно такой же пример, с которым вы столкнулись. – user2079303