В какой степени препроцессор C рассматривает целочисленные литеральные суффиксы? - PullRequest
11 голосов
/ 21 мая 2019

Сегодня я наткнулся на что-то вроде этого:

#define FOO 2u

#if (FOO == 2)
  unsigned int foo = FOO;
#endif

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

#include <stdio.h>

int main()
{
  #if (1u == 1)
    printf("1u == 1\n");
  #endif

  #if (1u + 1l == 2ll)
    printf("1u + 1l == 2ll\n");
  #endif

  #if (1ull - 2u == -1)
    printf("1ull - 2u == -1\n");
  #endif

  #if (1u - 2u == 0xFFFFFFFFFFFFFFFF)
    printf("1u - 2u == 0xFFFFFFFFFFFFFFFF\n");
  #endif

  #if (-1 == 0xFFFFFFFFFFFFFFFF)
    printf("-1 == 0xFFFFFFFFFFFFFFFF\n");
  #endif

  #if (-1l == 0xFFFFFFFFFFFFFFFF)
    printf("-1l == 0xFFFFFFFFFFFFFFFF\n");
  #endif

  #if (-1ll == 0xFFFFFFFFFFFFFFFF)
    printf("-1ll == 0xFFFFFFFFFFFFFFFF\n");
  #endif
}

... который просто печатает все операторы:

1u == 1
1u + 1l == 2ll
1ull - 2u == -1
1u - 2u == 0xFFFFFFFFFFFFFFFF
-1 == 0xFFFFFFFFFFFFFFFF
-1l == 0xFFFFFFFFFFFFFFFF
-1ll == 0xFFFFFFFFFFFFFFFF

..Я думаю, что препроцессор просто полностью игнорирует целочисленные литеральные суффиксы и, вероятно, всегда выполняет арифметику и сравнение с собственным целочисленным размером, в данном случае 64-разрядным?

Итак, вот что я хотел бы знать:

  1. В какой степени препроцессор рассматривает целочисленные литеральные суффиксы?Или он просто игнорирует их?
  2. Существуют ли какие-либо зависимости или различия в поведении в разных средах, например, в разных компиляторах, C и C ++, 32-битная или 64-битная машина и т. Д.?То есть, от чего зависит поведение препроцессора?
  3. Где все это указано / задокументировано?

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

Я видел этот вопрос переполнения стека , который разъясняет, где его следует указать , нопока я не смог найти ответ на свои вопросы.

Ответы [ 4 ]

6 голосов
/ 21 мая 2019

Как я отметил в комментарии, это определено в стандарте C. Вот полный текст §6.10.1 №4 (и две сноски):

C11 §6.10.1 Условное включение ...

*4 До оценки, вызовы макросов в списке токенов предварительной обработки, которые станут выражением управляющей константы, заменяются (за исключением тех имен макросов, измененных унарным оператором defined), как в обычном тексте. Если определенный токен генерируется в результате этого процесса замены или использование унарного оператора defined не соответствует ни одной из двух указанных форм до замены макроса, поведение не определено. После выполнения всех замен из-за расширения макроса и унарного оператора defined все оставшиеся идентификаторы (включая лексически идентичные ключевым словам) заменяются на число pp 0, а затем каждый токен предварительной обработки преобразуется в токен. Полученные токены составляют выражение управляющей константы, которое оценивается по правилам 6,6 . Для целей этого преобразования и оценки токена все целочисленные типы со знаком и все целые типы без знака действуют так, как если бы они имели такое же представление, что и типы intmax_t и uintmax_t, определенные в заголовке <stdint.h>. 167) Это включает интерпретацию констант символов, которые могут включать преобразование escape-последовательностей в члены набора символов выполнения. То, соответствует ли числовое значение для этих символьных констант значению, полученному, когда в выражении встречается идентичная символьная константа (кроме директивы #if или #elif), определяется реализацией. 168) Кроме того, может ли односимвольная символьная константа иметь отрицательное значение, определяется реализацией.

167 167) Таким образом, в реализации, где INT_MAX равно 0x7FFF и UINT_MAX равно 0xFFFF, константа 0x8000 подписана и положительна в выражении #if даже хотя это будет неподписанным на этапе перевода 7.

168 Таким образом, константное выражение в следующей директиве #if и операторе if не гарантирует одинаковое значение в этих двух контекстах.

#if 'z' - 'a' == 25
if ('z' - 'a' == 25)

Раздел 6.6 - это §6.6 Выражения констант , которые детализируют различия между полными выражениями в разделе §6.5 Выражения и константами.

Фактически препроцессор в основном игнорирует суффиксы. Шестнадцатеричные константы без знака. Результаты, которые вы показываете, следует ожидать на машине, где intmax_t и uintmax_t являются 64-битными величинами. Если бы ограничения на intmax_t и uintmax_t были больше, некоторые выражения могли бы измениться.

5 голосов
/ 21 мая 2019

C 2018 6.10.1 имеет дело с условным включением (#if и связанные с ним операторы и оператор defined). Пункт 1 гласит:

Выражение, которое управляет условным включением, должно быть выражением целочисленной константы, за исключением того, что: идентификаторы (включая лексически идентичные ключевым словам) интерпретируются, как описано ниже; и может содержать выражения унарного оператора вида

defined идентификатор

или

defined ( идентификатор )

Целочисленное константное выражение определено в 6.6 6:

целочисленное константное выражение должно иметь целочисленный тип и иметь только операнды, которые являются целочисленными константами, константами перечисления, символьными константами, выражениями sizeof, результаты которых являются целочисленными константами, выражениями _Alignof и плавающими константы, которые являются непосредственными операндами приведений. Операторы приведения в выражении с целочисленной константой должны преобразовывать только арифметические типы в целочисленные типы, кроме как как часть операнда, к оператору sizeof или _Alignof.

Этот абзац для C обычно, а не только для препроцессора. Таким образом, выражения, которые могут появляться в операторах #if, совпадают с выражениями целочисленных констант, которые обычно появляются в C. Однако, как указано в приведенной выше цитате, sizeof и _Alignof являются просто идентификаторами; они не распознаются как операторы C. В частности, 6.10.1 4 говорит нам:

… После выполнения всех замен в связи с расширением макроса и унарным оператором defined все оставшиеся идентификаторы (включая лексически идентичные ключевым словам) заменяются на номер pp 0,…

Итак, где sizeof или _Alignof появляются в выражении #if, оно становится 0. Таким образом, выражение #if может иметь только операнды, являющиеся константами, и выражения defined.

Параграф 4 продолжает:

... Полученные токены составляют выражение управляющей константы, которое оценивается по правилам 6.6. Для целей этого преобразования и оценки токена все целочисленные типы со знаком и все целые типы без знака действуют так, как если бы они имели то же представление, что и типы intmax_t и uintmax_t, определенные в заголовке <stdint.h>.…

6.6 - раздел для константных выражений.

Таким образом, компилятор будет принимать целочисленные суффиксы в выражениях #if, и это не зависит от реализации C (для суффиксов, требуемых в основном языке C; реализации могут допускать расширения). Однако вся арифметика будет выполняться с использованием intmax_t или uintmax_t, и они зависят от реализации. Если ваши выражения не зависят от ширины целых чисел выше требуемого минимума 1 , они должны оцениваться одинаково в любой реализации C.

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

Сноска

1 intmax_t обозначает тип со знаком, способный представлять любое значение любого целочисленного типа со знаком (7.20.1.5 1), а long long int - это тип со знаком, который должен быть не менее 64 бит ( 5.2.4.2.1 1), поэтому любая соответствующая реализация C должна обеспечивать 64-битную целочисленную арифметику в препроцессоре.

3 голосов
/ 21 мая 2019
  1. В какой степени препроцессор рассматривает целочисленные литеральные суффиксы?Или он просто игнорирует их?

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

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

Числовые токены предварительной обработки лексически включают в себя все плавающие и целочисленные константы.

( C11 6.4.8 / 2-3 ; выделение добавлено)

В большинстве случаев препроцессор не обрабатывает токены предварительной обработки этого типа иначе, чем любые другие.Исключение составляют управляющие выражения директив #if, которые оцениваются путем выполнения раскрытия макроса, замены идентификаторов на 0 и затем преобразования каждого токена предварительной обработки в токен перед оценкой результата в соответствии с правилами C.Преобразование в учетные записи токенов для суффиксов типов приводит к истинным целочисленным константам.

Это не обязательно приводит к результатам, идентичным тем, которые вы получили бы при оценке времени выполнения тех же выражений, поскольку

В целях данного преобразования и оценки токена все целочисленные типы со знаком и все целочисленные типы без знака действуют так, как если бы они имели то же представление, что и типы intmax_t и uintmax_t соответственно.

( C2011, 6.10.1 / 4 )

Вы продолжаете спрашивать

Существуют ли зависимости или различия в поведении в разных средах, например, в разных компиляторах, C и C ++, 32-битная или 64-битная машина и т. Д.?То есть, от чего зависит поведение препроцессора?

Единственная прямая зависимость - это определения реализации intmax_t и uintmax_t.Они не связаны напрямую с выбором языка или архитектурой машины, хотя с ними может быть корреляция .

Где все это указано / задокументировано?

Разумеется, в языковых спецификациях соответствующих языков.Я привел два наиболее важных раздела спецификации C11 и связал вас с последним проектом этого стандарта.(Текущий C - C18, но он не изменился ни в одном из этих случаев.)

0 голосов
/ 22 мая 2019

TLDR, версия без ответа:

l и ll фактически (не буквально!) Игнорируются условными выражениями препроцессора (в основном все обрабатывается так, как если бы оно имело суффикс ll), однако u считается (как правило, для каждой целочисленной константы C)!

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

#include <stdio.h>

int main()
{
#if (1 - 2u > 0) // If one operand is unsigned, the result is unsigned.
                 // Usual implicit type conversion.
  printf("1 - 2u > 0\n");
#endif

#if (0 < 0xFFFFFFFFFFFFFFFF)
  printf("0 < 0xFFFFFFFFFFFFFFFF\n");
#endif

#if (-1 < 0)
  printf("-1 < 0\n");
#endif

#if (-1 < 0xFFFFFFFFFFFFFFFF)
  printf("-1 < 0xFFFFFFFFFFFFFFFF\n"); // nope
#elif (-1 > 0xFFFFFFFFFFFFFFFF)
  printf("-1 > 0xFFFFFFFFFFFFFFFF\n"); // nope, obviously
#endif

#if (-1 == 0xFFFFFFFFFFFFFFFF)
  printf("-1 == 0xFFFFFFFFFFFFFFFF (!!!)\n");
#endif
}

С этим выводом:

1 - 2u > 0
0 < 0xFFFFFFFFFFFFFFFF
-1 < 0
-1 == 0xFFFFFFFFFFFFFFFF (!!!)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...