[英]Promotion when evaluating constant integer expressions in preprocessor directives - GCC
注意:请参阅下面的编辑。
原始问题:
遇到了一些我无法调和的奇怪行为:
#if -5 < 0
#warning Good, -5 is less than 0.
#else
#error BAD, -5 is NOT less than 0.
#endif
#if -(5u) < 0
#warning Good, -(5u) is less than 0.
#else
#error BAD, -(5u) is less than 0.
#endif
#if -5 < 0u
#warning Good, -5 is less than 0u.
#else
#error BAD, -5 is less than 0u.
#endif
编译时:
$ gcc -Wall -o pp_test.elf pp_test.c
pp_test.c:2:6: warning: #warning Good, -5 is less than 0.
pp_test.c:10:6: error: #error BAD, -(5u) is less than 0.
pp_test.c:13:9: **warning: the left operand of "<" changes sign when promoted**
pp_test.c:16:6: error: #error BAD, -5 is less than 0u.
这表明在评估常量整数表达式时,预处理器遵循不同的类型提升规则。 即,当运算符具有混合符号的操作数时,已签名的操作数将更改为无符号操作数。 相反的是(通常)在C中为真。
我在文献中找不到任何支持这一点的内容,但是我可能(可能?)我还不够彻底。 我错过了什么吗? 这种行为是否正确?
尽管如此,似乎#if或#elif指令中涉及显式无符号整数常量的任何条件表达式都可能无法按预期运行,即在C中。
编辑:根据我在Sourav Ghosh的回答中的评论,我的困惑最初源于表达式,其中包括用L
和LL
后缀指定的常量。 我原始问题中包含的示例代码太简单了。 这是一个更好的例子:
#if -5LL < 0L
#warning Good, -5LL is less than 0L.
#else
#error BAD, -5LL is NOT less than 0L.
#endif
#if -(5uLL) < 0L
#warning Good, -(5uLL) is less than 0L.
#else
#error BAD, -(5uLL) is less than 0L.
#endif
#if -5LL < 0uL
#warning Good, -5LL is less than 0uL.
#else
#error BAD, -5LL is less than 0uL.
#endif
建造:
$ gcc -Wall -o pp_test.elf pp_test.c
pp_test.c:2:6: warning: #warning Good, -5LL is less than 0L.
pp_test.c:10:6: error: #error BAD, -(5uLL) is less than 0L.
pp_test.c:13:9: warning: the left operand of "<" changes sign when promoted
pp_test.c:16:6: error: #error BAD, -5LL is less than 0uL.
这似乎违反了Sourav Ghosh发布的第6.3.1.8段中的条款(我的重点):
否则,如果用符号整型操作数的类型,可以表示所有与无符号整数类型的操作数的类型的值的,然后用无符号整数类型的操作数被转换成符号整型操作数的类型 。
它似乎违反了这个条款,因为-5LL
的排名高于0uL
,并且因为第一个(带signed long long
)的类型确实可以代表第二个类型( unsigned long
)的所有值。 问题是,预处理器不知道这一点。
如https://gcc.gnu.org/onlinedocs/gcc-3.0.2/cpp_4.html (我的重点)所述:
预处理器计算表达式的值。 它以编译器已知的最宽整数类型执行所有计算 ; 在GCC支持的大多数机器上,这是64位。 这与编译器用于计算常量表达式的值的规则不同,并且在某些情况下可能会给出不同的结果 。 如果值变为非零,则`#if'成功并包含受控文本; 否则会被跳过。
“ 执行编译器已知的最宽整数类型中的所有计算 ”似乎暗示的是,操作数本身被视为被指定为相同的“最宽”类型。 换句话说, -5
和-5L
被视为-5LL
, 0u
和0uL
被视为0uLL
。 这激活了Sourav Ghosh引用的条款,并导致观察到的行为。
实际上,就预处理器而言,只有一个等级,因此忽略依赖于具有不同等级的操作数的类型提升规则。 这与编译器如何评估表达式确实不同吗?
编辑#2:这是一个真实世界的例子,说明预处理器如何以不同的方式评估相同的表达式(由Optiboot提取)。
#ifndef BAUD_RATE
#if F_CPU >= 8000000L
#define BAUD_RATE 115200L
#elif F_CPU >= 1000000L
#define BAUD_RATE 9600L
#elif F_CPU >= 128000L
#define BAUD_RATE 4800L
#else
#define BAUD_RATE 1200L
#endif
#endif
#ifndef UART
#define UART 0
#endif
#define BAUD_SETTING (( (F_CPU + BAUD_RATE * 4L) / ((BAUD_RATE * 8L))) - 1 )
#define BAUD_ACTUAL (F_CPU/(8 * ((BAUD_SETTING)+1)))
#define BAUD_ERROR (( 100*(BAUD_ACTUAL - BAUD_RATE) ) / BAUD_RATE)
#if BAUD_ERROR >= 5
#error BAUD_RATE error greater than 5%
#elif (BAUD_ERROR + 5) <= 0
#error BAUD_RATE error greater than -5%
#elif BAUD_ERROR >= 2
#warning BAUD_RATE error greater than 2%
#elif (BAUD_ERROR + 2) <= 0
#warning BAUD_RATE error greater than -2%
#endif
volatile long long int baud_setting = BAUD_SETTING;
volatile long long int baud_actual = BAUD_ACTUAL;
volatile long long int baud_error = BAUD_ERROR;
void foo(void) {
baud_setting = BAUD_SETTING;
baud_actual = BAUD_ACTUAL;
baud_error = BAUD_ERROR;
}
为AVR目标构建:
$ avr-gcc -Wall -c -g -save-temps -o optiboot_pp_test.elf -DF_CPU=8000000L optiboot_pp_test.c
注意F_CPU
如何被指定为有符号常量。
optiboot_pp_test.c:28:6: warning: #warning BAUD_RATE error greater than -2% [-Wcpp]
#warning BAUD_RATE error greater than -2%
这按预期工作。 检查目标文件:
baud_setting = BAUD_SETTING;
8: 88 e0 ldi r24, 0x08 ; 8
a: 90 e0 ldi r25, 0x00 ; 0
c: a0 e0 ldi r26, 0x00 ; 0
e: b0 e0 ldi r27, 0x00 ; 0
10: 80 93 00 00 sts 0x0000, r24
14: 90 93 00 00 sts 0x0000, r25
18: a0 93 00 00 sts 0x0000, r26
1c: b0 93 00 00 sts 0x0000, r27
baud_actual = BAUD_ACTUAL;
20: 87 e0 ldi r24, 0x07 ; 7
22: 92 eb ldi r25, 0xB2 ; 178
24: a1 e0 ldi r26, 0x01 ; 1
26: b0 e0 ldi r27, 0x00 ; 0
28: 80 93 00 00 sts 0x0000, r24
2c: 90 93 00 00 sts 0x0000, r25
30: a0 93 00 00 sts 0x0000, r26
34: b0 93 00 00 sts 0x0000, r27
baud_error = BAUD_ERROR;
38: 8d ef ldi r24, 0xFD ; 253
3a: 9f ef ldi r25, 0xFF ; 255
3c: af ef ldi r26, 0xFF ; 255
3e: bf ef ldi r27, 0xFF ; 255
40: 80 93 00 00 sts 0x0000, r24
44: 90 93 00 00 sts 0x0000, r25
48: a0 93 00 00 sts 0x0000, r26
4c: b0 93 00 00 sts 0x0000, r27
...表示已分配预期值。 即, baud_setting
8
, baud_actual
111111
, baud_error
-3
。
现在我们用F_CPU构建,定义为无符号常量(按照此目标的惯例):
$ avr-gcc -Wall -c -g -save-temps -o optiboot_pp_test.elf -DF_CPU=8000000UL optiboot_pp_test.c
optiboot_pp_test.c:22:6: error: #error BAUD_RATE error greater than 5%
#error BAUD_RATE error greater than 5%
报告的错误幅度错误,错误的符号。
检查目标文件显示它与使用F_CPU的签名值构建的文件相同。
现在这一点都不令人惊讶,因为理解预处理器将所有常量视为最宽整数类型的有符号或无符号变量。
令人惊讶的是,标准中没有明确提及,也没有GCC文档(我能找到)。
是的,用于评估操作数的C规则完全由预处理器遵循,但仅限于二元运算符的两个操作数具有相同等级的情况。 我在标准中找不到任何文本说明预处理器处理使用或不使用L
或LL
指定的所有常量,就好像它们都是LL
然后才强制执行6.3.1.8中规定的整数提升规则,也不能找到任何提及GCC文档中的这种行为。 最接近的是上面引用的GCC文档的段落,声明预处理器“以编译器已知的最宽整数类型执行所有计算” 。
这不应该(不应该)明确表示将操作数视为用后缀指定它们,将后缀指定为编译器已知的最宽整数类型。 实际上,如果没有关于该主题的明确段落,我的期望是操作数将受到编译器评估时所有操作数所适用的相同类型转换和整数提升规则的约束。 似乎并非如此。 基于上述测试,其含义是在预处理器将操作数提升为编译器已知的最宽(有符号或无符号)整数类型之后 ,应用正常的C整数提升规则。
如果有人可以从标准或GCC文档中显示关于此主题的任何明确且相关的文本,我很感兴趣。
编辑#3: 注意:我已将评论部分中的以下段落复制到帖子本身,因为有太多评论可供查看。
如果有人可以从标准或GCC文档中显示关于此主题的任何明确且相关的文本,我很感兴趣。
这是6.10.1中的一些文字:
- 出于此标记转换和求值的目的,所有有符号整数类型和所有无符号整数类型的行为就像它们分别与头文件< stdint.h >中定义的intmax_t和uintmax_t类型相同 。
这似乎会成功。
引用通常的算术转换规则,( 强调我的 )来自C11
标准,章节§6.3.1.8。
否则,如果具有无符号整数类型的操作数的秩大于或等于另一个操作数的类型的秩,则具有有符号整数类型的操作数将转换为具有无符号整数类型的操作数的类型。
你的情况也是如此。
通常,如果您尝试执行涉及有符号和无符号类型的某些操作,则两个操作数将首先提升为无符号类型,然后执行操作。
在某些罕见的情况下,预处理器对数值常量值的解释可能与C不同,因为无论宽度说明符如何,将所有整数值视为最宽的可用有符号或无符号类型的副作用。 但是,给定生成的类型化数值,其评估条件表达式的规则明确与C的相同:
由此产生的标记构成控制常数表达式,该表达式根据[Section] 6.6的规则进行评估。
(C99,第6.10.1节)
第6.6节介绍C的常数表达规则,其中(第11段)
用于评估常量表达式的语义规则与非常量表达式相同。
因此,评估规则是全面的。 特别地,当二元运算符的操作数的类型不同时,在每种情况下应用相同的“通常算术转换”。 其他答案说明了这些细节。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.