Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
ЗФ_ОАиП / Лекции ГГУ Скорины - Программирование.doc
Скачиваний:
204
Добавлен:
21.03.2016
Размер:
2.27 Mб
Скачать

11.2. Побочные эффекты при вычислении выражений

1) Операции присваивания, инкремента и декремента в сложных выражениях могут вызывать побочные эффекты, так как они не только возвращают значение, но еще и изменяют операнд. Эти побочные эффекты могут вызвать трудности из-за того, что получение значения и обновление переменной могут проходить не тогда, когда ожидается. Кроме этого, порядок вычисления операндов некоторых операций зависит от реализации (от компилятора).

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

j = 3;

i = (k = j + 1) + (j = 5);

Значение переменной i будет равно 9 или 11 в зависимости от того, какое выражение в скобках будет вычислено первым. Таким образом, с использованием разных компиляторов можно получить различные результаты. Кроме побочных эффектов, использование вложенных операторов присваивания усложняет чтение и восприятие программного кода.

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

m[i++] = m[i++] = 0;

Смысл выражения – записать 0 в следующие две позиции массива m. Однако в зависимости от того, когда будет обновляться i, позиция в m может быть пропущена, и в результате переменная i будет увеличена только на 1.

В BC, например, получим: i = 0, m = {1, 2, 3, 4, 5} => i = 2, m = {0, 2, 3, 4, 5}, а не ожидаемые: i = 2, m = {0, 0, 3, 4, 5}.

Надо разбить выражение на 2 выражения: m[i++] = 0; m[i++] = 0;

Даже если в выражении содержится только один инкремент, результат может быть неоднозначным:

m[i++] = i;

Если изначально i = 1, то элемент массива может принять значение 1 или 2.

2) Операнды логических выражений вычисляются слева направо. Если значения первого операнда достаточно, чтобы определить результат операции, то второй операнд не вычисляется. Это опять же зависит от компилятора. Например, в BC есть ускоренное вычисление логических выражений.

int a = 2, b = 3, c = 4, i = 0, j = 0;

if (a < i++ && b < j++)

c = 10;

Т.к. a=2 уже не меньше i=0, то i++ выполнится, а второе выражение даже не будет проверяться и вычисляться и после выполнения оператора: j=0, а не j=1.

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

prog(a, a = k * 2); // в зависимости от того, какой аргумент вычисляется первым, в функцию могут быть переданы различные значения

Ввод-вывод – еще один потенциальный источник возникновения закулисных действий.

В примере осуществляется попытка прочитать два взаимосвязанных числа из стандартного ввода:

scanf (“%d %d”, &n, &m[n]);

Выражение неверно, поскольку одна его часть изменяет n, а другая использует ее. Значение m[n] будет правильным только в том случае, если новое значение n будет таким же, как и старое. Проблема здесь не в порядке вычисления аргументов, а в том, что все аргументы scanf() вычисляются до того, как эта функция вызывается на выполнение, так что &m[n] всегда будет вычисляться с использованием старого значения n.

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

int a = 2;

printf("%d %d", a+=3, a+=5); // a = 10 на экране 10 и 7 (а не 5 и 10)

или

printf("%d %d", a+=3, a); // a = 5 на экране 5 и 2 (а не 5 и 5)

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

1. Не использовать операции присваивания переменной в выражении, если эта переменная используется в выражении более одного раза.

2. Не применять операции увеличения или уменьшения к переменной, которая входит в выражение более одного раза.

3. В логических выражениях не проводить никаких дополнительных вычислений.

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

5. Не применять операции увеличения или уменьшения к переменной, присутствующей в более чем одном аргументе функции.

6. Использовать естественную форму выражений. Записывать выражения в том виде, в котором вы произносили бы их вслух.

7. Разбивать сложные выражения. Язык С имеет богатый и разнообразный синтаксис, поэтому многие увлекаются втискиванием всего подряд в одну конструкцию.

x += (xp = (2*k < (n-m) ? c[k+1] : d[k --]) );

Лучше написать так:

xp = 2*k < (n-m) ? c[k+1] : d[k --];

x += xp;

или даже так:

if (2*k < n-m)

xp = c[k+1];

else

xp = d[k--];

x += xp;

8. И как вывод: необходимо быть проще. Это убережет и от ошибок, и упростит восприятие вашего программного кода.