Стив Саммит. Язык си в вопросах и ответах. Выражения.
4. Выражения
5. ANSI C
7. Списки аргументов переменной длины.
8. Булевы выражения и переменные.
9. Структуры, перечисления и объединения.
10. Декларации.
13. Lint.
14. Стиль.
15. Операции с плавающей точкой.
16. Интерфейс с операционной системой.
17. Разное (Пребразование Fortran -> C , грамматики для YACC и т.п.)
Выражения
4.1: Почему вот такой код a[i] = i++; не работает?
О: Подвыражение i++ приводит к побочному эффекту — значение i изменяется, что приводит к неопределенности, если i уже встречается в том же выражении. (Обратите внимание на то, что хотя в книге K&R говорится, что поведение подобных выражений не описано, стандарт ANSI/ISO утверждает, что поведение не определено — см. вопрос 5.23.)
4.2: Пропустив код
int i = 7; printf(«%d\n», i++ * i++); через свой компилятор, я получил на выходе 49. А разве, независимо от порядка вычислений, результат не должен быть равен 56?
О: Хотя при использовании постфиксной формы операторов ++ и — увеличение и уменьшение выполняется после того как первоначальное значение использовано, тайный смысл слова «после» часто понимается неверно. _Не_ гарантируется, что увеличение или уменьшение будет выполнено немедленно после использования первоначального значения перед тем как будет вычислена любая другая часть выражения. Просто гарантируется, что измение будет произведено в какой-то момент до окончания вычисления (перед следующей «точкой последовательности» в терминах ANSI C). В приведенном примере компилятор умножил предыдущее значение само на себя и затем дважды увеличил i на 1. Поведение кода, содержащего многочисленные двусмысленные побочные эффекты неопределено (см. вопрос 5.23). Даже не пытайтесь выяснить, как Ваш компилятор все это делает (в противоположность неумным упражнениям во многих книгах по С); в K&R мудро сказано: «Да хранит Вас Ваша невинность, если Вы не знаете, как это делается на разных машинах»
4.3: Я экспериментировал с кодом
int i = 2; i = i++; Некоторые компиляторы выдавали i=2, некоторые 3, но один выдал 4. Я знаю, что поведение неопределено, но как можно получить 4?
О: Неопределенное (undefined) поведение означает, что может случиться _все_ что угодно. См. вопрос 5.23.
4.4 Люди твердят, что поведение неопределено, а я попробовал ANSI — компилятор и получил то, что ожидал.
О: Компилятор делает все, что ему заблагорассудится, когда встречается с неопределенным поведением (до некоторой степени это относится и к случаю зависимого от реализации и неописанного поведения). В частности, он может делать то, что Вы ожидаете. Неблагоразумно, однако, полагаться на это. См. также вопрос 5.18.
4.5: Могу я использовать круглые скобки, чтобы обеспечить нужный мне порядок вычислений? Если нет, то разве приоритет операторов не обеспечивает этого?
О: Круглые скобки, как и приоритет операторов обеспечивают лишь частичный порядок при вычислении выражений. Рассмотрим выражение
f() + g() * h() .
Хотя известно, что умножение будет выполнено раньше сложения, нельзя ничего сказать о том, какая из трех функций будет вызвана первой.
4.6 Тогда как насчет операторов &&, ||, и запятой ? Я имею в виду код типа if((c = getchar()) == EOF || c == ‘\n’)» …
О: Для этих операторов, как и для оператора ?: существует специальное исключение; каждый из них подразумевает определенный порядок вычислений, т.е. гарантируется вычисление слева-направо. В любой книге по С эти вопросы должны быть ясно изложены.
4.7 Если я не использую значение выражения, то как я должен увеличивать переменную i: так: ++i или так: i++ ?
О: Применение той или иной формы сказывается только на значении выражения, обе формы полностью эквивалентны, когда требуются только их побочные эффекты.
4.8 Почему неправильно работает код
int a = 1000, b = 1000; long int c = a * b; ?
О: Согласно общим правилам преобразования типов языка С, умножение выполняется с использованием целочисленной арифметики, и результат может привести к переполнению и/или усечен до того как будет присвоен стоящей слева переменной типа long int. Используйте явное приведение типов, чтобы включить арифметику длинных целых
long int c = (long int)a * b;
Заметьте, что код (long int)(a * b) _не_ приведет к желаемому результату.