Арифметические операции между константами - PullRequest
6 голосов
/ 09 марта 2012

Рассмотрим этот код;

#define A 5
#define B 3

int difference = A - B;

действительно ли значение "разницы" жестко закодировано как "2" во время компиляции, или оно рассчитывается во время выполнения?

Ответы [ 2 ]

6 голосов
/ 09 марта 2012

Макросы A и B немного отвлекают. Это:

#define A 5
#define B 3

int difference = A - B;

в точности соответствует этому:

int difference = 5 - 3;

так что давайте обсудим последнее.

5 - 3 является константным выражением , которое является выражением, которое «может быть оценено во время перевода, а не во время выполнения, и, соответственно, может использоваться в любом месте, где может быть константа». Это также * целочисленное константное выражение ". Например, метка case должна быть целочисленным константным выражением, поэтому вы можете написать либо:

switch (foo) {
    case 2: /* this is a constant */
    ...
}

или это:

switch (foo) {
    case 5 - 3: /* this is a constant expression */
    ...
}

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

Но если предположить, что difference объявлено внутри некоторой функции, инициализатор не является одним из этих контекстов.

Любой компилятор стоит того, за что вы платите (даже если он бесплатный), уменьшит 5 - 3 до 2 во время компиляции и сгенерирует код, который хранит значение 2 в difference. Но это не обязательно. Стандарт C определяет поведение программ; это не определяет, как это поведение должно быть реализовано. Но можно предположить, что любой используемый компилятор заменит 5 - 3 на 2.

Даже если вы напишите:

int difference = 2;

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

Пока окончательный результат состоит в том, что difference имеет значение 2, языковой стандарт не заботится о том, как это делается.

С другой стороны, если вы напишите:

switch (foo) {
    case 5 - 3: /* ... */
    case 2:     /* ... */
}

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

Наконец, если вы определяете difference в области видимости файла (вне какой-либо функции), тогда начальное значение делает должно быть постоянным. Но реальное различие в этом случае не в том, будет ли 5 - 3 вычисляться во время компиляции, а в том, разрешено ли использовать непостоянное выражение.

Ссылка: последний вариант стандарта C 2011 года: N1570 (большой PDF); константные выражения обсуждаются в разделе 6.6.

5 голосов
/ 09 марта 2012

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

Почему бы не посмотреть на разборку вашего компилятора? Это даст вам окончательный ответ.

...

Итак, давайте сделаем это.

Вот вывод из VC ++ 10:

#include <iostream>

#define A 5
#define B 3

int main() {
    int x = A - B;
    std::cout << x;  // make sure the compiler doesn't toss it away
010A1000  mov         ecx,dword ptr [__imp_std::cout (10A2048h)]  
010A1006  push        2  
010A1008  call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (10A2044h)]  
    return 0;
010A100E  xor         eax,eax  

Как видите, он просто заменил вхождение x статическим значением 2 и поместил его в стек для вызова cout. Он не оценивал выражение во время выполнения.

...