[英]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.