[Перевод] Сюрпризы в коде на Си

Характерной особенностью языка Си является возможность сделать что-либо несколькими различными способами, при этом все они будут эквивалентны. Например, если x является обычной переменной типа int, то каждое из следующих выражений будет выполнять одно и тоже действие:

x = x + 1;
x += 1;
x++;
++x;

В каждом случае переменная х будет увеличена на единицу. Единственно возможное различие в представленных выражениях связано с тем, что не очень хороший компилятор может сгенерировать немного лучший код для последних двух выражений (вообще это наводит на мысль о том, что пора бы воспользоваться компилятором получше).

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

y = x++; // переменной y присваивается значение x, до его инкремента
y = ++x; // переменной y присваивается значение х, после его инкремента

Интересно, что постфиксный инкремент немного «дороже», так как требует выделение памяти для сохранения старого значения переменной x. Однако компилятор, вероятнее всего, оптимизирует такое поведение. Иначе если память выделяется в случае, когда значение выражения не используется, то определенно требуется новый компилятор!

Если бы х был указателем на переменную типа int, то увеличение на 1 фактически означало бы увеличение на 4 (на 32-х битной машине). Если это большой сюрприз, то стоит освежить в памяти арифметические операции с указателями.

Иногда конструкции, кажущиеся эквивалентными, имеют очень тонкие различия…

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

alpha = 99;
beta = 99;
gamma = 99;

Конечно, приведенную выше конструкцию можно переписать более компактно:

alpha = beta = gamma = 99;

Они обе на 100 % эквивалентны. Или нет?

В большинстве случаев обе конструкции совершенно эквивалентны, но есть (по крайней мере) четыре ситуации при которых выбор той или иной конструкции имеет значение.

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

Во-вторых, писать поддерживаемый код – это всегда хорошо. Возможно в будущем, может потребоваться изменить код так, чтобы все три переменные не были установлены в одно и то же значение. Первая конструкция легче поддается модификации.

Третья причина связана с не очень хорошими компиляторами, которые могут генерировать следующий подобный код для первой конструкции:

mov r0, #99
mov alpha, r0
mov r0, #99
mov beta, r0
mov r0, #99
mov gamma, r0

Вторая конструкция намекает на то, что в r0 нужно загрузить значения только один раз. С другой стороны, отличному компилятору такие намеки не нужны.

Наконец, остается вопрос о порядке выполнения. В первой конструкции совершенно ясно, что alpha будет проинициализирована первой, а gamma – последней. Вторую конструкцию компилятор интерпретирует следующим образом:

alpha = (beta = (gamma = 99));

а это означает, что порядок инициализации будет обратным. Но важно ли это? В большинстве случаев нет. Однако если это регистры устройства, а не обычные переменные, то порядок присваивания может быть крайне важным. Поскольку при работе с «железом» очень часто требуется инициализировать значения таких регистров в строгом порядке.

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

Let’s block ads! (Why?)

Read More

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *