Почему этот макрос C или С++ не расширяется препроцессором?
Может ли кто-то указать мне проблему в коде при компиляции с gcc 4.1.0.
#define X 10
int main()
{
double a = 1e-X;
return 0;
}
Я получаю ошибку: Exponent не имеет цифр.
Когда я заменяю X на 10, он отлично работает. Также я проверил с помощью команды g++ -E, чтобы увидеть файл с применяемыми препроцессорами, он не заменил X на 10.
У меня создалось впечатление, что препроцессор заменяет каждый макрос, определенный в файле заменяющим текстом, с применением любого интеллекта. Я не прав?
Я знаю, что это действительно глупый вопрос, но я смущен, и я предпочел бы быть глупым, чем путать:).
Любые комментарии/предложения?
Ответы
Ответ 1
Когда вы пишете 1e-X
все вместе, X
не является отдельным символом для замены препроцессора - с каждой стороны должны быть пробелы (или некоторые другие символы). Подумайте об этом немного, и вы поймете, почему..:)
Изменить: "12-X" действителен, поскольку он анализируется как "12", "-", "X", которые представляют собой три отдельных токена. "1e-X" не может быть разделено так, потому что "1е-" не образует действительного токена сам по себе, как упоминал Джонатан в своем ответе.
Что касается решения вашей проблемы, вы можете использовать token-concatenation:
#define E(X) 1e-##X
int main()
{
double a = E(10); // expands to 1e-10
return 0;
}
Ответ 2
Препроцессор не является текстовым процессором, он работает на уровне токенов. В вашем коде, после определения, каждое появление токена X
будет заменено токеном 10
. Однако в остальной части вашего кода отсутствует токен X
.
1e-X
является синтаксически недействительным и не может быть превращен в токен, что в основном то, что говорит вам ошибка (он говорит, что для того, чтобы сделать его действительным токеном - в данном случае литералом с плавающей запятой - вы должны укажите действительный показатель).
Ответ 3
Несколько человек сказали, что 1e-X
лексируется как один токен, что является частично правильным. Объяснить:
Во время трансляции есть два класса токенов: токены предварительной обработки и токены. Исходный файл сначала разбивается на токены предварительной обработки; эти жетоны затем используются во всех задачах предварительной обработки, включая замену макросов. После предварительной обработки каждый токен предварительной обработки преобразуется в токен; эти итоговые токены используются во время фактической компиляции.
Есть меньше типов токенов предварительной обработки, чем типы токенов. Например, ключевые слова (например, for
, while
, if
) не являются значимыми на этапах предварительной обработки, поэтому нет токена предварительной обработки ключевого слова. Ключевые слова просто лексируются как идентификаторы. Когда происходит преобразование токенов предварительной обработки в токены, каждый токен предварительной обработки идентификатора проверяется; если он соответствует ключевому слову, он преобразуется в токен ключевого слова; в противном случае он преобразуется в токен идентификатора.
Во время предварительной обработки существует только один тип числового токена: номер предварительной обработки. Этот токен для предварительной обработки соответствует двум различным типам токенов: целочисленный литерал и плавающий литерал.
Ток предварительной обработки номера предварительной обработки определяется очень широко. Эффективно он соответствует любой последовательности символов, начинающейся с цифры или десятичной запятой, за которой следует любое количество цифр, недигиты (например, буквы) и e+
и e-
. Итак, все следующие допустимые токены предварительной обработки для предварительной обработки:
1.0e-10
.78
42
1e-X
1helloworld
Первые два могут быть преобразованы в плавающие литералы; третий может быть преобразован в целочисленный литерал. Последние два не являются допустимыми целыми литералами или плавающими литералами; эти токены предварительной обработки не могут быть преобразованы в токены. Вот почему вы можете предварительно обработать источник без ошибок, но не можете его скомпилировать: ошибка возникает при преобразовании токенов предварительной обработки в токены.
Ответ 4
GCC 4.5.0 также не изменяет X.
Ответ будет заключаться в том, как препроцессор интерпретирует токены предварительной обработки - и в правиле "максимального munch". Правило "максимального мэша" означает, что "x +++++ y" рассматривается как "x ++ ++ + y" и, следовательно, является ошибочным, а не как "x ++ + ++ y", который является законны.
Проблема заключается в том, почему препроцессор интерпретирует '1e-X' как единственный токен предварительной обработки. Ясно, что он будет рассматривать "1е-10" как единый токен. Нет действительной интерпретации для '1e-', если за ней не будет цифра после прохождения препроцессора. Итак, я должен угадать, что препроцессор видит '1e-X' как единственный токен (фактически ошибочный). Но я не расчленял правильные предложения в стандарте, чтобы увидеть, где это требуется. Но определение "числа предварительной обработки" или "pp-number" в стандарте (см. Ниже) несколько отличается от определения действительной целочисленной или постоянной с плавающей запятой и позволяет многим "pp-номерам" недействительными в качестве целочисленная или постоянная с плавающей запятой.
Если это помогает, вывод компилятора Sun C для "cc -E -v soq.c":
# 1 "soq.c"
# 2
int main()
{
"soq.c", line 4: invalid input token: 1e-X
double a = 1e-X ;
return 0;
}
#ident "acomp: Sun C 5.9 SunOS_sparc Patch 124867-09 2008/11/25"
cc: acomp failed for soq.c
Итак, хотя бы один компилятор C отклоняет код в препроцессоре - возможно, препроцессор GCC немного слабеет (я попытался спровоцировать его на жалобу gcc -Wall -pedantic -std=c89 -Wextra -E soq.c
, но он не произнес писк). И использование 3 X как в макросе, так и в обозначении "1e-XXX" показало, что все три X были использованы как компилятором GCC, так и Sun C.
C Стандартное определение номера предварительной обработки
Из стандарта C - ISO/IEC 9899: 1999 §6.4.8 Номера предварительной обработки:
pp-number:
digit
. digit
pp-number digit
pp-number identifier-nondigit
pp-number e sign
pp-number E sign
pp-number p sign
pp-number P sign
pp-number .
Учитывая это, "1e-X" является действительным "pp-числом", и поэтому X не является отдельным токеном (а также не является "XXX" в "1e-XXX" отдельным токеном). Поэтому препроцессор не может расширять X; это не отдельный токен, подлежащий расширению.