【发布时间】:2015-07-21 16:47:24
【问题描述】:
注意:请在下方查看我的编辑。
原始问题:
遇到了一些我无法调和的奇怪行为:
#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 的回答中的 cmets,我的困惑最初源于表达式,其中包含使用 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: 注意:我已将以下段落从 cmets 部分复制到帖子本身,因为 cmets 太多,无法看到。
如果有人可以就该主题显示任何明确且相关的文字, 无论是来自标准还是 GCC 文档,我都很感兴趣。
这是 6.10.1 中的一些文本:
- 出于此令牌转换和评估的目的,所有有符号整数类型和所有无符号整数类型的行为都好像它们分别具有与 intmax_t 和 uintmax_t 在标头 stdint.h> 中定义。
这似乎很成功。
【问题讨论】:
-
“即,当一个运算符有混合符号的操作数时,有符号的操作数被改变为一个无符号的操作数。相反(通常)在 C 中是正确的。” – 不,在 C 语言中也是如此。在 C 语言中,如果将等宽的有符号和无符号整数用作算术运算符的操作数,则有符号整数将转换为无符号类型。
-
"... 可能无法按预期运行..." 不。它确实完全按预期运行。 IOW:正如标准定义的那样——当然,除非你的编译器坏了。
-
Sourav Ghosh,感谢您添加“类型”标签。
-
“这与编译器评估表达式的方式真的没有区别吗?”这取决于回答问题的水平。关键区别在于 pp 如何解释 constants。它计算涉及与常量对应的类型化值的表达式的方式与 C 计算涉及相同类型化值的相同表达式的方式完全相同。随心所欲地描述它。我已经更新了我的答案,以便在这方面更加准确。
-
“关键的区别在于 pp 如何解释常量。” - 但这一切都不同了,不是吗?查看我对原始问题的新修改。
标签: c gcc c-preprocessor integer-promotion