Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

Неопределенное поведение в C

Anna | 24.06.2014 | нет комментариев

Достаточно трудной темой для программистов на С является undefined behavior. Даже опытные разработчики нередко не могут Отчетливо сформулировать поводы его появления. Статья призвана внести чуть огромнее ясности в данный вопрос.

Статья является ПЕРЕВОДОМ нескольких статей и выдержек из Эталона по данной теме.

Что такое «точки следования»?

Эталоне сказано:

Точки следования (sequence points)– такие точки в процессе выполнения программы, в которых все побочные результаты теснее исполненного кода завершили свое действие, а побочные результаты кода, подлежащего исполнению, еще не начали делать. (§1.9/7)

Побочные результаты? А что такое «побочные результаты»?

Второстепенный результат (side effect) (согласно Эталону) – итог доступа к volatile объекту, метаморфозы объекта, вызова функции из библиотеки I/O либо же вызова функции, включающей в себя какие-то из этих действий. Второстепенный результат является изменением состояния среды выполнения.

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

Скажем:

int x = y  ;   //  «y» тоже int

В дополнение к операции инициализации переменной «x» значение переменной «y» изменилось из-за побочного результата оператора .
Что ж, с этим ясно. Дальше к точкам следования. Альтернативное определение представления «точка следования» дано Стивом Саммитом (автор книг «Язык C в вопросах и результатах», блога «comp.lang.c»):
Точка следования – момент времени, когда «пыль улеглась», и все встреченные побочные результаты гарантированно закончены и остались позади.

Какие точки следования описаны в Эталоне C ?

В эталоне описаны следующие точки следования:

  • в конце вычисления полного выражения (§1.9/16). Под «полным выражением» (full-expression) подразумевается выражение, не являющееся подвыражением (subexpression) — частью иного выражения (прим: вычисление полного выражения может включать в себя вычисление подвыражения, лексически не являющегося его частью. Скажем, подвыражения, участвующие в вычислении довода по умолчанию, считаются частью выражения, которое вызвало функцию, а не выражения, определяющего данный довод).Скажем:
    int a = 5; // «;» - точка следования в данном контексте
  • в вычислении следующих выражений, а именно позже вычисления первого операнда:1. a && b (§5.14)
    2. a || b (§5.15)
    3. a? b: c (§5.16)
    4. a, b ($5.18)

    Вычисление этих выражений идет слева направо. Позже вычисления левого подвыражения все побочные результаты этого вычисления прекращают действие. Если позже вычисления левого подвыражения значение полного выражения вестимо, то правая часть не вычисляется. В последнем случае имеется в виду оператор запятая. В функции func(a, a ) запятая – не оператор, а легко разграничитель между доводами.

  • при вызове функции (неважно, является функция встроенной либо нет) позже вычисления всех ее доводов (если таковые имеются) и перед выполнением каких-либо инструкций в ее теле.
Что такое «неопределенное поведение»?

Эталон дает определение словосочетанию «неопределенное поведение» в §1.3.12:

Неопределенное поведение (undefined behavior)– поведение, которое может появляться в итоге применения ложных программных конструкций либо некорректных данных, на которые Интернациональный Эталон не налагает никаких требований. Неопределенное поведение также может появляться в обстановках, не описанных в Эталоне очевидно.

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

Какая связь между неопределенным поведением и точками следования?

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

Неуточняемое поведение (unspecified behavior) (согласно Эталону) – поведение, для которого Эталон предлагает два либо больше допустимых вариантов и не налагает Отчетливых требований на то, какой из них должен быть выбран в определенной обстановки.

Неуточняемое поведение появляется в итоге вычисления таких подвыражений, как:

  • доводы в вызове функции
  • операнды операторов (напр. , -, =, *, /), за исключением:
    1. операторов бинарной логики (&& и ||)
    2. оператора данные (?:)
    3. оператора запятой.

    (прим.: за исключением именно тех операторов, которые содержат точку следования)

Поведение, определяемое реализацией (implementation-defined behavior) (согласно Эталону) – поведение верно сформированной программной конструкции с положительными данными, которое зависит от реализации (должно быть документировано для всякой реализации).

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

Скажем:

int x = 5, y = 6;
int z = x     y  ; // не уточнено, будет вычислен первым x   либо y   

Еще один пример:


  int Hello()
  {
       return printf("Hello"); 
  }

  int World()
  {
       return printf("World !");
  }

  int main()
  {

      int a = Hello()   World(); /**может вывести «Hello World!» либо «World! Hello»
                      ^
                      | 
                Функции могут быть вызваны в любом порядке **/
      return 0;
  } 

В §5/4 Эталон говорит:

Между двумя точками следования скалярный объект должен менять хранимое значение при вычислении выражения не больше одного раза.

Что это значит?

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


i   *   i; // 
i =   i;   // 
  i = 2;   //    i изменено больше 1 раза
i =   i  1 // 
i = (i,  i,  i); // нет точки следования между правым `  i` и присвоением `i` (`i` изменяется больше 1 раза между 2мя точками следования)  

Но в то же время:


i = (i,   i, 1)   1; // определено
i = (  i,i  ,i)     //  определено
int j = i;
j = (  i, i  , j*i); // определено 

Помимо того (по Эталону) – ветхое значение выражения (до вычисления) должно быть доступно только для определения хранимого значения.
Это значит, что некорректными являются те выражения, в которых доступ к значению может предшествовать его модификации.

Скажем:


std::printf("%d %d", i,  i); // неведомо, что произойдет прежде – вычисление (  i) либо доступ к нему.

Еще один пример:


a[i] = i   ;   // либо a[  i] = i , либо  a[i  ] =   i  и т.д. 
Я слышал, что в C 0x нет никаких Точек Следования, это правда?

Да, это правда.
Представление «точка следования» было заменено комитетом ISO C на уточненное и дополненное представление Отношения Следования [ДОПОСЛЕ].

Что такое Отношение Следования[ДО]?

Следование ДО(Sequenced Before) это отношение, которое:

  • ассиметрично
  • транзитивно
  • появляется между парами вычислений и формирующее из них Отчасти упорядоченное уйма (partially ordered set)

Официально, это обозначает, что при 2-х данных выражениях А и B, если А [следует ДО] B, то выполнение А должно предшествовать выполнению В. Если же А не [следует ДО] В, тогда выполнение А и В является неупорядоченным (unsequenced) (выполнение неупорядоченных вычислений может пересекаться).
Вычисление A и В являются неопределенно упорядоченным (indeterminantly sequenced), когда либо А [следует ДО] В, либо В [следует ДО] А, но что именно – не уточнено. Неопределенно упорядоченные вычисления не могут пересекаться, но всякое из них может выполнятся первым.

Что обозначает слово «вычисление» в контексте C 0x ?

В С 0x вычисление (evaluation) выражения (либо подвыражения) в всеобщем случае включает в себя:

  • подсчет (computation) значения (включая определение расположения объекта в памяти для вычисления значения gvalue-выражения и приобретение значения по ссылке для вычисления prvalue-выражения)
  • инициирование побочных результатов

Эталон говорит нам (§1.9/14):

Всякий подсчет значения и второстепенный результат, связанные с полным выражением, [следуют ДО] подсчета значения и побочного результата, связанных со дальнейшим полным выражением, которое будет вычислено.

Банальный пример:

int x;  x = 10;    x; 

В данном примере подсчет значения и второстепенный результат, связанный с выражением ( x), [следует ПОСЛЕ] подсчета значения и побочного результата (x=10).

чай между вещами, описанными выше, и неопределенным поведением должна быть какая-то связь, да?

Безусловно, связь есть.

В §1.9/15 упоминается, что:

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

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

Скажем:


int main()
{
     int num = 19 ;
     num = (num << 3)   (num >> 3) ;
} 

1) Вычисление операндов оператора « » неупорядоченно.
2) Вычисление операндов операторов «<<» и «>>» неупорядоченно.

§1.9/15 подсчет значения операндов определенного оператора [следует ДО] подсчета значения итога работы оператора.

Это обозначает, что в выражении x y подсчет значений «х» и «у» [следует ДО] подсчета x y.

Сейчас к больше значимому:

§1.9/15 Если появление побочного результата скалярного объекта неупорядоченно по отношению к одному из дальнейший событий:

  • появлению иного побочного результата этого же объекта
  • подсчету значения с применением значения этого объект

то поведение программы будет НЕОПРЕДЕЛЕННЫМ.

Пример:


f(i  =  -1,  i  =  1);

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

Поток выполнения программы

Оперируя терминами, расшифрованными ранее, поток выполнения программы дозволено представить графически. В следующих дальше диаграммах обозначим вычисление выражения (либо подвыражения) как E(x), точку следования — %, второстепенный результат «k» для объекта «e» обозначим S(k,e). Если для вычисления нужно считать значение из именованного объекта (пускай «x» — имя), вычисление будем обозначать V(x), в остальных случаях

 

Источник: programmingmaster.ru

 

Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB