Невизначена поведінка та точки послідовності перезавантажені


84

Розгляньте цю тему як продовження наступної теми:

Попередній внесок
Невизначена поведінка та точки послідовності

Давайте ще раз переглянемо цей кумедний і заплутаний вираз (курсив викладено з вищезазначеної теми * посмішка *):

i += ++i;

Ми говоримо, що це викликає невизначену поведінку. Я вважаю , що , коли говорять про це, ми неявно припускаємо , що тип з iє одним з вбудованих типів.

Що робити , якщо тип з iє певний користувачем тип? Скажімо, його тип Indexвизначений далі у цій публікації (див. Нижче). Чи все одно це буде викликати невизначену поведінку?

Якщо так, чому? Хіба це не еквівалентно письму i.operator+=(i.operator++());чи навіть синтаксично простіше i.add(i.inc());? Або вони теж посилаються на невизначену поведінку?

Якщо ні, чому ні? Зрештою, об'єкт iзмінюється двічі між послідовними точками послідовності. Будь ласка, згадайте правило: вираз може змінювати значення об'єкта лише один раз між послідовними "точками послідовності . І якщо i += ++iце вираз, то він повинен викликати undefined-поведінку. Якщо так, то його еквіваленти i.operator+=(i.operator++());і i.add(i.inc());також повинен викликати undefined-поведінку, яка здається неправдою! (наскільки я розумію)

Або i += ++iце не вираз для початку? Якщо так, то що це таке і яке визначення виразу ?

Якщо це вираз, і в той же час його поведінка також чітко визначена, то це означає, що кількість точок послідовності, пов'язаних з виразом, якимось чином залежить від типу операндів, що беруть участь у виразі. Я правильно (навіть частково)?


До речі, як щодо цього виразу?

//Consider two cases:
//1. If a is an array of a built-in type
//2. If a is user-defined type which overloads the subscript operator!

a[++i] = i; //Taken from the previous topic. But here type of `i` is Index.

Ви також повинні це врахувати у своїй відповіді (якщо ви точно знаєте її поведінку). :-)


Є

++++++i;

чітко визначено в C ++ 03? Зрештою, це це,

((i.operator++()).operator++()).operator++();

class Index
{
    int state;

    public:
        Index(int s) : state(s) {}
        Index& operator++()
        {
            state++;
            return *this;
        }
        Index& operator+=(const Index & index)
        {
            state+= index.state;
            return *this;
        }
        operator int()
        {
            return state;
        }
        Index & add(const Index & index)
        {
            state += index.state;
            return *this;
        }
        Index & inc()
        {
            state++;
            return *this;
        }
};

13
+1 чудове запитання, яке надихнуло чудові відповіді. Я відчуваю, що маю сказати, що це все ще жахливий код, який слід реконструювати, щоб бути більш читабельним, але ви, мабуть, це все одно знаєте :)
Філіп Поттер,

4
@ Що таке питання: хто сказав, що це те саме? або хто сказав, що це не те саме? Чи це не залежить від того, як ви їх впроваджуєте? (Примітка: Я припускаю, що тип - sце визначений користувачем тип!)
Nawaz

5
Я не бачу, щоб будь-який скалярний об'єкт був змінений двічі між двома точками послідовності ...
Йоханнес Шауб - litb

3
@ Johanes: тоді мова йде про скалярний об'єкт. Що це? Цікаво, чому я ніколи раніше про це не чув. Можливо, тому, що підручники / C ++ - faq про це не згадують або не роблять акцент? Чи відрізняється він від об’єктів вбудованого типу?
Nawaz

3
@Phillip: Очевидно, я не збираюся писати такий код у реальному житті; насправді жоден розумний програміст не збирається його писати. Ці питання, як правило, розробляються для того, щоб ми могли краще зрозуміти весь бізнес невизначеної поведінки та точок послідовності! :-)
Наваз

Відповіді:


48

Це схоже на код

i.operator+=(i.operator ++());

Працює чудово щодо точок послідовності. Розділ 1.9.17 стандарту C ++ ISO говорить про точки послідовності та оцінку функцій:

При виклику функції (незалежно від того, функція вбудована), існує точка послідовності після оцінки всіх аргументів функції (якщо такі є), яка має місце перед виконанням будь-яких виразів або операторів у тілі функції. Також існує точка послідовності після копіювання поверненого значення та перед виконанням будь-яких виразів поза функцією.

Це означатиме, наприклад, що параметр i.operator ++()as operator +=має точку послідовності після його оцінки. Коротше кажучи, оскільки перевантажені оператори є функціями, застосовуються звичайні правила послідовності.

Чудове питання, до речі! Мені дуже подобається, як ти змушуєш мене зрозуміти всі нюанси мови, яку я вже думав, що знаю (і думав, що я думав, що знаю). :-)


12

http://www.eelis.net/C++/analogliterals.xhtml Мені спадають на думку аналогові літерали

  unsigned int c = ( o-----o
                     |     !
                     !     !
                     !     !
                     o-----o ).area;

  assert( c == (I-----I) * (I-------I) );

  assert( ( o-----o
            |     !
            !     !
            !     !
            !     !
            o-----o ).area == ( o---------o
                                |         !
                                !         !
                                o---------o ).area );

Виникло запитання, це ++++++ i; чітко визначено в C ++ 03?
Промисловий антидепресант

11

Як вже говорили інші, ваш i += ++iприклад працює з визначеним користувачем типом, оскільки ви викликаєте функції, а функції містять точки послідовності.

З іншого боку, a[++i] = iне так щастить, якщо припустити, що aце ваш базовий тип масиву або навіть визначений користувачем тип. Проблема у вас тут полягає в тому, що ми не знаємо, яка частина виразу, що містить i, обчислюється першою. Це може бути те, що ++iобчислюється, передається operator[](або необроблена версія), щоб отримати об'єкт там, а потім значення iотримує передається до цього (яке відбувається після збільшення i). З іншого боку, можливо, остання сторона оцінюється спочатку, зберігається для подальшого призначення, а потім ++iоцінюється частина.


отже ... чи результат, отже, не визначений, а не UB, оскільки порядок обчислення виразів не визначений?
Філіп Поттер

@Philip: unspecified означає, що ми очікуємо, що компілятор вкаже поведінку, тоді як undefined не передбачає такого зобов'язання. Я думаю, що це не визначено, щоб надати компіляторам більше місця для оптимізації.
Matthieu M.

@Noah: Я також розмістив відповідь. Будь ласка, перевірте це і повідомте мені про свої думки. :-)
Наваз

1
@Philip: результат - UB, через правило 5/4: "Вимоги цього пункту повинні виконуватися для кожного допустимого впорядкування підвиразів повного виразу; в іншому випадку поведінка не визначена.". Якби всі допустимі замовлення мали точки послідовності між модифікацією ++iта зчитуванням iна RHS завдання, тоді порядок був би невстановленим. Оскільки один із допустимих впорядкувань робить ці дві речі без втручальної точки послідовності, поведінка невизначена.
Steve Jessop

1
@Philip: Він не просто визначає невизначену поведінку як невизначену. Знову ж таки, якщо діапазон невизначеної поведінки включає таку, що невизначена, то загальна поведінка невизначена. Якщо діапазон невизначеної поведінки визначений у всіх можливостях, то загальна поведінка невстановлена. Але ви маєте рацію щодо другого пункту, я думав про визначену користувачем aі вбудовану i.
Steve Jessop

8

Я думаю, що це чітко визначено:

З проекту стандарту С ++ (n1905) §1.9 / 16:

"Існує також точка послідовності після копіювання поверненого значення та перед виконанням будь-яких виразів поза функцією13). Кілька контекстів у C ++ викликають оцінку виклику функції, хоча в одиниці перекладу не відображається відповідний синтаксис виклику функції. [ Приклад : оцінка нового виразу викликає одну або кілька функцій виділення та конструктора; див. 5.3.4. Для іншого прикладу, виклик функції перетворення (12.3.2) може виникнути в контекстах, в яких не відображається синтаксис виклику функції. - end приклад ] Точки послідовності при введенні функції та виведенні функції (як описано вище) є особливостями викликів функції, як обчислюється, незалежно від синтаксису виразущо викликає функцію. "

Зверніть увагу на частину, яку я виділив жирним шрифтом. Це означає, що насправді є точка послідовності після виклику функції збільшення ( i.operator ++()), але перед викликом складеного призначення ( i.operator+=).


6

Гаразд Переглянувши попередні відповіді, я знову задумався над власним запитанням, особливо над тією частиною, на яку намагався відповісти лише Ной, але я не впевнений у ньому повністю.

a[++i] = i;

Випадок 1:

If a- це масив вбудованого типу. Тоді те, що сказав Ной, є правильним. Це є,

a [++ i] = i не так щастить, якщо припустити, що a - це ваш основний тип масиву, або навіть визначений користувачем . Проблема у вас тут полягає в тому, що ми не знаємо, яка частина виразу, що містить i, обчислюється першою.

Отже, a[++i]=iвикликає невизначену поведінку, або результат невизначений. Що б це не було, воно не є чітко визначеним!

PS: У цитаті вище, закреслений це, звичайно, моє.

Випадок 2:

Якщо aє об'єктом визначеного користувачем типу, який перевантажує operator[], то знову є два випадки.

  1. Якщо тип повернення перевантаженої operator[]функції є вбудованим, то знову a[++i]=iвикликається undefined-поведінка або результат не вказаний.
  2. Але якщо тип повернення перевантаженої operator[]функції - це визначений користувачем тип, тоді поведінка користувача a[++i] = iє чітко визначеним (наскільки я розумію), оскільки в цьому випадку a[++i]=iце еквівалентно запису, a.operator[](++i).operator=(i);що є таким же, як a[++i].operator=(i);. Тобто присвоєння operator=викликається на поверненому об’єкті a[++i], який, здається, є дуже чітко визначеним, оскільки на час a[++i]повернення ++iвже було обчислено, а потім повернутий об’єкт викликає operator=функцію, передаючи оновлене значення iяк аргумент. Зверніть увагу, що між цими двома викликами є точка послідовності . А синтаксис гарантує відсутність конкуренції між цими двома викликами таoperator[]спочатку буде викликано, і послідовно аргумент, ++iпереданий в нього, також буде оцінений першим.

Подумайте про це як про те, someInstance.Fun(++k).Gun(10).Sun(k).Tun();що кожен послідовний виклик функції повертає об’єкт певного типу, визначеного користувачем. Для мене ця ситуація здається більш такою:, eat(++k);drink(10);sleep(k)оскільки в обох ситуаціях існує точка послідовності після кожного виклику функції.

Будь ласка, виправте мене, якщо я помиляюся. :-)


1
@Nawaz k++і kякі НЕ розділені крапками послідовності. Вони обидва можуть бути оцінені перед будь-яким Sunабо Funоцінюються. Мова вимагає лише того, що Funобчислюється раніше Sun, а не того Fun, що аргументи обчислюються раніше Sunаргументів. Я щось на кшталт пояснюю те саме, не маючи можливості надати посилання, тому ми не збираємося прогресувати звідси.
Філіп Поттер

1
@Nawaz: тому що немає нічого, що визначає точку послідовності, яка їх розділяє. Існують точки послідовності до і після Sunвиконання, але Funаргумент ++kможе бути оцінений до або після цього. Існують точки послідовності до і після Funвиконання, але Sunаргумент kможе бути оцінений до або після цього. Отже, один із можливих випадків полягає в тому, що обидва kі ++kобчислюються перед будь-яким Sunабо Funобчислюються, і тому обидва перебувають перед точками послідовності виклику функції, і тому немає точки поділу, що розділяє kі ++k.
Філіп Поттер

1
@Philip: Я повторюю: чим ця ситуація відрізняється від eat(i++);drink(10);sleep(i);? ... навіть зараз, можна сказати, i++може бути оцінено до або після цього?
Nawaz

1
@Nawaz: як я можу зробити себе більш чітким? У прикладі Fun / Sun не існує точки послідовності між kі ++k. У прикладі є / пити, є в якості точки послідовності між iі i++.
Філіп Поттер

3
@Philip: це взагалі не має сенсу. Між Fun () та Sun () існує точка послідовності, але між їх аргументом не існує точок послідовності. Це як сказати, між eat()і sleep()існує точка (и) послідовності, але між цими аргументами немає навіть однієї. Як можуть аргументи до двох викликів функцій, розділених точками послідовності, належати до одних і тих же точок послідовності?
Nawaz
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.