На каком этапе компиляции зарезервированы идентификаторы? - PullRequest
1 голос
/ 28 сентября 2010

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

  1. Некоторые классы идентификаторов зарезервированы для использования в реализации на C ++ и C.

  2. Компилятор должен выполнитьэтапы компиляции (предварительная обработка, компиляция, связывание), как если бы они были выполнены последовательно.

  3. Препроцессор C не знает о зарезервированном состоянии идентификаторов.

  4. Следовательно, программа может использовать зарезервированные идентификаторы тогда и только тогда, когда :

    1. Все используемые зарезервированные идентификаторы являются символами препроцессора.

    2. Результат предварительной обработки не включает зарезервированные идентификаторы.

    3. Идентификаторы не конфликтуют с символами, предопределенными компилятором (GNUC и др.).

Это действительно?Я не уверен в пунктах 3 и 4.3.Кроме того, есть ли способ проверить это?

Ответы [ 5 ]

5 голосов
/ 29 сентября 2010

(комментарии к вопросу объясняют, что мы говорим о зарезервированных идентификаторах в смысле раздела 7.1.3 C99, т. Е. Идентификаторах, совпадающим с /^_[A-Z_]/ в любом месте, /^_/ в области видимости файла, /^str[a-z]/ с внешней связью и т. Д. Итак, вот мое предположение, по крайней мере, часть того, что вы спрашиваете ...)

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

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

#ifndef _MYHEADER_H
#define _MYHEADER_H
// ...
#endif

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

Точно так же у меня часто есть внешне видимая функция с именем strip() или около того:

char *strip(char *s) {
  // remove leading whitespace
  }

При чтении 7.1.3, 7.26 и 7.26.11 в C99 это вызывает неопределенное поведение. Однако я решил не заботиться об этом. Идентификатор не зарезервирован в том смысле, что сегодня может произойти что-то плохое, но потому что Стандарт оставляет за собой право изобретать новую стандартную подпрограмму str-ip() в будущем пересмотре. И я решил, что string - ip, каким бы оно ни было, является маловероятным именем для строковой операции, которая будет добавлена ​​в будущем - поэтому в этом маловероятном случае я Я перейду этот мост, когда доберусь до него. Технически я использую неопределенное поведение, но не ожидаю, что его укусят.

Наконец, контрпример к вашей точке 4:

#include <string.h>
#define memcpy(d,s,n)  (my_crazy_function((n), (s)))
void foo(char *a, char *b) {
  memcpy(a, b, 5);  // intends to invoke my_crazy_function
  memmove(a, b, 5); // standard behaviour expected
}

Это соответствует вашим 4.1, 4.2, 4.3 (если я понимаю ваше намерение по этому последнему). Однако, если memmove дополнительно реализован как макрос (через 7.1.4 / 1), который написан в терминах memcpy, тогда у вас будут проблемы.

2 голосов
/ 29 сентября 2010

История сложнее, чем, я думаю, по крайней мере для тогда и только тогда, когда .То, что я помню из C99:

Например, 3. ложно, токен defined зарезервирован даже на этапе предварительной обработки, и псевдомакросы, такие как __LINE__, __func__ и т. Д., Также не могут быть переопределены.

Тогда резервирование идентификаторов зависит от области действия.

  • Некоторые идентификаторы явно зарезервированы для внешних символов, например, setjmp.
  • Идентификаторы, начинающиеся с подчеркивания, а затем еще одно подчеркивание или заглавная буква, зарезервированы везде в C. Вам следуетникогда не прикасайтесь к ним, даже с препроцессором.
  • Идентификаторы, начинающиеся с подчеркивания, а затем со строчной буквы, запрещены в области видимости файла, поскольку они могут ссылаться на внешние символы.Их можно свободно использовать в области действия.

4.2 также не совсем корректно.Во-первых, только неопределенное поведение (он же очень злой) иметь определенный макрос, который имеет ключевое слово в качестве имени при следующих условиях:

Стандартный заголовок включен, покамакрос определен с тем же именем, что и ключевое слово (7.1.2).

Тогда макрос, содержащий собственное имя в своем расширении, является "безопасным", поскольку расширение гарантировано не будетрекурсивный.Может быть допустимо что-то вроде следующего, хотя и не рекомендуется:

#define if(...)                                         \
for(int _i = 0; _i < 1; ++_i)                           \
  for(int _cond = (__VA_ARGS__);                        \
      _i < 1;                                           \
      printf("line %d val %d\n", __LINE__, _cond),      \
        ++_i)                                           \
    if(_cond)

(Кстати, никто не использует этот макрос, он компилирует и делает то, на что он похож, но имеет угловые случаи, которые позволяют ему взорваться.)

2 голосов
/ 29 сентября 2010

Препроцессор C не знает о зарезервированном статусе идентификаторов.

Я не уверен, что вы подразумеваете под "осведомленностью", но я не думаю, что вы обязательно можетепредположим, что - 7.1.3 говорит

Все идентификаторы, которые начинаются с подчеркивания либо в верхнем, либо в другом подчеркивании, всегда зарезервированы для любого использования

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

Я бы предположил, что «программа может использовать зарезервированные идентификаторы, если и толькоесли «стандарт» (например, набор предопределенных макросов) или реализация говорят об этом в своей документации.

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

1 голос
/ 29 сентября 2010

Идентификаторы, такие как _UNDERSCORE_CAP и double__underscore, зарезервированы для использования реализацией по своему усмотрению. Это не проблема, если реализация использует их, такие как, скажем, наличие _File идентификатора или макроса в <stdio.h>, для этого и используется резервирование. Это потенциальная проблема, если пользователь использует один.

Поэтому, чтобы диагностировать это, компилятор должен отслеживать, откуда пришли идентификаторы. Было бы недостаточно просто проверять код не в <angle_bracket_files.h>, поскольку они могут определять макросы, которые могут использоваться и могут расширяться до чего-то, используя слова, зарезервированные для реализации. Например, isupper может быть определено в <ctype.h> как

#define isupper(x) (_UPPER_BIT & _CHAR_TRAITS[x])

или что-то подобное. (Прошло много времени с тех пор, как я увидел определение, на котором основывалось выше.)

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

0 голосов
/ 28 сентября 2010

Если вы спрашиваете, можете ли вы #define if while и сделать ваш код нечитабельным, тогда да. Это было обычной практикой в ​​соревнованиях по запутыванию Си. Это на самом деле пошло бы против вашего 4.2, однако.

Для таких вещей, как GNUC, они предопределены, но вы обычно можете переопределить их и отменить их. Это не очень хорошая идея, но вы можете. Более интересно было бы переопределить или отменить определение __LINE__, __FILE__ и подобные символы препроцессора (поскольку они изменяются автоматически).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...