Поведение оператора приращения ввода
Возможный дубликат:
Поведение оператора post и post increment в C, С++, Java и С#
Вот пример:
void foo(int i, int j)
{
printf("%d %d", i, j);
}
...
test = 0;
foo(test++, test);
Я ожидаю получить вывод "0 1", но я получаю "0 0",
Что дает?
Ответы
Ответ 1
Это пример неуказанного поведения. В стандарте не говорится, какие аргументы порядка должны оцениваться. Это решение для реализации компилятора. Компилятор может свободно оценивать аргументы функции в любом порядке.
В этом случае он выглядит как фактически обрабатывает аргументы справа налево вместо ожидаемых слева направо.
В целом, делать побочные эффекты в аргументах - плохая практика программирования.
Вместо foo (test ++, test); следует написать foo (test, test + 1); тест ++;
Это будет семантически эквивалентно тому, что вы пытаетесь выполнить.
Изменить:
Как правильно указывает Энтони, это undefined как для чтения, так и для изменения одной переменной без промежуточной точки последовательности. Поэтому в этом случае поведение действительно undefined. Таким образом, компилятор может генерировать любой желаемый код.
Ответ 2
Это не просто неуказанное поведение, это действительно undefined поведение.
Да, порядок оценки аргументов неуточнен, но undefined - читать и изменять одну переменную без промежуточной точки последовательности, если только чтение не предназначено исключительно для вычисления нового значения. Нет никакой точки последовательности между оценками аргументов функции, поэтому f(test,test++)
- undefined поведение: test
считывается для одного аргумента и изменяется для другого. Если вы переместите модификацию в функцию, тогда вы в порядке:
int preincrement(int* p)
{
return ++(*p);
}
int test;
printf("%d %d\n",preincrement(&test),test);
Это потому, что есть точка последовательности на входе и выходе на preincrement
, поэтому вызов должен быть оценен до или после простого чтения. Теперь заказ просто не указан.
Заметим также, что оператор запятой предоставляет точку последовательности, поэтому
int dummy;
dummy=test++,test;
отлично - приращение происходит до чтения, поэтому dummy
устанавливается в новое значение.
Ответ 3
Все, что я сказал первоначально, НЕПРАВИЛЬНО! Точка во времени, при которой побочный эффект вычисляется , неуточнен. Visual С++ будет выполнять приращение после вызова функции foo(), если test является локальной переменной, но если тест объявлен как статический или глобальный, он будет увеличен до вызова функции foo() и даст разные результаты, хотя конечное значение тест будет правильным.
Приращение действительно должно выполняться в отдельном заявлении после вызова функции foo(). Даже если поведение было указано в стандарте C/С++, это было бы запутанным. Вы могли бы подумать, что компиляторы С++ помечены как потенциальная ошибка.
Здесь - хорошее описание точек последовательности и неуказанного поведения.
< ---- НАЧАЛО НЕПРАВИЛЬНОГО НЕПРАВИЛЬНОГО НЕПРАВИЛЬНОГО ---- >
"++" бит "test ++" запускается после вызова foo. Итак, вы проходите (0,0) в foo, а не (1,0)
Вот вывод ассемблера из Visual Studio 2002:
mov ecx, DWORD PTR _i$[ebp]
push ecx
mov edx, DWORD PTR tv66[ebp]
push edx
call _foo
add esp, 8
mov eax, DWORD PTR _i$[ebp]
add eax, 1
mov DWORD PTR _i$[ebp], eax
Приращение выполняется ПОСЛЕ вызова функции foo(). Хотя это поведение по дизайну, это, безусловно, запутывает случайного читателя и, вероятно, его следует избегать. Приращение действительно должно выполняться в отдельном заявлении после вызова функции foo()
< ---- КОНЕЦ НЕПРАВИЛЬНОГО НЕПРАВИЛЬНОГО НЕИСПРАВНОСТИ ---- >
Ответ 4
Это "неуказанное поведение", но на практике с тем, как указан стек вызовов C, он почти всегда гарантирует, что вы увидите его как 0, 0 и никогда 1, 0.
Как заметил кто-то, ассемблер, выводимый VC, сначала вставляет самый правый параметр в стек. Это то, как вызовы функции C реализованы в ассемблере. Это должно учитывать функцию "бесконечный список параметров". Нажав параметры в порядке справа налево, первым параметром гарантируется верхний элемент в стеке.
Возьмите подпись printf:
int printf(const char *format, ...);
Эти эллипсы обозначают неизвестное количество параметров. Если параметры были сдвинуты влево-вправо, формат будет находиться в нижней части стека, размер которого мы не знаем.
Зная, что в C (и С++) параметры обрабатываются слева направо, мы можем определить самый простой способ анализа и интерпретации вызова функции. Перейдите к концу списка параметров и начните нажимать, оценивая любые сложные операторы, когда вы идете.
Однако даже это не может спасти вас, поскольку большинство компиляторов C имеют возможность анализировать функции "Pascal style". И все это означает, что параметры функции вставляются в стек слева направо. Если, например, printf был скомпилирован с помощью параметра Pascal, то выход, скорее всего, будет 1, 0 (однако, поскольку printf использует эллипс, я не думаю, что его можно скомпилировать в стиле Pascal).
Ответ 5
C не гарантирует порядок оценки параметров в вызове функции, поэтому с этим вы можете получить результаты "0 1" или "0 0". Порядок может быть изменен от компилятора к компилятору, и один и тот же компилятор может выбирать разные заказы на основе параметров оптимизации.
Безопаснее писать foo (test, test + 1), а затем выполнить тест ++ в следующей строке. Во всяком случае, компилятор должен оптимизировать его, если это возможно.
Ответ 6
Компилятор может не оценивать аргументы в ожидаемом порядке.
Ответ 7
Порядок оценки аргументов функции - undefined. В этом случае кажется, что он сделал их справа налево.
(Изменение переменных между точками последовательности в основном позволяет компилятору делать все, что угодно.)
Ответ 8
Um, теперь, когда OP был отредактирован для согласованности, он не синхронизирован с ответами. Правильный ответ о порядке оценки правильный. Однако конкретные возможные значения различаются для foo (++ test, test); случай.
Тест ++ будет увеличен до передачи, поэтому первый аргумент всегда будет 1. Второй аргумент будет 0 или 1 в зависимости от порядка оценки.
Ответ 9
В соответствии со стандартом C поведение undefined имеет более чем одну ссылку на переменную в одной точке последовательности (здесь вы можете думать об этом как о выражении или параметрах функции), где один из Более того, эти ссылки включают предварительную модификацию.
Так:
foo (f ++, f) < - undefined относительно того, когда f увеличивается.
И аналогично (я вижу это все время в коде пользователя):
* p = p ++ + p;
Обычно компилятор не будет изменять свое поведение для этого типа вещей (кроме основных изменений).
Избегайте этого, включив предупреждения и обращая на них внимание.
Ответ 10
Чтобы повторить то, что говорили другие, это не неуказанное поведение, а скорее undefined. Эта программа может легально выводить ничего или ничего, оставлять n при любом значении или отправлять оскорбительные письма своему боссу.
Как правило, составители компиляторов обычно просто делают то, что проще для них писать, что обычно означает, что программа будет извлекать n один или два раза, вызывать функцию и увеличивать когда-то. Это, как и любое другое мыслимое поведение, прекрасно соответствует стандарту. Нет причин ожидать такого же поведения между компиляторами или версиями или с разными параметрами компилятора. Нет причин, по которым два разных, но похожих друг друга примера в одной и той же программе должны быть скомпилированы последовательно, хотя я бы сделал ставку.
Короче говоря, не делайте этого. Протестируйте его в разных обстоятельствах, если вам интересно, но не притворяйтесь, что есть один правильный или даже предсказуемый результат.