Условные обозначения для записи простых дополнений шестнадцатеричных и десятичных чисел

Несмотря на то, что старожил, я боюсь, что я больше не разбираюсь в синтаксическом анализе констант в C. Второй из следующих 1-лайнеров не компилируется:

int main( void ) { return (0xe +2); }
int main( void ) { return (0xe+2); }

$ gcc -s weird.c

weird.c: In function ‘main:
weird.c:1:28: error: invalid suffix "+2" on integer constant
int main( void ) { return (0xe+2); }
                           ^

Причина сбоя компиляции, вероятно, состоит в том, что 0xe + 2 анализируется как шестнадцатеричная константа с плавающей запятой в соответствии с стандартным предложением C11 6.4.4.2. Мой вопрос заключается в том, существует ли конвенция для написания простых добавлений шестнадцатеричных и десятичных чисел в C, я не люблю полагаться на пустое пространство при разборе.

Это было с версией gcc версии 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1 ~ 16.04.9). Прекращение компиляции после предварительной обработки (-E) показывает, что сбой компиляции происходит в gcc not cpp.

Ответ 1

Мой вопрос заключается в том, существует ли конвенция для написания простых дополнений шестнадцатеричных и десятичных чисел в C

Конвенция заключается в использовании пробелов. Это фактически предусмотрено C11 6.4 §3:

Знаки предварительной обработки могут быть разделены пробелом; это состоит из комментариев (описанных ниже) или символов пробела e- (пробел, горизонтальная вкладка, новая строка, вертикальная вкладка и форма-фид) или и то, и другое.

Там, где простое пространство является широко используемым.

Подобные экзотические проблемы существуют здесь и там на этом языке, некоторые примеры:

  • ---a необходимо переписать как - --a.
  • a+++++b необходимо переписать как a++ + ++b.
  • a///comment
    b;
    должны быть переписаны как
    a///comment
    b

И так далее. Виновным во всех этих случаях является маркерный парсер, который следует за так называемым "максимальным правилом munch", C11 6.4 §4:

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

В этом конкретном случае процессор pr e- не делает различий между константами с плавающей запятой и целыми константами, когда он создает токен pr e- с номером p p-, определенный в C11 6.4.8:

p p- номер e знак
p p- номер e знак
p p- число p знак
p p- номер P знак
номер p-.

Номер предварительной обработки начинается с цифры, которой необязательно предшествует период (.), За которым могут следовать действительные идентификационные символы и последовательности символов e+, e-, e+, e-, p+, p-, p+ или p-.

Здесь число p p-, по-видимому, не должно быть константой с плавающей точкой, что касается процессора pr e-.


(Как примечание, аналогичное соглашение существует также при завершении шестнадцатеричных escape-последовательностей внутри строк. Если я, например, хочу напечатать строку "ABBA" на новой строке, то я не могу написать

puts("\xD\xABBA"); (CR + LF + строка)

Поскольку строка в этом случае может быть интерпретирована как часть шестнадцатеричной escape-последовательности. Вместо этого я должен использовать пустое пространство для завершения escape-последовательности, а затем полагаться на конкатенацию строки процессора e-: puts("\xD\xA" "BBA"). Цель та же самая, чтобы направлять процессор pr e-, как разбирать код. )

Ответ 2

Потому что GCC считает, что 0xe+2 - число с плавающей запятой, а это всего лишь добавление двух целых чисел.

Согласно cppreference:

Из-за максимального munch, шестнадцатеричные целочисленные константы, заканчивающиеся на e и E, сопровождаемые операторами + или -, должны быть отделены от оператора пробелами или скобками в источнике:

int x = 0xE+2;   // error
int y = 0xa+2;   // OK
int z = 0xE +2;  // OK
int q = (0xE)+2; // OK