Подтвердить что ты не робот

Почему a = (b ++) имеет такое же поведение, как a = b ++?

Я пишу небольшое тестовое приложение на C с GCC 4.8.4, предварительно установленным на моем Ubuntu 14.04. И я запутался в том, что выражение a=(b++); ведет себя так же, как и a=b++;. Используется следующий простой код:

#include <stdint.h>
#include <stdio.h>

int main(int argc, char* argv[]){
    uint8_t a1, a2, b1=10, b2=10;
    a1=(b1++);
    a2=b2++;

    printf("a1=%u, a2=%u, b1=%u, b2=%u.\n", a1, a2, b1, b2);

}

Результат после компиляции gcc a1=a2=10, а b1=b2=11. Тем не менее, я ожидал, что в круглых скобках будет b1 увеличено до того, как его значение будет присвоено a1.

А именно, a1 должно быть 11, а a2 равно 10.

Кто-нибудь узнает об этой проблеме?

4b9b3361

Ответ 1

Цитата из C99: 6.5.2.4:

Результатом оператора postfix ++ является значение операнда. После того, как результат будет получен, значение операнда будет увеличено. (То есть к нему добавляется значение 1 соответствующего типа.) См. обсуждение аддитивных операторов и составное назначение для информацию об ограничениях, типах и преобразованиях и операции над указателями. Побочный эффект обновления сохраненного значения Операнда происходит между предыдущей и следующей последовательностью точка.

Вы можете посмотреть приложение C99: приложение C, чтобы понять, что представляют собой действительные точки последовательности.

В вашем вопросе просто добавление скобок не изменяет точки последовательности, это делает только символ ;.

Или, другими словами, вы можете просмотреть его, как временную копию b, а побочный эффект - оригинал b. Но, пока точка последовательности не будет достигнута, вся оценка выполняется на временной копии b. Временная копия b затем отбрасывается, побочный эффект, то есть операция приращения фиксируется в хранилище, когда достигается точка последовательности.

Ответ 2

Тем не менее, я ожидал, что в круглых скобках будет добавлен b1 до того, как его значение будет присвоено a1

Вы не должны были ожидать, что: размещение скобок вокруг выражения increment не влияет на применение его побочных эффектов.

Побочные эффекты (в этом случае это означает, что запись 11 в b1) применяется некоторое время после получения текущего значения b1. Это может произойти до или после полного выражения выражения. Вот почему пост-инкременс останется после инкремента с круглыми скобками или без них. Если вам нужен предварительный инкремент, поместите ++ перед переменной:

a1 = ++b1;

Ответ 3

Скобки могут быть сложно обдумать. Но они не означают, "убедитесь, что все внутри происходит первым".

Предположим, что

a = b + c * d;

Чем выше приоритет умножения на добавление, тем мы сообщим, что компилятор устроит умножение на c на d, а затем добавит результат в b. Если мы хотим другую интерпретацию, мы можем использовать круглые скобки:

a = (b + c) * d;

Но предположим, что у нас есть некоторые вызовы функций, брошенные в микс. То есть предположим, что мы пишем

 a = x() + y() * z();

Теперь, хотя ясно, что возвращаемое значение y() будет умножено на возвращаемое значение z(), можем ли мы сказать что-либо о порядке, что x(), y() и z() будут называется в? Ответ: нет, мы абсолютно не можем! Если вы вообще не уверены, я приглашаю вас попробовать его, используя функции x, y и z следующим образом:

int x() { printf("this is x()\n"); return 2; }
int y() { printf("this is y()\n"); return 3; }
int z() { printf("this is z()\n"); return 4; }

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

 a = (x() + y()) * z();

порядок вызовов x, y и z оставался точно таким же, компилятор только что организовал, чтобы комбинировать свои результаты по-разному.

Наконец, важно понять, что выражения типа i++ выполняют две вещи: принимают значение i и добавляют к нему 1, а затем сохраняют новое значение обратно в i. Но магазин обратно в i не обязательно происходит сразу, это может произойти позже. И вопрос о том, "когда именно возвращается магазин в i?"? вроде как вопрос о том, "когда функция x вызывается?". Вы не можете сказать, это до компилятора, обычно это не имеет значения, он будет отличаться от компилятора к компилятору, если вам действительно все равно, вам придется сделать что-то еще, чтобы заставить порядок.

И в любом случае помните, что определение i++ заключается в том, что оно выдает старое значение i внешнему выражению. Это довольно абсолютное правило, и его нельзя изменить, просто добавив некоторые круглые скобки! Это не то, что круглые скобки.

Вернемся к предыдущему примеру с функциями x, y и z. Я заметил, что функция x была вызвана первой. Предположим, что я этого не хотел, предположим, что мне нужно сначала вызвать функции y и z. Могу ли я достичь этого, написав

x = z() + ((y() * z())?

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

Вверху выше, я сказал: "Если вам действительно все равно, вам придется сделать что-то еще, чтобы заставить заказ". Обычно вам нужно использовать некоторые временные переменные и некоторые дополнительные инструкции. (Технический термин - "вставить некоторые точки последовательности".) Например, чтобы вызвать сначала вызов y и z, я мог написать

c = y();
d = z();
b = x();
a = b + c * d;

В вашем случае, если вы хотите убедиться, что новое значение b присвоено a, вы можете написать

c = b++;
a = b;

Но, конечно, это глупо - если все, что вы хотите сделать, - это приращение b и присвоить новое значение a, что префикс ++ предназначен для:

a = ++b;

Ответ 4

Ваши ожидания полностью необоснованны.

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

Кроме того, по определению выражение post-increment b++ оценивает исходное значение b. Это требование останется на месте, независимо от того, сколько пар круглых скобок вы добавляете вокруг b++. Даже если скобки каким-то образом "вынуждали" мгновенное приращение, языку все равно потребуется (((b++))) для оценки старого значения b, что означает, что a все равно будет гарантированно получать неинстрицированное значение b.

Круглые скобки влияют только на синтаксическую группировку между операторами и их операндами. Например, в вашем исходном выражении a = b++ можно сразу спросить, есть ли теги ++ только b или результат a = b. В вашем случае, добавив круглые скобки, вы просто принудительно принудительно применили оператор ++ для применения к (для группировки с) операндом b. Однако в соответствии с синтаксисом языка (и приоритетом и ассоциативностью оператора, полученными из него), ++ уже применяется к b, то есть унарный ++ имеет более высокий приоритет, чем двоичный =. Ваши круглые скобки ничего не изменили, он только повторил группировку, которая уже была там неявно. Следовательно, никаких изменений в поведении.

Ответ 5

Скобки полностью синтаксические. Они просто группируют выражения, и они полезны, если вы хотите переопределить приоритет или ассоциативность операторов. Например, если вы используете круглые скобки здесь:

a = 2*(b+1);

вы имеете в виду, что результат b+1 должен быть удвоен, тогда как если вы опустите круглые скобки:

a = 2*b+1;

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

   =                      =
  / \                    / \
 a   *                  a   +
    / \                    / \
   2   +                  *   1
      / \                / \
     b   1              2   b

a = 2*(b+1);            a = 2*b+1;

Используя круглые скобки, вы можете изменить дерево синтаксиса, соответствующее вашей программе, и (разумеется) другой синтаксис может соответствовать разной семантике.

С другой стороны, в вашей программе:

a1 = (b1++);
a2 = b2++;
Скобки

являются избыточными, поскольку оператор присваивания имеет более низкий приоритет, чем приращение постфикса (++). Два присвоения эквивалентны; в обоих случаях соответствующее дерево синтаксиса выглядит следующим образом:

    =
   / \
  a   ++ (postfix)
      |
      b

Теперь, когда мы закончили синтаксис, отпустите семантику. Это утверждение означает: оцените b++ и присвойте результат a. Оценка b++ возвращает текущее значение b (которое равно 10 в вашей программе) и в качестве побочного эффекта увеличивает b (теперь это становится 11). Возвращаемое значение (то есть 10) присваивается a. Это то, что вы наблюдаете, и это правильное поведение.

Ответ 6

ОК, в двух словах: b++ - выражение унарное, а круглые скобки вокруг него никогда не будут влиять на приоритет арифметических операций, поскольку оператор приращения ++ имеет один из наивысший (если не самый высокий) приоритет в C. Пока в a * (b + c), (b + c) является двоичным выражением (не путать с системой двоичной нумерации!) из-за переменной b и его добавление c. Поэтому его легко запомнить: круглые скобки, расположенные вокруг двоичных, тройных, четвертичных... + выражений INF будут почти всегда иметь влияние на приоритет (*); скобки вокруг унарных НИКОГДА не будут - потому что они "достаточно сильны", чтобы "выдерживать" группировку в круглых скобках.

(*) Как обычно, есть некоторые исключения из правила, если только несколько: e. г. -> (для доступа к элементам указателей на структуры) имеет очень сильное связывание, несмотря на то, что он является двоичным оператором. Тем не менее, новички C, скорее всего, займут довольно много времени, пока они не смогут написать -> в своем коде, так как им потребуется передовое понимание как указателей, так и структур.

Ответ 7

Однако я ожидал, что в круглых скобках будет увеличен b1 до того, как его значение будет присвоено a1.

Вы не назначаете b1 в a1: вы назначаете результат выражения postincrement.

Рассмотрим следующую программу, которая печатает значение b при выполнении назначения:

#include <iostream>
using namespace std;

int b;

struct verbose
{
    int x;

    void operator=(int y) {
        cout << "b is " << b << " when operator= is executed" << endl;  
        x = y;
    }
};

int main() {
    // your code goes here
    verbose a;
    b = 10;
    a = b++;
    cout << "a is " << a.x << endl;
    return 0;
}

Я подозреваю, что это поведение undefined, но, тем не менее, при использовании ideone.com я получаю вывод, показанный ниже

b is 11 when operator= is executed
a is 10

Ответ 8

Скобки не будут изменять поведение post-increment.

a1 = (b1 ++);//b1 = 10

Он равен,

 uint8_t mid_value = b1++; //10
 a1 = (mid_value); //10

Ответ 9

Размещение ++ в конце инструкции (так называемый пост-инкремент) означает, что приращение должно выполняться после инструкции.

Даже включение переменной в круглую скобку не изменяет того факта, что она будет увеличена после выполнения инструкции.

Из learn.geekinterview.com:

В постфиксной форме приращение или декремент происходит после того, как значение используется в оценке выражения.

В операции препинания приращения или декремента приращение или декремент происходит до того, как значение используется в оценке выражения.

Вот почему a = (b++) и a = b++ одинаковы с точки зрения поведения.

В вашем случае, если вы хотите сначала увеличить b, вы должны использовать pre-increment, ++b вместо b++ или (b++).

Изменить

a1 = (b1++);

к

a1 = ++b1; // b will be incremented before it is assigned to a.

Ответ 10

Чтобы сделать это коротким: b++ увеличивается после выполнения инструкции

Но даже после этого результат b++ помещается в a.

Из-за этого круглые скобки не меняют значение здесь.