Подтвердить что ты не робот

Являются ли константные выражения C оцененными во время компиляции или во время выполнения?

Если я пишу #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

Резюме

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

4b9b3361

Ответ 1

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

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

Ответ 2

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

32768 / 10

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

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

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

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

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

Ответ 3

Я не знаю ни одного стандарта, который гарантирует, что он будет оптимизирован. Препроцессор заменит 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
...

Ответ 4

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

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

#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)). Здесь нет особого различия, но вы ДОЛЖНЫ знать об этом!

Ответ 5

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

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

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

Ответ 6

Из Проект Комитета WG14/N1124 - 6 мая 2005 г. ISO/IEC 9899: TC2:

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

Синтаксис

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

Описание

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

Ограничения

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

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

Ответ 7

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

Ответ 8

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

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

Посмотрите здесь, чтобы узнать больше о компиляции и оптимизации: http://www.agner.org/optimize/#manuals

Ответ 9

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

Edit

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

Линия управления формы

#define identifier token-sequence 

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

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