Поведение оператора приращения ввода

Возможный дубликат:
Поведение оператора 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 один или два раза, вызывать функцию и увеличивать когда-то. Это, как и любое другое мыслимое поведение, прекрасно соответствует стандарту. Нет причин ожидать такого же поведения между компиляторами или версиями или с разными параметрами компилятора. Нет причин, по которым два разных, но похожих друг друга примера в одной и той же программе должны быть скомпилированы последовательно, хотя я бы сделал ставку.

Короче говоря, не делайте этого. Протестируйте его в разных обстоятельствах, если вам интересно, но не притворяйтесь, что есть один правильный или даже предсказуемый результат.