C预处理程序在多大程度上考虑整数文字后缀?

时间:2019-05-21 18:17:59

标签: c++ c integer c-preprocessor suffix

今天,我偶然发现了这样的东西:

#define FOO 2u

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

无论代码为何原样(不用问why),我都想知道预处理器甚至可以处理整数文字后缀的程度。实际上,我感到惊讶的是它完全有效。 在使用此代码对GCC and 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. 所有指定/记录在哪里?

我想自己找出并签出 Wikipediathe C standard (working paper)。我找到了有关整数后缀的信息和有关预处理器的信息,但没有找到有关这些组合的信息。显然,我也用谷歌搜索,但没有得到任何有用的结果。

我已经看到this Stack Overflow question阐明了应该的指定位置,但是,我找不到我的问题的答案。

4 个答案:

答案 0 :(得分:6)

正如我在评论中指出的那样,这是在C标准中定义的。这是§6.10.1¶4的全文(和两个脚注):

  

C11 §6.10.1 Conditional inclusion   …

     

¶4在评估之前,将替换将成为控制常量表达式的预处理令牌列表中的宏调用(由defined一元运算符修改的宏名称除外),就像在普通文字。如果由于此替换过程而生成了定义的令牌,或者在宏替换之前使用defined一元运算符与两种指定格式之一都不匹配,则该行为未定义。在执行了由于宏扩展和defined一元运算符引起的所有替换之后,所有剩余的标识符(包括在词法上与关键字相同的标识符)都被替换为pp-number 0,然后将每个预处理令牌转换为令牌。生成的标记组成控制常量表达式,该表达式根据6.6的规则求值。出于此令牌转换和评估的目的,所有有符号整数类型和所有无符号整数类型的行为就像它们分别具有与头文件{{中定义的类型intmax_tuintmax_t相同的表示形式一样。 1}}。 167)包括解释字符常量,这可能涉及将转义序列转换为执行字符集成员。这些字符常量的数值是否与在表达式中(而不是<stdint.h>#if指令中)出现相同字符常量时获得的值相匹配而定义。 168)< / sup>而且,单字符字符常量是否可以具有负值是实现定义的。

     

167 167)因此,在#elifINT_MAX0x7FFFUINT_MAX的实现中,常数0xFFFF 0x8000表达式中的符号是正号,即使在翻译阶段7中它是无符号的。

     

168 因此,不能保证以下#if指令和#if语句中的常量表达式在这两个上下文中的值都相同。

if

第6.6节是§6.6 Constant expressions,其中详细介绍了§6.5 Expressions节中的完整表达式和常量表达式之间的区别。

实际上,预处理器在很大程度上忽略了后缀。十六进制常量是无符号的。显示的结果应该在#if 'z' - 'a' == 25 if ('z' - 'a' == 25) intmax_t是64位数量的计算机上可以预期。如果uintmax_tintmax_t的限制较大,则某些表达式可能会更改。

答案 1 :(得分:5)

C 2018 6.10.1处理条件包含(#if和相关语句以及defined运算符)。第1段说:

  

控制条件包含的表达式应为整数常量表达式,不同的是:标识符(包括与关键字在词法上相同的标识符)的解释如下;并且可能包含以下格式的一元运算符表达式

     

defined 标识符

     

     

defined ( 标识符 )

整数常量表达式在6.6 6中定义:

  

整数常量表达式应为整数类型,并且仅应具有整数常量,枚举常量,字符常量,结果为整数常量sizeof的{​​{1}}表达式}表达式,以及作为强制类型转换的立即数的浮点常量。整数常量表达式中的强制转换运算符只能将算术类型转换为整数类型,除非作为_Alignofsizeof运算符的操作数的一部分。

该段落通常用于C,而不仅仅是预处理器。因此,可以在_Alignof语句中出现的表达式与通常可以在C中出现的整数常量表达式相同。但是,如上面引文所述,#ifsizeof只是身份标识;它们不被视为C运算符。特别是6.10.1 4告诉我们:

  

…在执行了由于宏扩展和_Alignof一元运算符引起的所有替换之后,所有剩余的标识符(包括在词法上与关键字相同的标识符)都被替换为pp-number defined,…< / p>

因此,0sizeof出现在_Alignof表达式中时,它变成#if。因此,0表达式只能具有常量和#if表达式的操作数。

第4段继续说:

  

…结果标记生成控制常数表达式,该表达式根据6.6的规则求值。出于此令牌转换和评估的目的,所有有符号整数类型和所有无符号整数类型的行为就像它们分别具有与头文件{{中定义的类型definedintmax_t相同的表示形式一样。 1}}。…

6.6是常量表达式的部分。

因此,编译器将在uintmax_t表达式中接受整数后缀,并且该后缀不依赖于C实现(对于核心C语言所需的后缀;实现可以允许扩展)。但是,将使用<stdint.h>#if来执行所有算法,而这些算法确实取决于实现。如果您的表达式不依赖于大于最小所需 1 的整数的宽度,则在任何C实现中,它们的求值都应相同。

此外,第4段还说,字符常量和值可能会有一些变化,由于与该问题无关,我在这里省略了。

脚注

1 intmax_t指定一种能够表示任何带符号整数类型(7.20.1.5 1)的任何值的带符号类型,而uintmax_t是一个必须为至少64位(5.2.4.2.1 1),因此任何符合C的实现都必须在预处理器中提供64位整数运算。

答案 2 :(得分:3)

  
      
  1. 预处理器在多大程度上考虑整数文字后缀?还是只是忽略它们?
  2.   

整数常量的类型后缀对预处理器没有固有的意义,但它们是相应的预处理标记的固有部分,而不是单独的。该标准对他们说:

  

预处理编号以一个数字开头,可以选择在其后加一个   句点(。),后跟有效的标识符字符和   字符序列e +,e-,E +,E-,p +,p-,P +或P-。

     

预处理数字令牌的词法包括所有浮动和   整数常量标记。

({C11 6.4.8/2-3;已添加重点)

在大多数情况下,预处理器对这种类型的预处理令牌的处理方式与其他处理令牌没有什么不同。例外情况是#if指令的控制表达式,这些表达式通过执行宏扩展,将标识符替换为0,然后将每个预处理令牌转换为令牌进行评估,然后根据C规则。转换为令牌会考虑类型后缀,从而产生善意整数常量。

这不一定会产生与您在运行时对相同表达式求值所得到的结果相同的结果,因为

  

出于此令牌转换和评估的目的,所有已签名   整数类型和所有无符号整数类型的行为就像它们具有   分别与类型intmax_tuintmax_t相同。

C2011, 6.10.1/4

你继续问

  
      
  1. 在不同环境下是否存在任何依赖关系或不同行为,例如不同的编译器,C与C ++,32位与64位机器等?即,预处理程序的行为取决于什么?
  2.   

唯一的直接依赖项是intmax_tuintmax_t的实现定义。尽管这些可能存在关联,但它们与语言选择或机器体系结构并不直接相关。

  
      
  1. 所有指定/记录在哪里?
  2.   

当然,使用相应语言的语言规范。我引用了C11规范的两个更相关的部分,并将您链接到该标准的最新草案。 (当前的C是C18,但在所有这些方面都没有改变。)

答案 3 :(得分:0)

TLDR简化版本:

lll被有效地有效地(不是字面意思!)被预处理器条件所忽略(基本上,所有内容都被视为具有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 (!!!)