Порядок оценки параметров перед вызовом функции в 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 */
}
7 ответов
Нет, параметры функции не оцениваются в определенном порядке в C.
См. Ответы Мартина Йорка на " Что такое обычное неопределенное поведение, о котором должен знать программист с ++?,
Порядок вычисления аргументов функции не определен, из 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
Просто чтобы добавить немного опыта.
Следующий код:
int i=1;
printf("%d %d %d\n", i++, i++, i);
результаты в
2 1 3
- используя g++ 4.2.1 в Linux.i6861 2 3
- с помощью SunStudio C++ 5.9 в Linux.i6862 1 3
- используя g++ 4.2.1 на SunOS.x86pc1 2 3
- с помощью SunStudio C++ 5.9 на SunOS.x86pc1 2 3
- используя g++ 4.2.1 на SunOS.sun4u1 2 3
- используя SunStudio C++ 5.9 в SunOS.sun4u
Можно ли предположить порядок оценки параметров функции при вызове ее в C?
Нет, это не может быть предположено, если, это неопределенное поведение, проект стандарта C99 в разделе6.5
параграф 3
говорит:
Группировка операторов и операндов указывается синтаксисом.74) За исключением случаев, указанных далее (для операторов function-call (), &&, ||,?: И запятых), порядок вычисления подвыражений и порядок в какие побочные эффекты имеют место, оба не определены.
Это также говорит, кроме как указано позже и конкретно сайты 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
поймает много случаев неопределенного поведения во время выполнения.
Как уже говорили другие, порядок, в котором оцениваются аргументы функции, не определен, и между их оценкой нет последовательности. Потому что вы меняете 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.
Изменение переменной более одного раза в выражении является неопределенным поведением. Таким образом, вы можете получить разные результаты на разных компиляторах. Поэтому избегайте изменения переменной более одного раза.
Ответ Гранта правильный, он не определен.
НО,,,
В вашем примере ваш компилятор, кажется, оценивает в порядке справа налево (неудивительно, что порядок, в котором аргументы помещаются в стек). Если вы можете выполнить другие тесты, чтобы показать, что порядок поддерживается согласованно даже с включенной оптимизацией, и если вы собираетесь придерживаться только одной версии компилятора, вы можете смело предполагать упорядочение справа налево.
Это абсолютно непереносимо и ужасно, ужасно.