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

Это поведение undefined в C? Если не прогнозировать выход логически

Код 1

#include <stdio.h>
int f(int *a, int b) 
{
  b = b - 1;
  if(b == 0) return 1;
  else {
    *a = *a+1;

    return *a + f(a, b);
  }
}

int main() {
  int X = 5;
  printf("%d\n",f(&X, X));
}

Рассмотрим этот код. Вопрос здесь - предсказать выход. По логике, я получаю 31 как вывод. (Выход на машине)

Когда я меняю оператор return на

return f(a, b) + *a;

Я логически получаю 37. (Выход на машине)

Один из моих друзей сказал, что при вычислении выражения return в

return *a + f(a, b);

мы вычисляем значение a, идя глубина дерева, т.е. вызывается первый вычисленный тогда f(a, b), тогда как в

return f(a,b) + *a;

Он решается при возврате назад, т.е. f(a, b) вычисляется сначала, тогда вызывается *a.

При таком подходе я попытался предсказать вывод следующего кода:

Код 2

#include <stdio.h>
int foo(int n) 
{
    static int r;
    if(n <= 1)
        return 1;

    r = n + r;
    return r + foo(n - 2);
} 

int main () {
   printf("value : %d",foo(5));
}

Для return(r+foo(n-2));

введите описание изображения здесь

Im получает 14 как вывод логически (Выход на машине)

Для return(foo(n-2)+r);

введите здесь описание изображения

Я получаю 17 в качестве вывода. (Выход на машине)

Однако, когда я запускаю код в своей системе, я получаю 17 в обоих случаях.

Мои вопросы:

  • Правилен ли мой подход?
  • Если да, то почему я получаю тот же результат в Code 2, когда я запускаю на машине?
  • Если нет, то какой правильный способ интерпретировать Код 1 и Код 2?
  • Есть ли какое-либо поведение undefined, потому что C не поддерживает pass by Reference? Поскольку он используется в Code 1, он может быть реализован с помощью указателей?

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

4b9b3361

Ответ 1

Код 1

Для кода 1, поскольку порядок оценки членов в return *a + f(a, b); (и в return f(a, b) + *a;) не указан стандартом, а функция изменяет значение, которое a равно указывая на ваш код имеет неуказанное поведение и возможны различные ответы.

Как вы можете сказать из фурора в комментариях, термины "поведение w90", "неуказанное поведение" и т.д. имеют технические значения в стандарте C, а более ранние версии этого ответа неправильно использовали поведение undefined ', где он должен был использовать "неуказанный".

Название вопроса: "Является ли это поведение undefined в C?", а ответ "Нет: это неуказанное поведение, а не поведение undefined".

Код 2 - в пересмотренном виде

При фиксированном Code 2 функция также имеет неопределенное поведение: значение статической переменной r изменяется рекурсивным вызовом, поэтому изменения в порядке оценки могут изменить результат.

Код 2 - предварительная ревизия

Для Код 2, как первоначально показано в int f(static int n) { … }, код не компилируется (или, по крайней мере, не должен). Единственным классом хранения, разрешенным в определении аргумента функции, является register, поэтому наличие static должно давать вам ошибки компиляции.

ISO/IEC 9899: 2011 §6.7.6.3 Объявление функций (включая прототипы)¶2 Единственным спецификатором класса хранения, который должен быть указан в объявлении параметра, является register.

Компиляция с GCC 6.3.0 на macOS Sierra 10.12.2, как это (обратите внимание, никаких дополнительных предупреждений не требуется):

$ gcc -O ub17.c -o ub17
ub17.c:3:27: error: storage class specified for parameter ‘n’
 int foo(static int n)
                    ^

Нет; он вообще не компилируется, как показано - по крайней мере, не для меня, используя современную версию GCC.

Однако, если предположить, что это исправлено, функция также имеет неопределенное поведение undefined: значение статической переменной r изменяется рекурсивным вызовом, поэтому изменения в порядке оценки могут измените результат.

Ответ 2

C стандартное состояние, что

6.5.2.2/10 Вызов функций:

После оценок указателя функции и фактических аргументов есть точка последовательности, но до фактического вызова. Каждая оценка в вызывающей функции (включая другие вызовы функций), которая иначе не секретируется отдельно до или после выполнения тела вызываемой функции, неопределенно упорядочена 1 с учетом к выполнению вызываемой функции. 94)

И нога 86 (раздел 6.5/3) гласит:

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

В выражениях return f(a,b) + *a; и return *a + f(a,b); оценка подвыражения *a определяется неопределенно. В этом случае разные результаты можно увидеть для одной и той же программы. Обратите внимание, что побочный эффект на a секвенирован в приведенных выше выражениях, но он не указан в каком порядке.


1. Оценки A и B неопределенно секвенированы, когда A секвенируется либо до, либо после B, но не указано, что. (C11-5.1.2.3/3)

Ответ 3

Я сосредоточусь на определении первого примера.

Первый пример определяется неопределенным поведением. Это означает, что существует несколько возможных результатов, но поведение не undefined. (И если код может обрабатывать эти результаты, поведение определяется.)

Тривиальный пример неуказанного поведения:

int a = 0;
int c = a + a;

Не указано, выполняется ли сначала оценка слева или справа, так как они не подвержены влиянию. Оператор + не указывает точки последовательности 1. Есть два возможных упорядочения: сначала слева оценивается сначала, а затем справа или наоборот. Поскольку ни одна из сторон не модифицирована 2 поведение определяется.


Если бы слева или справа было изменено без точки последовательности, то есть без последствий, поведение было бы undefined 2:

int a = 0;
int c = ++a + a;


Если бы слева или справа была изменена точка последовательности между ними, то левая и правая стороны были бы неопределенно секвенированы 3. Это означает, что они секвенированы, но не определены, какие из них оцениваются первыми. Поведение будет определено. Имейте в виду, что оператор запятой вводит точку последовательности 4:

int a = 0;
int c = a + ((void)0,++a,0);

Существует два возможных порядка.

Если сначала оценивается левая сторона, тогда a оценивается до 0. Затем оценивается правая часть. Сначала оценивается первый (void) 0, за которым следует точка последовательности. Затем a увеличивается, а затем точка последовательности. Тогда 0 оценивается как 0 и добавляется в левую сторону. Результат равен 0.

Если сначала оценивается правая сторона, оценивается (void) 0, за которой следует точка последовательности. Затем a увеличивается, а затем точка последовательности. Тогда 0 оценивается как 0. Затем оценивается левая сторона, а оценка равна 1. Результат равен 1.


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

void Function( int* a)
{
    ++(*a);
    return 0;
}
int a = 0;
int c = a + Function( &a );
assert( c == 0 || c == 1 );

Существует два возможных порядка.

Если сначала оценивается левая сторона, a оценивается до 0. Затем оценивается правая сторона, появляется точка последовательности и вызывается функция. Затем a увеличивается, а затем другая точка последовательности, введенная в конце полного выражения 6 конец которого обозначается точкой с запятой. Затем возвращается 0 и добавляется к 0. Результат равен 0.

Если сначала оценивается правая сторона, то есть точка последовательности и функция вызывается. Затем a увеличивается, а затем другая точка последовательности, введенная в конце полного выражения. Затем возвращается 0. Затем оценивается левая сторона, а оценка равна 1 и добавляется к 0. Результат равен 1.


(Цитируется по: ISO/IEC 9899: 201x)

1 (6.5 Выражения 3)
Кроме указанных позже, побочные эффекты и вычисления значений подвыражений не имеют последствий.

2 (6.5 Выражения 2)
Если побочный эффект скалярного объекта не зависит от другого побочного эффекта на том же скалярном объекте или вычислении значения, используя значение одного и того же скаляра объект, поведение undefined.

3 (5.1.2.3 Выполнение программы)
 Оценки A и B неопределенно секвенированы, когда A секвенирован либо до, либо после B, но не указано, что.

4 (6.5.17 Comma operator 2)
Левый операнд оператора запятой оценивается как выражение void; Eсть точка последовательности между ее оценкой и правильной операндом.

5 (6.5.2.2 Функциональные вызовы 10)
После оценок указателя функции и фактической точки есть точка последовательности аргументы, но до фактического вызова.

6 (6.8. Выражения и блоки 4)
Существует точка последовательности между оценкой полного выражения и оценка следующего полного выражения для оценки.