2015-12-15 2 views
4

Если я хочу использовать инструкции препроцессора #define для упрощения определения и вычисления констант и общих функций и воспользоваться меньшим объемом ОЗУ (в отличие от использования значений const). Однако я не уверен, как они решаются, если многие макросы используются вместе.Как скопированы макрокоманды в C?

Я разрабатываю свою собственную обработку DateTime, похожую на временные метки linux, но для игры с обновлениями галочек, которые представляют собой 1/60 секунды. Я бы предпочел объявить ценности прикованными, но задаться вопросом, будет ли жестко закодированное значение работать быстрее.

#include <stdint.h> 

// my time type, measured in 1/60 of a second. 
typedef int64_t DateTime; 

// radix for pulling out display values 
#define TICKS_PER_SEC 60L 
#define SEC_PER_MIN 60L 
#define MIN_PER_HR  60L 
#define HRS_PER_DAY 24L 
#define DAYS_PER_WEEK 7L 
#define WEEKS_PER_YEAR 52L 

// defined using previous definitions (I like his style, write once!) 
#define TICKS_PER_MIN TICKS_PER_SEC * SEC_PER_MIN 
#define TICKS_PER_HR  TICKS_PER_SEC * SEC_PER_MIN * MIN_PER_HR 
#define TICKS_PER_DAY TICKS_PER_SEC * SEC_PER_MIN * MIN_PER_HR * HRS_PER_DAY 
// ... so on, up to years 

//hard coded conversion factors. 
#define TICKS_PER_MIN_H 3600L  // 60 seconds = 60^2 ticks 
#define TICKS_PER_HR_H  216000L // 60 minutes = 60^3 ticks 
#define TICKS_PER_DAY_H 5184000L // 24 hours = 60^3 * 24 ticks 

// an example macro to get the number of the day of the week 
#define sec(t)((t/TICKS_PER_DAY) % DAYS_PER_WEEK) 

Если я использую sec(t) макрос, который использует TICKS_PER_DAY, который определяется 3 предыдущих макросов TICKS_PER_SEC * SEC_PER_MIN * MIN_PER_HR * HRS_PER_DAY, делает это место повсюду в своем коде, который вызывает sec(t):

(t/5184000L) % 7L) 

или же она расшириться каждый время до:

(t/(60L * 60L * 60L * 24L)) % 7L) 

, так что на каждом этапе выполняются инструкции по дополнительному умножению ? Является ли это компромиссом между макросами и константными переменными, или я неправильно понимаю, как работает препроцессор?

UPDATE:

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

1. Правильного порядка операций:

(t/60 * 60 * 60 * 24) != (t/(60 * 60 * 60 * 24)) 

2. поощрять постоянный Фелди~d нг компилятора путем группирования вместе постоянных значений:

// note parentheses to prevent out-of-order operations 
#define TICKS_PER_MIN (TICKS_PER_SEC * SEC_PER_MIN) 
#define TICKS_PER_HR  (TICKS_PER_SEC * SEC_PER_MIN * MIN_PER_HR) 
#define TICKS_PER_DAY (TICKS_PER_SEC * SEC_PER_MIN * MIN_PER_HR * HRS_PER_DAY) 
+0

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

+0

Итак, если я использую один такой, как gcc, я должен ожидать, что он будет рассматриваться как одно значение? Есть ли способ проверить, что он делает? – AaronCarson

+1

Эти значения не являются * вложенными *; они являются * цепочками * расширений: расширения, которые используют макросы, расширения которых используют больше макросов. Вложенный, если он существует, означает, что тело макроса может определять макрос. – Kaz

ответ

1

См. gcc preprocessor macro docs, в частности Объектноподобные макросы.

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

(t/(60L * 60L * 60L * 24L)) % 7L)

Однако, это может быть, что компилятор (независимо от оптимизации?) Будет решить эту проблему, чтобы

(t/5184000L) % 7L)

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

Примечание1: вы должны использовать «(t)» в своем определении для защиты от непреднамеренных расширений/интерпретаций. Примечание2: еще одна рекомендация - избегать использования undef, поскольку это делает код менее читаемым. См. Примечания о том, как это влияет на расширение макроса (раздел Объектно-ориентированные макросы).

UPDATE: из раздела Object-like Macros:

Когда препроцессор расширяет имя макроса, расширение макроса заменяет вызов макроса, то разложение проверяется на более макросы для расширения. Например,

#define TABLESIZE BUFSIZE #define BUFSIZE 1024 TABLESIZE ==> BUFSIZE ==> 1024 расширяется размером таблица первой, чтобы произвести BUFSIZE, то, что макро расширяется для получения конечного результата, 1024.

Обратите внимание, что BUFSIZE не была определена, когда размер таблица была определена. «#define» для TABLESIZE использует именно указанное вами расширение - в данном случае BUFSIZE - и не проверяет, содержит ли он также макрокоманды. Только при использовании TABLESIZE это результат его расширения, отсканированного для большего количества имен макросов.

(курсив мой)

+0

Это просто вид документации, которую я искал, но не смог найти! Спасибо, я прочитаю это для получения дополнительных разъяснений. – AaronCarson

+0

Рад помочь! Я также разместил обновление, относящееся к ключевому разделу, который может вас заинтересовать. – tniles09

+0

Ваш ответ, кажется, наиболее подходит для моего конкретного вопроса, и для загрузки я не знал, что определения могут быть определены «не в порядке», так сказать, и но все же решаются правильно. Это заставляет меня чувствовать уверенность в том, что я использую прикованные макросы, чтобы сделать код читаемым, более легко поддерживаемым и правильно эффективным. – AaronCarson

2

Препроцессор просто делает замену текста. Он будет оценивать второе выражение с «дополнительными» умножениями. Компилятор обычно пытается оптимизировать арифметику между константами, однако, если это возможно, без изменения ответа.

Чтобы максимизировать свои возможности для оптимизации, вам нужно помнить, что вы сохраняете константы «рядом друг с другом», чтобы он мог видеть оптимизацию, особенно с типами с плавающей запятой. Другими словами, если t является переменной, вы хотели бы 30 * 20 * t вместо 30 * t * 20.

+0

Итак, используя мой пример, это означает, что в большинстве случаев «TICKS_PER_DAY» будет оптимизирован компилятором из '60L * 60L * 60L * 24L' на' 5184000L'? То есть Скорее всего, это приведет к изменению постоянных значений вместе, как вы продемонстрировали? – AaronCarson

+1

Почти. Вы должны поместить круглое выражение вокруг всего выражения в определение макроса, чтобы оно было '(60L * 60L * 60L * 24L)'. То, как у вас есть это 't/TICKS_PER_DAY', не даст ответ, который вы ожидаете из-за порядка операций над операциями умножения и деления. – Brick

+0

Кирпич, это имеет смысл. Я добавлю это к моему примеру. – AaronCarson

1

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

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

#define TICKS_PER_DAY TICKS_PER_SEC * SEC_PER_MIN * MIN_PER_HR * HRS_PER_DAY 

Теперь посмотрим на sec (обратите внимание, что точка с запятой не должны присутствовать, но я буду игнорировать, что на данный момент):

#define sec(t)((t/TICKS_PER_DAY) % DAYS_PER_WEEK); 

Если это конкретизируется в sec(x), она будет расширяться:

((x/60L * 60L * 60L * 24L) % 7L); 

Это явно не то, что было задумано. Он будет делить только на начальный 60L, после чего остальные значения будут умножены.

Правильный способ исправить это исправить определение TICKS_PER_DAY правильно инкапсулировать свои внутренние операции:

#define TICKS_PER_DAY (TICKS_PER_SEC * SEC_PER_MIN * MIN_PER_HR * HRS_PER_DAY) 

И, конечно же, sec должно быть выражение макрос и не должно содержать точку с запятой, которая бы предотвратить его использование, например, в контексте как sec(x) + 10:

#define sec(t) ((t/TICKS_PER_DAY) % DAYS_PER_WEEK) 

Теперь давайте посмотрим, как sec(x) будет расширена с этим исправлением ошибок:

((x/(60L * 60L * 60L * 24L)) % 7L) 

Теперь это фактически будет делать то, что предназначалось. Компилятор должен постоянно сбрасывать множители, откачиваясь в один разрыв, за которым следует один мод.

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

+0

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

+0

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

+0

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

1

Это расширяется:

(t/(60L * 60L * 60L * 24L)) % 7L) 

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

НО это не означает, что все вычисления будут повторяться в каждой точке, в которой вы используете sec (t). Это происходит потому, что вычисление происходит во время компиляции. Таким образом, вы не платите цену во время выполнения. Компилятор предварительно вычисляет такие постоянные вычисления и использует вычисленное значение в сгенерированном коде.

Смежные вопросы