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

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

Пример:

#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

Резюме

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

обратите внимание, что TIMERB_1_S / 10 - это 3276, а не 3277

M.M 04.02.2016 03:08
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
32
1
17 646
9
Перейти к ответу Данный вопрос помечен как решенный

Ответы 9

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

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

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

A control line of the form

#define identifier token-sequence 

causes the preprocessor to replace subsequent instances of the identifier with the given sequence of tokens; leading and trailing whitespace around the roken sequence is discaded

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

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

Roddy 12.01.2009 22:36

@David: макрос будет РАСШИРЕН во время компиляции (как еще это могло быть), но нет гарантия, что выражение «TIMERB_1_S / 10» (то есть деление) не оценивается во время выполнения.

Roddy 12.01.2009 22:54

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

BCS 13.01.2009 01:49

Will the operation 32768 / 10 occur at runtime every time I use the TIMERB_100_MS macro?

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

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

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

Используя эту технику, вы можете показать, что даже сложные математические операции, такие как log(3.0), будут преобразованы во встроенные немедленные значения, хотя при разборке log(3.0) будет кодироваться как 4607626529066517259, что является 64-битным целочисленным преобразованием эквивалентного IEEE double ...

Mark Lakata 18.05.2016 02:55
Ответ принят как подходящий

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

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

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

dmckee --- ex-moderator kitten 12.01.2009 21:16

Даже если препроцессор сделает это, он не сможет этого сделать, пока он не определен таким образом: #define TIMER_100_MS (TIMERB_1_S / 10) Поскольку вы не указываете выражение в паре, было бы сложно вычислить его как 3277, где вы могли бы написать 1 / TIMER_100_MS ( Я полагаю, где результат в любом случае не был бы желаемым) Но если бы препроцессор уже оценил это выражение здесь, он даже нарушил бы порядок приоритета оператора.

dhein 12.02.2015 15:18

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

6.6 Constant expressions

Syntax

constant-expression:
    conditional-expression

Description

A constant expression can be evaluated during translation rather than runtime, and accordingly may be used in any place that a constant may be.

Constraints

Constant expressions shall not contain assignment, increment, decrement, function-call, or comma operators, except when they are contained within a subexpression that is not evaluated.96)

Each constant expression shall evaluate to a constant that is in the range of epresentable values for its type.

Спасибо за ссылку на ваш источник, это ценный ресурс.

Enrico 30.03.2016 22:26

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

Однако НЕ следует писать:

#define TIMER_100_MS      TIMERB_1_S / 10

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

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

Очень хороший момент, и это на самом деле критическая разница: вы можете захотеть сделать x% TIMER_100_MS, и без скобок это сначала возьмет мод x на TIMERB_1_S, а затем разделит результат на 10. Ранее была такая ошибка при реализации кольцевого буфера: )

Ferit Buyukkececi 02.02.2016 09:47

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

32768 / 10

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

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

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

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

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

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

Brick 14.02.2017 00:06

@Brick, но в этом случае это не «операция 32768/10». Однако вопрос касался только «операции 32768/10». Итак, мой ответ ничего не говорит о случае «3 * 32768/10», потому что это «(3 * 32768) / 10». Мой ответ верен по поводу «32768/10 * 3» и других случаях, когда есть такая операция.

Johannes Schaub - litb 14.02.2017 00:24

Это вопрос, скопированный напрямую: «Будет ли операция 32768/10 выполняться во время выполнения каждый раз, когда я использую макрос TIMER_100_MS?» Это определенно об использовании макроса, и макрос вычисляется без скобок. Вы не сможете узнать, упрощается это или нет, если не знаете все места, где используется макрос.

Brick 14.02.2017 00:26

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

Постоянное складывание! вот что я искал.

user2387149 05.10.2016 00:06

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

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

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

Результат целочисленного деления четко определен. Где в игру вступает математика с плавающей запятой?

Judge Maygarden 19.08.2016 18:49

Другие вопросы по теме