Сегодня я наткнулся на что-то вроде этого:
#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-битном?
Я хотел узнать сам и проверил Википедия и стандарт C (рабочий документ). Я нашел информацию о целочисленных суффиксах и информацию о препроцессоре, но ничего о их комбинации. Очевидно, я также гуглил, но не получил никаких полезных результатов.
Я видел этот вопрос Stack Overflow, в котором поясняется, где указывается должен, но пока не нашел ответа на свои вопросы.





C 2018 6.10.1 касается условного включения (#if и связанных операторов и оператора defined). В пункте 1 говорится:
The expression that controls conditional inclusion shall be an integer constant expression except that: identifiers (including those lexically identical to keywords) are interpreted as described below; and it may contain unary operator expressions of the form
definedidentifieror
defined(identifier)…
Целочисленное постоянное выражение определен в 6.6 6:
An integer constant expression shall have integer type and shall only have operands that are integer constants, enumeration constants, character constants,
sizeofexpressions whose results are integer constants,_Alignofexpressions, and floating constants that are the immediate operands of casts. Cast operators in an integer constant expression shall only convert arithmetic types to integer types, except as part of an operand to thesizeofor_Alignofoperator.
Этот параграф предназначен для C в целом, а не только для препроцессора. Таким образом, выражения, которые могут появляться в операторах #if, такие же, как целочисленные константные выражения, которые обычно могут появляться в C. Однако, как указано в приведенной выше цитате, sizeof и _Alignof — это просто идентификаторы; они не распознаются как операторы C. В частности, 6.10.1 4 говорит нам:
… After all replacements due to macro expansion and the
definedunary operator have been performed, all remaining identifiers (including those lexically identical to keywords) are replaced with the pp-number0,…
Итак, где sizeof или _Alignof появляются в выражении #if, оно становится 0. Таким образом, выражение #if может иметь только операнды, которые являются константами и выражениями defined.
Далее в абзаце 4 говорится:
… The resulting tokens compose the controlling constant expression which is evaluated according to the rules of 6.6. For the purposes of this token conversion and evaluation, all signed integer types and all unsigned integer types act as if they have the same representation as, respectively, the types
intmax_tanduintmax_tdefined in the header<stdint.h>.…
6.6 — это раздел для константных выражений.
Таким образом, компилятор будет принимать целочисленные суффиксы в выражениях #if, и это не зависит от реализации C (для суффиксов, необходимых в ядре языка C; реализации могут разрешать расширения). Однако вся арифметика будет выполняться с использованием intmax_t или uintmax_t, и это зависит от реализации. Если ваши выражения не зависят от ширины целых чисел выше минимально необходимой1, они должны оцениваться одинаково в любой реализации C.
Кроме того, в параграфе 4 говорится, что могут быть некоторые вариации с символьными константами и значениями, которые я опускаю здесь, поскольку они не имеют отношения к этому вопросу.
1intmax_t обозначает тип со знаком, способный представлять любое значение любого целочисленного типа со знаком (7.20.1.5 1), а long long int — тип со знаком, который должен быть не менее 64 бит (5.2.4.2.1 1), поэтому любая соответствующая реализация C должен обеспечивать 64-битную целочисленную арифметику в препроцессоре.
О Таким образом, выражения, которые могут появляться в операторах #if, совпадают с целочисленными константными выражениями, которые обычно могут появляться в C.: Думаю, не совсем. Сноска в главе «Константные выражения» читается как Дополнительные ограничения, которые применяются к целочисленным константным выражениям, используемым в директивах предварительной обработки условного включения, обсуждаются в 6.10.1., а затем в сноске к 6.10.1: Поскольку управляющее постоянное выражение оценивается на этапе трансляции 4, все идентификаторы либо являются, либо не являются именами макросов — просто отсутствуют ключевые слова, константы перечисления и т. д..
@Michi: Это обсуждается, начиная с предложения сразу после того, как вы остановили кавычку, начиная с «Однако» и заканчивая «Таким образом, выражение #if может иметь только операнды, которые являются константами и определенными выражениями».
Хорошо, теперь я понимаю, что вы имеете в виду. Я был сбит с толку, просто прочитав sizeof и _Alignof. Раньше я не понимал всего смысла. Во всяком случае, я думаю, что мы имеем в виду одно и то же.
Как я отметил в комментарии, это определено в стандарте C. Вот полный текст §6.10.1 ¶4 (и две сноски):
C11 §6.10.1 Conditional inclusion …
¶4 Prior to evaluation, macro invocations in the list of preprocessing tokens that will become the controlling constant expression are replaced (except for those macro names modified by the
definedunary operator), just as in normal text. If the token defined is generated as a result of this replacement process or use of thedefinedunary operator does not match one of the two specified forms prior to macro replacement, the behavior is undefined. After all replacements due to macro expansion and thedefinedunary operator have been performed, all remaining identifiers (including those lexically identical to keywords) are replaced with the pp-number 0, and then each preprocessing token is converted into a token. The resulting tokens compose the controlling constant expression which is evaluated according to the rules of 6.6. For the purposes of this token conversion and evaluation, all signed integer types and all unsigned integer types act as if they have the same representation as, respectively, the typesintmax_tanduintmax_tdefined in the header<stdint.h>.167) This includes interpreting character constants, which may involve converting escape sequences into execution character set members. Whether the numeric value for these character constants matches the value obtained when an identical character constant occurs in an expression (other than within a#ifor#elifdirective) is implementation-defined.168) Also, whether a single-character character constant may have a negative value is implementation-defined.167 167) Thus, on an implementation where
INT_MAXis0x7FFFandUINT_MAXis0xFFFF, the constant0x8000is signed and positive within a#ifexpression even though it would be unsigned in translation phase 7.168 Thus, the constant expression in the following
#ifdirective andifstatement is not guaranteed to evaluate to the same value in these two contexts.#if 'z' - 'a' == 25 if ('z' - 'a' == 25)
Раздел 6.6 — §6.6 Постоянные выражения, в котором подробно описаны различия между полными выражениями в разделе §6.5 Выражения и константными выражениями.
По сути, препроцессор в значительной степени игнорирует суффиксы. Шестнадцатеричные константы не имеют знака. Показанные вами результаты следует ожидать на машине, где intmax_t и uintmax_t — 64-битные величины. Если бы ограничения на intmax_t и uintmax_t были больше, некоторые выражения могли бы измениться.
Препроцессор не может игнорировать суффиксы. Это может игнорировать их эффекты ширины, но не подписанность.
- To which degree does the preprocessor regard integer literal suffixes? Or does it just ignore them?
Суффиксы типов целочисленных констант не имеют значения для препроцессора по своей сути, но они являются неотъемлемой частью соответствующих токенов предварительной обработки, а не отдельными. Стандарт говорит о них следующее:
A preprocessing number begins with a digit optionally preceded by a period (.) and may be followed by valid identifier characters and the character sequences e+, e-, E+, E-, p+, p-, P+, or P-.
Preprocessing number tokens lexically include all floating and integer constant tokens.
(С11 6.4.8/2-3; курсив добавлен)
По большей части препроцессор не обрабатывает токены предварительной обработки этого типа иначе, чем любые другие. Исключением являются управляющие выражения директив #if, которые оцениваются путем выполнения расширения макроса, замены идентификаторов на 0, а затем преобразование каждого токена предварительной обработки в токен перед оценкой результата по правилам C. Преобразование в токены учитывает суффиксы типов, что дает целочисленные константы добросовестный.
Однако это не обязательно дает результаты, идентичные тем, которые вы получили бы при вычислении тех же выражений во время выполнения, потому что
For the purposes of this token conversion and evaluation, all signed integer types and all unsigned integer types act as if they have the same representation as, respectively, the types
intmax_tanduintmax_t.
Вы продолжаете спрашивать
- Are there any dependencies or different behaviors with different environments, e.g. different compilers, C vs. C++, 32 bit vs. 64 bit machine, etc.? I.e., what does the preprocessor's behavior depend on?
Единственная прямая зависимость — это определения реализации intmax_t и uintmax_t. Они не связаны напрямую с выбором языка или архитектурой машины, хотя с ними может быть корреляции.
- Where is all that specified/documented?
Разумеется, в языковых спецификациях соответствующих языков. Я процитировал два наиболее важных раздела спецификации C11 и связал вас с последним проектом этого стандарта. (Текущий C — это C18, но он не изменился ни в одном из этих аспектов.)
Суффиксы целочисленных констант имеют значение для препроцессора, поскольку они влияют на подписание. -1u < 0 оценивается как false, но -1l < 0 оценивается как true.
Да, @EricPostpischil, они влияют на оценку управляющих выражений директив #if. Я, возможно, слишком тонко здесь разбиваю волосы, но хотя это имеет значение для C, я не считаю это значащим к препроцессору.
Очевидно, что значения -1u < 0 и -1l < 0 в утверждении #if различны, поскольку они приводят к разным результатам, и поэтому выбор утверждений для включения различается. И разница только в суффиксе. Следовательно, суффикс имеет значение для предварительной обработки.
Как я уже сказал, я разделяю волосы довольно тонко. В частности, я помещаю вычисление выражения за рамки собственно препроцессора, поскольку (1) оно работает с жетоны, а не выполняет предварительную обработку токенов, и (2) оно регулируется обычными правилами C для вычисления, как в отличие от специальных правил, относящихся к препроцессору. Это соответствует вопросу, который выражает удивление по поводу синтаксиса C для целочисленных констант, которые распознаются и обрабатываются. Да, потому что никакое другое не определено для этой цели.
Хотя первое чтение сбивает с толку, я понимаю точку зрения Джона. Красиво структурированный ответ!
Упрощенная версия 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 (!!!)
C11 §6.10.1 Условное включение ¶4: … Для целей этого преобразования и оценки токенов все типы целых чисел со знаком и все типы целых чисел без знака действуют так, как если бы они имели то же представление, что и типы
intmax_tиuintmax_t, определенные в заголовке<stdint.h>, соответственно. …