Запрос новичка о программе на C, стек вызовов функций, точка последовательности (секвенирование)
Приведенный ниже код отображает разные результаты при компиляции и запуске на Code::Blocks.
void sum(int a,int b){
printf("a=%d b=%d\n",a,b);
}
int main(){
int i=1;
sum(i=5,++i);
printf("i=%d\n\n",i);
/***********************/
i=2;
sum(i=5,i++);
printf("i=%d\n\n",i);
/**********************/
i=3;
sum(i=5,i);
printf("i=%d\n\n",i);
return 0;
}
Выход:
a=5 b=5
i=5
a=5 b=2
i=5
a=5 b=5
i=5
Я думаю, что ответ на этот вопрос связан с точкой последовательности, а точка последовательности здесь связана с оператором ++. GCC должен следовать порядку, чтобы передать значение в стек в фиксированном порядке, но из-за ++ ответы разные. Я думаю, что для начинающего написать вызов функции, как это, не очень часто, но урок об операторах является общим, так что можно попробовать.
Мои вопросы: каким должен быть точный ответ на него и подобные вопросы? На каком этапе компиляции эти вещи решаются (ясно или неясно)? Какой конкретный алгоритм (ы) (для оптимизации или вообще) задействован? Может ли один и тот же компилятор предоставлять разные результаты для таких выражений или операторов? И последнее - как новичок поймет и выяснит эти проблемы? Это иногда очень удивительно.
1 ответ
Порядок действий определяется во время нескольких этапов компиляции, что и приводит к странным результатам, которые вы видите. В частности, на этапе оптимизации компилятор может переупорядочивать код способами, которые не всегда очевидны, и в этом случае это влияет на результат (что хорошо, потому что вы делаете что-то неопределенное, а компилятору явно разрешено делать все, что он хочет с этим кодом). Никакого конкретного алгоритма здесь не существует, это взаимодействие нескольких разных алгоритмов, применяемых в разных точках, и алгоритм, применяемый в каждой точке, может варьироваться в зависимости от того, что компилятор решил, что это лучший способ обработать определенный фрагмент кода.
Когда документация говорит о неопределенном поведении, это не поведение определенного компилятора, которое не определено, а спецификация того, что компилятор должен или разрешено делать. Поведение компилятора полностью определено, но оно определяется детальными решениями, заложенными глубоко в дизайне его парсера, генератора кода и модулей оптимизатора, и это достаточно сложно, чтобы даже разработчики, написавшие компилятор, не могли сказать вам, что он будет делать без затрат много времени для анализа того, как данный бит кода протекает через весь процесс.
Новичок не сможет выяснить результат. Даже опытный разработчик не сможет. Вот почему "неопределенный" является таким нежелательным словом для разработчиков, и поэтому они пытаются избежать неопределенного поведения, такого как чума. Процитируем из обсуждения рассматриваемой языковой спецификации: "Короче говоря, вы не можете использовать sizeof() для структуры, элементы которой еще не определены, и если вы это сделаете, демоны могут вылететь из вашего носа".,