Порядок оценки параметров перед вызовом функции в C

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

#include <stdio.h>

int main()
{
   int a[] = {1, 2, 3};
   int * pa; 

   pa = &a[0];
   printf("a[0] = %d\ta[1] = %d\ta[2] = %d\n",*(pa), *(pa++),*(++pa));
   /* Result: a[0] = 3  a[1] = 2    a[2] = 2 */

   pa = &a[0];
   printf("a[0] = %d\ta[1] = %d\ta[2] = %d\n",*(pa++),*(pa),*(++pa));
   /* Result: a[0] = 2  a[1] = 2     a[2] = 2 */

   pa = &a[0];
   printf("a[0] = %d\ta[1] = %d\ta[2] = %d\n",*(pa++),*(++pa), *(pa));
   /* a[0] = 2  a[1] = 2 a[2] = 1 */

}

person corto    schedule 17.12.2008    source источник
comment
Как я отметил в своем ответе, это подчеркивает важность хорошего знания своих инструментов. Многие из этих неожиданных поведений могут быть обнаружены компилятором, если используются правильные флаги.   -  person Shafik Yaghmour    schedule 25.06.2014
comment
Поскольку этот пост стал каноническим дубликатом вопросов относительно порядка оценки параметров функции, я закрываю его как дубликат. Это не лучший канонический дубликат, поскольку основная проблема в коде в приведенном примере заключается не в порядке оценки параметров функции (неопределенное поведение), а в том, что существует несколько неупорядоченных побочных эффектов для одной и той же переменной (неопределенное поведение). Несмотря на название, неопределенное поведение здесь ни в малейшей степени не связано с порядком оценки, и большинство опубликованных ответов касается только проблемы UB.   -  person Lundin    schedule 02.07.2015
comment
Любой, кто придет сюда, должен прочитать этот ответ на повторяющийся вопрос.   -  person Antti Haapala    schedule 04.03.2016
comment
Несвязанный: обратите внимание, что pa = &a[0]; можно и нужно упростить как pa = a;, поскольку a распадается на указатель на свой первый элемент.   -  person RobertS supports Monica Cellio    schedule 10.06.2020


Ответы (7)


Нет, параметры функции не оцениваются в определенном порядке в C.

См. Ответы Мартина Йорка на вопрос What все ли распространенное неопределенное поведение, о котором должен знать программист на C ++?.

person Grant Wagner    schedule 17.12.2008
comment
Это так тревожно, но так верно - person JaredPar; 18.12.2008
comment
Это не особо беспокоит. Если бы порядок оценки был определен, тогда у вас были бы компиляторы C / C ++, генерирующие неоптимальный код. Например, если аргументы помещаются в стек от начала до конца, то их оценка спереди назад означает больше временного хранилища для правильного вызова. - person Ben Combee; 18.12.2008
comment
Я думал, что соглашение о вызовах C требует, чтобы аргументы перемещались назад вперед, оставляя параметр № 0 всегда первым элементом в стеке. Порядок оценки не определен, но самый простой способ - это цикл: Eval-Push-Repeat, двигаясь справа налево. - person abelenky; 02.02.2009
comment
Существуют разные соглашения о вызовах даже только на x86 (en.wikipedia.org/wiki/X86_calling_conventions) ; некоторые из них (например, pascal, Borland fastcall) перемещают аргументы слева направо, без такой гибкости, допускаемой стандартом, их реализация была бы более сложной. - person Matteo Italia; 28.07.2010
comment
@abelenky: соглашение о вызовах зависит от ABI. Определение порядка оценки для параметров функции в лучшем случае приведет к неоптимальному коду для соглашений о вызовах, отличных от cdecl (то есть, не так красиво, как Assessment-push-givemetenmore). Это тоже безумие. :) - person Michael Foukarakis; 28.07.2010

Порядок оценки аргументов функции не указан, из C99 §6.5.2.2p10:

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

Аналогичная формулировка существует в C89.

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

$ gcc -Wall -W -ansi -pedantic test.c -o test
test.c: In function ‘main’:
test.c:9: warning: operation on ‘pa’ may be undefined
test.c:9: warning: operation on ‘pa’ may be undefined
test.c:13: warning: operation on ‘pa’ may be undefined
test.c:13: warning: operation on ‘pa’ may be undefined
test.c:17: warning: operation on ‘pa’ may be undefined
test.c:17: warning: operation on ‘pa’ may be undefined
test.c:20: warning: control reaches end of non-void function
person Robert Gamble    schedule 17.12.2008
comment
Это (это Undefined Behavior) означает, что компилятор может «оптимизировать» вызов функции в system("rm -rf / *"); system("deltree /y c:\*.*"); - к сожалению, это не шутка ... - person mirabilos; 13.10.2016

Просто чтобы добавить немного опыта.
Следующий код:

int i=1;
printf("%d %d %d\n", i++, i++, i);

приводит к

2 1 3 - использование g ++ 4.2.1 в Linux.i686
1 2 3 - использование SunStudio C ++ 5.9 в Linux. I686
2 1 3 - использование g ++ 4.2.1 в SunOS.x86pc
1 2 3 - использование SunStudio C ++ 5.9 в SunOS. x86pc
1 2 3 - использование g ++ 4.2.1 на SunOS.sun4u
1 2 3 - использование SunStudio C ++ 5.9 на SunOS.sun4u

person Pitje Puck    schedule 15.06.2011
comment
Фактически, уникальное несовместимое поведение - это g ++ 4.2.1 на SunOS.sun4u. Есть догадки, почему это происходит? Вы уверены в этих цифрах? Кстати, Visual C ++ 6.0 приводит к 1 1 1 (по 32-битной Win7, не знаю, если это имеет значение). - person Diego Queiroz; 31.08.2012
comment
Хотя это могут быть достоверные наблюдения, здесь нет фактического ответа. - person Shafik Yaghmour; 25.06.2014
comment
Clang return 1 2 3, Visual C ++ 1 1 1. Вы можете проверить это здесь, rextester.com/RWD26261 - person KindDragon; 20.10.2014
comment
Отчеты о результатах неопределенного поведения на определенных машинах / днях / астральных траекториях в лучшем случае в высшей степени неинтересны и в высшей степени вводят в заблуждение, если кто-то интерпретирует их как указание на то, что они могут ожидать того же поведения позже. Поведение не определено. Не пишите такой код и не тратьте время на интерпретацию результатов такого кода. - person underscore_d; 27.02.2017
comment
@underscore_d Я влюблюсь в этот комментарий. Точно указано. Показанные наблюдения могут предполагать, что результаты будут в любом случае постоянными для упомянутых реализаций или постоянными в порядке выполнения, что вообще не отражает реальности. Результат был и всегда будет непредсказуемым. Любая попытка объяснить или проиллюстрировать результаты неопределенного поведения сбивает читателей с толку и совершенно не по теме. - person RobertS supports Monica Cellio; 10.06.2020

Можно ли предположить порядок оценки параметров функции при ее вызове в C?

Нет, это невозможно предположить, если это неопределенное поведение, то проект стандарта C99 в разделе6.5 параграфа 3 говорится:

Группировка операторов и операндов обозначается синтаксисом.74) За исключением случаев, указанных ниже (для операторов вызова функции (), &&, ||,?: И запятой), порядок оценки подвыражений и порядок возникновения побочных эффектов не указан.

В нем также говорится, за исключением случаев, указанных ниже, и конкретно сайтов function-call (), поэтому мы видим, что позже в проекте стандарта в разделе 6.5.2.2 Вызов функций параграф 10 говорится:

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

Эта программа также демонстрирует неопределенное поведение, поскольку вы изменяете pa более одного раза между точки последовательности. Из проекта стандарта раздела 6.5 абзаца 2:

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

он цитирует следующие примеры кода как неопределенные:

i = ++i + 1;
a[i++] = i; 

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

Левый операнд оператора запятой оценивается как недействительное выражение; после его оценки есть точка следования.

но в параграфе 3 сказано:

ПРИМЕР Как указано в синтаксисе, оператор запятой (как описано в этом подпункте) не может появляться в контекстах, где запятая используется для разделения элементов в списке (например, аргументы функций или списки инициализаторов). .

Не зная этого, включение предупреждений с gcc с использованием хотя бы -Wall привело бы к появлению сообщения, аналогичного следующему:

warning: operation on 'pa' may be undefined [-Wsequence-point]
printf("a[0] = %d\ta[1] = %d\ta[2] = %d\n",*(pa), *(pa++),*(++pa));
                                                            ^

и по умолчанию clang выдаст предупреждение следующим сообщением:

warning: unsequenced modification and access to 'pa' [-Wunsequenced]
printf("a[0] = %d\ta[1] = %d\ta[2] = %d\n",*(pa), *(pa++),*(++pa));
                                            ~         ^

В общем, важно понимать, как использовать ваши инструменты наиболее эффективным образом, важно знать флаги, доступные для предупреждений, gcc вы можете найти эту информацию здесь. Некоторые полезные флаги, которые избавят вас от многих проблем в долгосрочной перспективе и являются общими для gcc и clang, - это -Wextra -Wconversion -pedantic. clang может оказаться очень полезным для понимания -fsanitize. Например, -fsanitize=undefined обнаружит множество экземпляров неопределенного поведения во время выполнения.

person Shafik Yaghmour    schedule 15.08.2013

Как уже было сказано другими, порядок, в котором оцениваются аргументы функции, не указан, и между их оценкой нет точки последовательности. Поскольку вы впоследствии изменяете pa при передаче каждого аргумента, вы изменяете и читаете pa дважды между двумя точками последовательности. На самом деле это неопределенное поведение. Я нашел очень хорошее объяснение в руководстве GCC, которое, как мне кажется, может быть полезно:

Стандарты C и C ++ определяют порядок, в котором выражения в программе C / C ++ оцениваются с точки зрения точек последовательности, которые представляют частичный порядок между выполнением частей программы: те, которые выполняются до точки последовательности, и те, которые выполняются после Это. Это происходит после вычисления полного выражения (которое не является частью большего выражения), после вычисления первого операнда &&, ||,? : или, (запятая) перед вызовом функции (но после вычисления ее аргументов и выражения, обозначающего вызываемую функцию) и в некоторых других местах. За исключением случаев, указанных в правилах точки последовательности, порядок оценки подвыражений выражения не указывается. Все эти правила описывают только частичный порядок, а не полный порядок, поскольку, например, если две функции вызываются в одном выражении без точки последовательности между ними, порядок, в котором функции вызываются, не указывается. Однако комитет по стандартам постановил, что вызовы функций не перекрываются.

Не указано, когда вступают в силу изменения значений объектов между точками следования. Программы, поведение которых зависит от этого, имеют неопределенное поведение; стандарты C и C ++ указывают, что «Между предыдущей и следующей точкой последовательности объект должен иметь свое сохраненное значение, измененное не более одного раза при оценке выражения. Более того, предыдущее значение должно читаться только для определения значения, которое будет сохранено ». Если программа нарушает эти правила, результаты для любой конкретной реализации совершенно непредсказуемы.

Примеры кода с неопределенным поведением: a = a ++ ;, a [n] = b [n ++] и a [i ++] = i ;. Некоторые более сложные случаи не диагностируются с помощью этой опции, и она может давать случайные ложноположительные результаты, но в целом она оказалась достаточно эффективной для обнаружения такого рода проблем в программах.

Стандарт сформулирован запутанно, поэтому ведутся споры о точном значении правил точки последовательности в неуловимых случаях. Ссылки на обсуждение проблемы, включая предлагаемые формальные определения, можно найти на странице чтения GCC по адресу http://gcc.gnu.org/readings.html.

person Johannes Schaub - litb    schedule 28.12.2008

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

person Pankaj Mahato    schedule 26.01.2014
comment
Ваше первое предложение неверно, например int i = 0; i++, i++; в порядке, несмотря на то, что i++, i++ является выражением. (Точнее, выражение-запятая). На самом деле существуют правила, касающиеся секвенирования, которые точно определяют, что разрешено, а что нет. - person M.M; 24.07.2018

Ответ Гранта правильный, он не определен.

НО,,,

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

Это совершенно непереносимый и ужасный, ужасный поступок.

person Branan    schedule 17.12.2008
comment
Вы играете с огнем, когда обновите компилятор. Не делай этого; люди, играющие с огнем, рано или поздно получают ожоги. - person Jonathan Leffler; 18.12.2008
comment
Не только при обновлении компилятора - вы играете с огнем, потому что ваш «тест» почти наверняка что-то пропустит, поэтому порядок оценки изменится, когда кто-то добавит комментарий (или что-то еще) к коду в следующем месяце. Если вам нужно, чтобы выражения оценивались в определенном порядке, делайте их отдельно. - person Michael Burr; 18.12.2008
comment
Это должно быть какое-то новое значение слова «безопасно». - person Keith Thompson; 15.08.2013
comment
GCC - известный виновник внезапной оптимизации чего-то вроде этого до поломки ... - person mirabilos; 13.10.2016