Когда пробел (или скобки) требуются в C во время компиляции? - PullRequest
1 голос
/ 31 марта 2019

Я изучаю, как работает компиляция, и моя конечная цель - написать мини-C-компилятор.Я все еще в начале этого проекта.Работая над деталями сканера и парсера для построения AST, я понял, что в выражениях, подобных этим i+ +4, i+(+4), i- -4 или i-(-4), требуется пространство (или скобки).В противном случае в выражении i--4 (например) -- интерпретируется как унарный оператор --, и возникает ошибка.Я прекрасно понимаю причину.Это не вопрос.
Вопрос заключается в следующем. Раньше я наивно полагал, что пробелы не так важны в Си, хотя бы из-за проблем читаемости кода.Но теперь мне интересно, есть ли другие примеры, подобные тезисам, описанным выше?

Ответы [ 5 ]

2 голосов
/ 31 марта 2019

Пробелы действительно требуются во многих ситуациях:

  • Макроопределения:

    #define MACRO (a)      // defines a simple macro, it expands to (a)
    #define MACRO(a)       // defines a function-like macro with a single parameter a
    
  • Синтаксис комментария получил:

    a/*b    // starts a comment */
    a/ *b   // a divided by the value pointed to by b
    
  • Предварительная обработка числовых литералов:

    0x2e+1  <>  0x2e +1
    
  • Аналогичная проблема с триграфами:

    "??/??/????"  <>  "??" "/??" "/????"  // "??/??/????" is parsed as "\\????"   
    
  • Разделение токенов:

    a+ +b   <>  a++b   // a++b would be a syntax error
    a- -b   <>  a--b   // a--b would be a syntax error
    a& &b   <>  a&&b   // but &b is unlikely to be a valid operand for &
    
  • Проблема с вложенным шаблоном C ++:

    template a<b<c> >
    
2 голосов
/ 31 марта 2019

Мне пришлось исправить старый код и изменить

#define ALT_7      (0xfe+OFFSET)

на

#define ALT_7      (0xfe +OFFSET)

Причина в том, что 0xfe+OFFSET это номер предварительной обработки токен ине три токена, как можно наивно думатьСтарый компилятор проанализировал его как три, но новый потерпел неудачу, потому что он проанализировал его как тот, который не был допустимой числовой константой.

Вероятно, что-то еще на стороне препроцессора, но он более неясен (каквесь предмет предварительной обработки C / C ++).

1 голос
/ 31 марта 2019

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

C 2018 5.1.1.2 определяет фазы перевода (перефразирование и обобщение, а не точные кавычки):

  1. Многобайтовые символы физического исходного файла сопоставляются с исходным набором символов.Последовательности триграфа заменяются односимвольными представлениями.

  2. Строки, продолженные обратной косой чертой, объединяются.

  3. Исходный файл преобразуется из символов в предварительную обработкутокены и пробельные символы - каждая последовательность символов, которая может быть токеном предварительной обработки, преобразуется в токен предварительной обработки, и каждый комментарий становится одним пробелом.

  4. Предварительная обработка выполняется (выполняются директивыи макросы раскрываются).

  5. Исходные символы в константах символов и строковых литералах преобразуются в члены набора символов выполнения.

  6. Смежная строкаЛитералы объединяются.

  7. Пробелы отбрасываются.«Каждый токен предварительной обработки преобразуется в токен.Полученные токены синтаксически и семантически анализируются и переводятся как единица перевода ». (Этот цитируемый текст является основной частью компиляции C, как мы ее думаем!)

  8. Программа связанастать исполняемым файлом.

Прежде всего, когда в исходном коде C требуются пробелы, определяется фаза 3 - формирование токенов предварительной обработки.Это указано в C 2018 6.4.Грамматика для токенов предварительной обработки дана в параграфе 1 (подробнее об этом ниже), а параграф 4 говорит нам:

Если входной поток был проанализирован в токены предварительной обработки до заданного символа, следующийТокен предварительной обработки - это самая длинная последовательность символов, которая может составлять токен предварительной обработки.Из этого правила есть одно исключение: токены предварительной обработки имени заголовка распознаются только в директивах предварительной обработки #include и в определенных местах реализации в директивах #pragma.В таких контекстах последовательность символов, которые могут быть либо именем заголовка, либо строковым литералом, распознается как первый.

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

Затем в последующих подразделах 6.4 описывается, как выглядят эти токены.

Этап 3 вызывает два правила длявам нужен пробел, который по существу:

  • Если исходный код будет проанализирован, согласно приведенным выше правилам, как один токен предварительной обработки, где вам нужно два, то вы должны вставить пробел, где вы хотитеПервый токен заканчивается.
  • Если для ввода комментария используются / и *, отличные от /*, поместите между ними пробел.

Фаза 4 вызывает другуюправить.Поскольку 6.10.3 3 говорит: «В идентификаторе объекта-подобного макроса между идентификатором и списком замены должен быть пробел», вам нужен пробел, чтобы различать функциональный макрос:

#define foo(x) (3*(x)) // Macro that acts on argument x.
#define foo (x)        // Macro that expands to `(x)`.
0 голосов
/ 31 марта 2019

Компилятор C состоит из нескольких частей, вопрос в следующем: что вы реализуете?

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

Помимо этого, он также имеет концепцию одно- или двухсимвольных операторов и, похоже, жадно соответствует им.То есть - - превращается в MINUS_TOKEN, MINUS_TOKEN, тогда как -- независимо от того, где, всегда превращается в DECREMENT.

То есть, ваш пример i--4 дает ошибку синтаксического анализатора, так какесть посторонний 4, следующий за оператором постфикса-декремента.

Таким образом, это доказывает, что операторы сопоставляются жадно.Запись i - -4 OTOH работает, потому что жадное сопоставление видит пространство как конец для первого - токена и начинает новый, который затем дает второй минус.

В итоге, C сам игнорируетпробел после фазы токенизации препроцессор не делает.

0 голосов
/ 31 марта 2019

Лексеры в большинстве языков основаны на жадных регулярных выражениях - токен настолько длинен, насколько это возможно.

Если ++ можно интерпретировать как оператор ++ (слева направо), он не будет разделен на два плюса. Если inta можно интерпретировать как идентификатор, он не будет интерпретироваться как int с последующим a и т. Д.

i+ +4 нужен пробел, скобка или что-то между + и +, иначе лексер будет жадно использовать его слева направо как ++.

...