Почему этот макрос C или C ++ не раскрывается препроцессором? - PullRequest
31 голосов
/ 09 июня 2010

Может кто-нибудь укажет мне проблему в коде при компиляции с gcc 4.1.0.

#define X 10
int main()
{
  double a = 1e-X;
  return 0;
}

Я получаю ошибку: у экспонента нет цифр.

Когда я заменяю X на 10, все работает нормально. Также я проверил с помощью команды g ++ -E, чтобы увидеть файл с примененными препроцессорами, он не заменил X на 10. У меня сложилось впечатление, что препроцессор заменяет каждый макрос, определенный в файле, текстом замены с применением каких-либо сведений. Я не прав?

Я знаю, что это действительно глупый вопрос, но я в замешательстве, и я бы предпочел быть глупым, чем растерянным:).

Есть комментарии / предложения?

Ответы [ 4 ]

17 голосов
/ 09 июня 2010

Препроцессор не является текстовым процессором, он работает на уровне токенов.В вашем коде после определения каждый экземпляр токена X будет заменен токеном 10.Однако в остальной части кода нет токена X.

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

14 голосов
/ 09 июня 2010

Когда вы пишете 1e-X все вместе таким образом, X не является отдельным символом для замены препроцессором - должен быть пробел (или некоторые другие символы) с обеих сторон. Подумайте об этом немного, и вы поймете, почему ..:)

Редактировать: «12-X» действителен, потому что он анализируется как «12», «-», «X», которые являются тремя отдельными токенами. «1e-X» не может быть разделен таким образом, потому что «1e-» сам по себе не образует действительный токен, как упоминал Джонатан в своем ответе.

Что касается решения вашей проблемы, вы можете использовать токен-конкатенацию:

#define E(X) 1e-##X
int main()
{
  double a = E(10); // expands to 1e-10
  return 0;
}
9 голосов
/ 12 июня 2010

Несколько человек сказали, что 1e-X лексирован как один токен, что отчасти правильно. Объяснить:

Во время перевода существует два класса токенов: токены предварительной обработки и токены . Исходный файл изначально разлагается на токены предварительной обработки; Затем эти токены используются во всех задачах предварительной обработки, включая замену макросов. После предварительной обработки каждый токен предварительной обработки преобразуется в токен; эти полученные токены используются во время фактической компиляции.

Существует меньше типов токенов предварительной обработки, чем есть типы токенов. Например, ключевые слова (например, for, while, if) не имеют значения на этапах предварительной обработки, поэтому токен предварительной обработки ключевого слова отсутствует. Ключевые слова просто обозначаются как идентификаторы. Когда происходит преобразование токенов предварительной обработки в токены, проверяется каждый токен предварительной обработки идентификатора; если оно соответствует ключевому слову, оно конвертируется в маркер ключевого слова; в противном случае он конвертируется в токен идентификатора.

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

Маркер предварительной обработки номера предварительной обработки определен очень широко. Фактически он соответствует любой последовательности символов, начинающейся с цифры или десятичной точки, за которой следует любое количество цифр, недицифров (например, букв) и e+ и e-. Итак, все перечисленные ниже действительные токены предварительной обработки числа:

1.0e-10
.78
42
1e-X
1helloworld

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

6 голосов
/ 09 июня 2010

GCC 4.5.0 также не меняет X.

Ответ будет заключаться в том, как препроцессор интерпретирует токены препроцессора, и в правиле «максимального мунка». Правило «максимального мунка» - это то, что диктует, что «x +++++ y» трактуется как «x ++ ++ + y» и, следовательно, является ошибочным, а не «x ++ + ++ y», что законны.

Проблема в том, почему препроцессор интерпретирует '1e-X' как один токен предварительной обработки. Ясно, что он будет обрабатывать '1e-10' как один токен. Не существует действительного толкования для '1e-', если за ним не следует цифра после прохождения препроцессора. Итак, я должен предположить, что препроцессор видит '1e-X' как один токен (фактически ошибочный). Но я не анализировал правильные положения в стандарте, чтобы увидеть, где это требуется . Но определение «числа предварительной обработки» или «числа pp» в стандарте (см. Ниже) несколько отличается от определения допустимой целочисленной константы или константы с плавающей запятой и допускает множество «чисел 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; это не отдельный токен, подлежащий расширению.

...