Является ли изменение объекта более чем один раз в выражении через его имя и через его ссылку хорошо определенным? - PullRequest
5 голосов
/ 24 февраля 2020

Здравствуйте, у меня простой вопрос: изменяет ли объект более одного раза в одном выражении; один раз через его идентификатор (имя) и второй через ссылку на него или указатель, который указывает на него Неопределенное поведение?

int i = 1;
std::cout << i << ", " << ++i << std::endl; //1- Error. Undefined Behavior

int& refI = i;
std::cout << i << ", " << ++refI << std::endl; //2-  Is this OK?

int* ptrI = &refI; // ptrI point to the referred-to object (&i)

std::cout << i << ", " << ++*ptrI << std::endl; // 3 is this also OK?
  • Во втором, кажется, работает нормально, но я запутался, потому что из того, что я узнал; Ссылка - это просто псевдоним для уже существующего объекта. и любое изменение в нем повлияет на объект, на который ссылаются. Таким образом, я вижу, что i и refI - это одно и то же, поэтому модифицируете один и тот же объект (i) более одного раза в одном и том же выражении.

  • Но почему все компиляторы рассматривают оператор 2 как четко определенное поведение?

  • Как насчет оператора 3 (ptrI)?

Ответы [ 3 ]

4 голосов
/ 24 февраля 2020

Все они имеют неопределенное поведение до C ++ 17 и все имеют четко определенное поведение после C ++ 17.

Обратите внимание, что вы не модифицируете i больше, чем один раз в любом примере. Вы изменяете его только с приращением.

Тем не менее, это также неопределенное поведение - иметь побочный эффект на одном скаляре (здесь приращение i) без последовательности с вычислением значения (здесь слева - ручное использование i). Независимо от того, вызван ли побочный эффект непосредственным воздействием на переменную, или с помощью ссылки, или указателя, не имеет значения.

До C ++ 17 оператор << не подразумевал какой-либо последовательности своих операндов, поэтому поведение не определено во всех ваших примерах.

Начиная с C ++ 17, оператор << гарантированно оценивает свои операнды слева направо. C ++ 17 также расширил правила последовательности операторов для перегруженных операторов при вызове с нотацией оператора. Таким образом, во всех ваших примерах поведение четко определено, и левостороннее использование i оценивается first , перед тем как значение i увеличивается.

Обратите внимание, однако, что некоторые компиляторы не внедрили эти изменения в правила оценки очень своевременно, поэтому, даже если вы используете флаг -std=c++17, он, к сожалению, может нарушать ожидаемое поведение в более старых и текущих версиях компилятора.

Кроме того По крайней мере, в случае G CC предупреждение -Wsequence-point явно задокументировано, чтобы предупредить даже о поведении, которое стало четко определенным в C ++ 17, чтобы помочь пользователю избежать написания кода, который будет иметь неопределенное поведение в C и более ранние версии C ++, см. G CC документацию .

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

2 голосов
/ 24 февраля 2020

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

Тем не менее, ваши три примера в точности эквивалентен с точки зрения порядка правил оценки (если мы рассматриваем только объект, определенный как i).

Поэтому давайте просто рассмотрим ваш первый пример:

std::cout << i << ", " << ++i << std::endl;

Для простоты мы можем игнорировать ", " и std::endl, следовательно:

std::cout << i << ++i;

enter image description here

  • V C (X) = значение вычисление X
  • SE (X) = побочные эффекты X
  • Exe c (X) = выполнение тела функции X
  • X <- - Y = X секвенируется после Y </li>

Начиная с c ++ 11 и до c ++ 17, это неопределенное поведение, поскольку побочные эффекты D (см. График) не секвенированы относительно вычисления значения C. Тем не менее, оба связаны с объектом i. Это неопределенное поведение.

Начиная с c ++ 17, существует дополнительная гарантия (для выражений << и >>), что как вычисление значения, так и побочные эффекты C будут упорядочены перед значением вычисления и побочные эффекты D (отмечены пунктирными линиями), поэтому код становится четким.

0 голосов
/ 24 февраля 2020

Все это приводит к неопределенному поведению. Тот факт, что компилятор не выдает предупреждение, не делает его не таким. С UB может случиться все что угодно: работает, иногда работает, падает, взрывает ваш компьютер, & c.

...