Выражения константы C оцениваются во время компиляции или во время выполнения? - PullRequest
31 голосов
/ 12 января 2009

Если я напишу # define , который выполняет операцию с использованием других констант препроцессора, вычисляется ли окончательное значение каждый раз, когда макрос появляется во время выполнения? Зависит ли это от оптимизаций в компиляторе или подпадает под стандарт?

Пример:

#define EXTERNAL_CLOCK_FREQUENCY    32768
#define TIMER_1_S                   EXTERNAL_CLOCK_FREQUENCY
#define TIMER_100_MS                TIMERB_1_S / 10

Будет ли операция 32768/10 выполняться во время выполнения каждый раз, когда я использую макрос TIMER_100_MS?

Я бы хотел избежать следующего:

#define EXTERNAL_CLOCK_FREQUENCY    32768
#define TIMER_1_S                   EXTERNAL_CLOCK_FREQUENCY
#define TIMER_100_MS                3276

Краткое описание

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

Ответы [ 9 ]

32 голосов
/ 12 января 2009

Макросы - это просто текстовая подстановка, поэтому в вашем примере запись TIMER_100_MS в программе - необычный способ написания 32768 / 10.

Следовательно, вопрос в том, когда компилятор будет оценивать 32768 / 10, который является константным интегральным выражением. Я не думаю, что стандарт требует какого-то особого поведения здесь (поскольку оценка во время выполнения и во время компиляции не различима), но любой наполовину достойный компилятор оценит его во время компиляции.

26 голосов
/ 13 января 2009

Большинство ответов здесь сосредоточены на эффекте подстановки макросов. Но я думаю, что он хотел знать,

32768 / 10

оценивается во время компиляции. Прежде всего, это арифметическое константное выражение и, кроме того, интегральное константное выражение (потому что оно имеет только литералы целочисленного типа). Реализация может вычислять его во время выполнения, но также должна иметь возможность вычислять его во время компиляции, потому что

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

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

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

Редактировать : В примечании к комментарию, если макрос подставляется в выражение, подобное 3 * TIMER_100_MS, тогда это будет оценивать (3 * 32768) / 10. Следовательно, простой и прямой ответ: «Нет, это не будет происходить во время выполнения каждый раз, потому что деление может вообще не происходить из-за правил приоритета и ассоциативности» . В моем ответе выше предполагается, что макрос всегда подставляется так, что на самом деле происходит деление.

12 голосов
/ 12 января 2009

Я не знаю ни одного стандарта, который бы гарантировал его оптимизацию. Препроцессор заменит 32768/10 на TIMER_100_MS, что можно увидеть, выполнив gcc -c. Чтобы увидеть, продолжает ли оптимизация компилятор, запустите gcc -S и проверьте ассемблер. С gcc 4.1, даже без каких-либо флагов оптимизации, это уменьшается до константы во время компиляции:

#include <stdlib.h>
#include <stdio.h>

#define EXTERNAL_CLOCK_FREQUENCY    32768
#define TIMER_1_S                   EXTERNAL_CLOCK_FREQUENCY
#define TIMER_100_MS                TIMER_1_S / 10

int main(int argc, char **argv)
{
  printf("%d\n", TIMER_100_MS);

  return(0);
}

gcc -S test.c
cat test.s

...
    popl    %ebx
    movl    $3276, 4(%esp)
    leal    LC0-"L00000000001$pb"(%ebx), %eax
    movl    %eax, (%esp)
    call    L_printf$stub
...
11 голосов
/ 12 января 2009

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

Однако вы НЕ должны писать:

#define TIMER_100_MS      TIMERB_1_S / 10

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

#define TIMER_100_MS      (TIMERB_1_S / 10)

Рассмотрим:

i = 10 * TIMER_100_MS;

В первом случае было бы 32768 ((10 * TIMERB_1_S) / 10), во втором 32760 (10 * (TIMERB_1_S / 10)). Здесь нет разницы, но вы ДОЛЖНЫ знать об этом!

8 голосов
/ 13 января 2009

Ребята, это преобразование называется «постоянное сворачивание», и даже большинство студенческих компиляторов делают это. Если у вас есть компилятор, созданный кем-то другим, а не вами или вашим соседом по колледжу, и вы компилируете язык со статической типизацией, вы можете рассчитывать на него даже без включенной оптимизации. Другое дело, если вы имеете дело с каким-то дурацким динамическим языком, которому разрешено изменять значение /.

8 голосов
/ 12 января 2009

Из проекта комитета WG14 / N1124 - 6 мая 2005 г. ИСО / МЭК 9899: TC2 :

6.6 Постоянные выражения

Синтаксис

постоянное выражение:
условно-выражение

Описание

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

Ограничения

Постоянные выражения не должны содержать назначение, приращение, декремент, вызов функции или запятая операторы, кроме случаев, когда они содержится в подвыражении, которое не оценивается.96)

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

8 голосов
/ 12 января 2009

Будет ли операция 32768/10 происходить во время выполнения каждый раз, когда я использую макрос TIMERB_100_MS ?

В любом месте вашего кода, где вы используете TIMERB_100_MS, он будет заменен на 32768 / 10 препроцессором.

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

0 голосов
/ 24 июня 2016

Это неправильно, компиляторы не могут манипулировать числами с плавающей запятой во время компиляции. Если вы удовлетворены значением 3276 во время компиляции, тогда вы можете идти, но компилятор не сможет оценить это во время компиляции с точностью с плавающей запятой. Плавающие точки слишком сложны для оптимизации компиляторами, потому что оптимизация числа с плавающей точкой может привести к неожиданным результатам в математических выражениях, поэтому достойный компилятор (любая версия gcc, любая версия clang, любая версия msvc, любая версия icc) не упростит его 3276,8, конец истории.

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

Ознакомьтесь с дополнительными аспектами компиляции и оптимизации: http://www.agner.org/optimize/#manuals

0 голосов
/ 12 января 2009

Во время компиляции. Это стандарт языка (и так было всегда), который не зависит от компилятора.

Редактировать

Комментатор попросил ссылку - цитата из "Языка программирования C", 2-е издание, Приложение A12.3 (стр. 229):

Контрольная линия вида

#define identifier token-sequence 

вызывает замену препроцессора последующие экземпляры идентификатора с заданной последовательностью токенов; ведущие и замыкающие пробелы вокруг последовательность рокена отбрасывается

Конец редактирования

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