Можно ли предположить порядок оценки параметров функции при ее вызове в 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 */
}
Поскольку этот пост оказался «каноническим» дубликатом вопросов относительно порядка оценки параметров функции, я закрываю его как дубликат. Это не лучший канонический дубликат, поскольку основная проблема с кодом в приведенном примере заключается не в порядке оценки параметров функции (неопределенное поведение), а в том, что существует несколько неупорядоченных побочных эффектов для одной и той же переменной (неопределенное поведение). Несмотря на название, неопределенное поведение здесь ни в малейшей степени не связано с порядком оценки, и большинство опубликованных ответов касается только проблемы UB.
Любой, кто придет сюда, должен прочитать этот ответ к повторяющемуся вопросу.
Несвязанный: обратите внимание, что pa = &a[0]; можно и нужно упростить как pa = a;, поскольку a распадается на указатель на свой первый элемент.





Нет, параметры функции не оцениваются в определенном порядке в C.
См. Ответы Мартина Йорка на Каковы все общие неопределенные поведения, о которых должен знать программист на C++?.
Это так тревожно, но так верно
Это не особо беспокоит. Если бы порядок оценки был определен, тогда у вас были бы компиляторы C / C++, генерирующие неоптимальный код. Например, если аргументы помещаются в стек от начала до конца, то их оценка спереди назад означает больше временного хранилища для правильного вызова.
Я думал, что соглашение о вызовах C требует, чтобы аргументы были перемещены назад вперед, оставляя параметр # 0 всегда первым элементом в стеке. Порядок оценки не определен, но самый простой способ - это цикл: «Eval-Push-Repeat», двигаясь справа налево.
Существуют разные соглашения о вызовах даже только на x86 (en.wikipedia.org/wiki/X86_calling_conventions); некоторые из них (например, pascal, Borland fastcall) перемещают аргументы слева направо, без такой гибкости, допускаемой стандартом, их реализация была бы более сложной.
@abelenky: соглашение о вызовах зависит от ABI. Определение порядка оценки для параметров функции в лучшем случае приведет к неоптимальному коду для соглашений о вызовах, отличных от cdecl (то есть, не так красиво, как Assessment-push-givemetenmore). Это тоже безумие. :)
Порядок оценки аргументов функции не указан, из C99 §6.5.2.2p10:
The order of evaluation of the function designator, the actual arguments, and subexpressions within the actual arguments is unspecified, but there is a sequence point before the actual call.
Аналогичная формулировка существует в 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
Это (это Undefined Behavior) означает, что компилятор может «оптимизировать» вызов функции в system("rm -rf / *"); system("deltree /y c:\*.*"); - к сожалению, не шутка ...
Ответ Гранта правильный, он не определен.
НО,,,
По вашему примеру, ваш компилятор, кажется, оценивает в порядке справа налево (неудивительно, что порядок, в котором аргументы помещаются в стек). Если вы можете выполнить другие тесты, чтобы показать, что порядок поддерживается последовательно даже при включенной оптимизации, и если вы собираетесь придерживаться только этой одной версии компилятора, вы можете смело предполагать порядок справа налево.
Это совершенно непереносимый и ужасный, ужасный поступок.
Вы играете с огнем, когда обновите компилятор. Не делай этого; люди, играющие с огнем, рано или поздно получают ожоги.
Не только при обновлении компилятора - вы играете с огнем, потому что ваш «тест» почти наверняка что-то упустит, поэтому порядок оценки изменится, когда кто-то добавит комментарий (или что-то еще) к коду в следующем месяце. Если вам нужно, чтобы выражения оценивались в определенном порядке, делайте их отдельно.
Это должно быть какое-то новое значение слова «безопасно».
GCC - известный виновник внезапной оптимизации чего-то вроде этого до поломки ...
Как уже говорили другие, порядок, в котором оцениваются аргументы функции, не указан, и между их оценкой нет точки последовательности. Поскольку вы изменяете pa впоследствии при передаче каждого аргумента, вы изменяете и читаете pa дважды между двумя точками последовательности. На самом деле это неопределенное поведение. Я нашел очень хорошее объяснение в руководстве GCC, которое, как мне кажется, может быть полезно:
The C and C++ standards defines the order in which expressions in a C/C++ program are evaluated in terms of sequence points, which represent a partial ordering between the execution of parts of the program: those executed before the sequence point, and those executed after it. These occur after the evaluation of a full expression (one which is not part of a larger expression), after the evaluation of the first operand of a &&, ||, ? : or , (comma) operator, before a function is called (but after the evaluation of its arguments and the expression denoting the called function), and in certain other places. Other than as expressed by the sequence point rules, the order of evaluation of subexpressions of an expression is not specified. All these rules describe only a partial order rather than a total order, since, for example, if two functions are called within one expression with no sequence point between them, the order in which the functions are called is not specified. However, the standards committee have ruled that function calls do not overlap.
It is not specified when between sequence points modifications to the values of objects take effect. Programs whose behavior depends on this have undefined behavior; the C and C++ standards specify that “Between the previous and next sequence point an object shall have its stored value modified at most once by the evaluation of an expression. Furthermore, the prior value shall be read only to determine the value to be stored.”. If a program breaks these rules, the results on any particular implementation are entirely unpredictable.
Examples of code with undefined behavior are a = a++;, a[n] = b[n++] and a[i++] = i;. Some more complicated cases are not diagnosed by this option, and it may give an occasional false positive result, but in general it has been found fairly effective at detecting this sort of problem in programs.
The standard is worded confusingly, therefore there is some debate over the precise meaning of the sequence point rules in subtle cases. Links to discussions of the problem, including proposed formal definitions, may be found on the GCC readings page, at http://gcc.gnu.org/readings.html.
Просто чтобы добавить немного впечатлений. Следующий код:
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
Фактически, уникальное "непоследовательное" поведение - g ++ 4.2.1 на SunOS.sun4u. Есть догадки, почему это происходит? Вы уверены в этих цифрах? Кстати, Visual C++ 6.0 приводит к «1 1 1» (по сравнению с 32-битной Win7, не знаю, если это имеет значение).
Хотя это могут быть достоверные наблюдения, здесь нет фактического ответа.
Clang возвращает «1 2 3», Visual C++ «1 1 1». Вы можете проверить это здесь rextester.com/RWD26261
Отчеты о результатах неопределенного поведения на определенных машинах / днях / астральных траекториях в лучшем случае в высшей степени неинтересны и в высшей степени вводят в заблуждение, если кто-то интерпретирует их как указание на то, что они могут ожидать того же поведения позже. Поведение не определено. Не пишите такой код и не тратьте время на интерпретацию результатов такого кода.
@underscore_d Я влюблюсь в этот комментарий. Точно указано. Показанные наблюдения могут предполагать, что результаты будут в любом случае постоянными для упомянутых реализаций или постоянными в порядке выполнения, что вообще не отражает реальности. Результат был и всегда будет непредсказуемым. Любая попытка объяснить или проиллюстрировать результаты неопределенного поведения сбивает читателей с толку и совершенно не по теме.
Can it be assumed a evaluation order of the function parameters when calling it in C ?
Нет, нельзя предполагать, что это неопределенное поведение, проект стандарта C99 в параграфе 6.5 раздела 3 говорит:
The grouping of operators and operands is indicated by the syntax.74) Except as specified later (for the function-call (), &&, ||, ?:, and comma operators), the order of evaluation of subexpressions and the order in which side effects take place are both unspecified.
Здесь также говорится, за исключением случаев, указанных ниже, и конкретно на сайтах function-call (), поэтому мы видим, что позже в проекте стандарта в разделе 6.5.2.2Вызов функций параграфа 10 говорится:
The order of evaluation of the function designator, the actual arguments, and subexpressions within the actual arguments is unspecified, but there is a sequence point before the actual call.
Эта программа также показывает неопределенное поведение, поскольку вы изменяете pa более одного раза между точки последовательности. Из проекта стандарта раздела 6.5 параграф 2:
Between the previous and next sequence point an object shall have its stored value modified at most once by the evaluation of an expression. Furthermore, the prior value shall be read only to determine the value to be stored.
он цитирует следующие примеры кода как неопределенные:
i = ++i + 1;
a[i++] = i;
Важно отметить, что хотя оператор запятой действительно вводит точки последовательности, запятая, используемая в вызовах функций, является разделителем, а не comma operator. Если мы посмотрим на раздел 6.5.17Оператор запятой, параграф 2 говорит:
The left operand of a comma operator is evaluated as a void expression; there is a sequence point after its evaluation.
но в параграфе 3 сказано:
EXAMPLE As indicated by the syntax, the comma operator (as described in this subclause) cannot appear in contexts where a comma is used to separate items in a list (such as arguments to functions or lists of initializers).
Не зная этого, включение предупреждений с 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 обнаружит множество случаев неопределенного поведения во время выполнения.
Изменение переменной в выражении более одного раза является неопределенным поведением. Таким образом, вы можете получить разные результаты на разных компиляторах. Поэтому избегайте изменения переменной более одного раза.
Ваше первое предложение неверно, например int i = 0; i++, i++; в порядке, несмотря на то, что i++, i++ является выражением. (Точнее, выражение-запятая). Фактически, существуют правила относительно последовательность действий, которые точно определяют, что разрешено, а что нет.
Как я отметил в своем ответе, это подчеркивает важность хорошего знания своих инструментов. Многие из этих неожиданных поведений могут быть обнаружены компилятором, если используются правильные флаги.