簡體   English   中英

在預處理程序指令中評估常量整數表達式時的升級 - GCC

[英]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的回答中的評論,我的困惑最初源於表達式,其中包括用LLL后綴指定的常量。 我原始問題中包含的示例代碼太簡單了。 這是一個更好的例子:

#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被視為-5LL0u0uL被視為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 8baud_actual 111111baud_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規則完全由預處理器遵循,但僅限於二元運算符的兩個操作數具有相同等級的情況。 我在標准中找不到任何文本說明預處理器處理使用或不使用LLL指定的所有常量,就好像它們都是LL 然后才強制執行6.3.1.8中規定的整數提升規則,也不能找到任何提及GCC文檔中的這種行為。 最接近的是上面引用的GCC文檔的段落,聲明預處理器“以編譯器已知的最寬整數類型執行所有計算”

這不應該(不應該)明確表示將操作數視為用后綴指定它們,將后綴指定為編譯器已知的最寬整數類型。 實際上,如果沒有關於該主題的明確段落,我的期望是操作數將受到編譯器評估時所有操作數所適用的相同類型轉換和整數提升規則的約束。 似乎並非如此。 基於上述測試,其含義預處理器將操作數提升為編譯器已知的最寬(有符號或無符號)整數類型之后 ,應用正常的C整數提升規則。

如果有人可以從標准或GCC文檔中顯示關於此主題的任何明確且相關的文本,我很感興趣。


編輯#3: 注意:我已將評論部分中的以下段落復制到帖子本身,因為有太多評論可供查看。

如果有人可以從標准或GCC文檔中顯示關於此主題的任何明確且相關的文本,我很感興趣。

這是6.10.1中的一些文字:

  1. 出於此標記轉換和求值的目的,所有有符號整數類型和所有無符號整數類型的行為就像它們分別與頭文件< stdint.h >中定義的intmax_tuintmax_t類型相同

這似乎會成功。

引用通常的算術轉換規則,( 強調我的 )來自C11標准,章節§6.3.1.8。

否則,如果具有無符號整數類型的操作數的秩大於或等於另一個操作數的類型的秩,則具有有符號整數類型的操作數將轉換為具有無符號整數類型的操作數的類型。

你的情況也是如此。

通常,如果您嘗試執行涉及有符號和無符號類型的某些操作,則兩個操作數將首先提升為無符號類型,然后執行操作。

請閱讀此處有關算術運算的整數轉換,包括比較。

這基本上導致 - 對於您的示例,您混合有相同等級的有符號和無符號的地方 - 已簽名的轉換為無符號表示,反之亦然。 因此,后兩者的比較是無符號的。 這對於預處理器和實際編譯器是相同的。

6.3.1.3p2 ,對於2s補碼有符號表示(現在最常用於標准CPU)意味着有符號整數值的二進制表示只是被重新解釋為無符號(正)值,因此比較都失敗了。

請注意,您應該啟用-Wconversions (gcc)以查看有關此類有問題的轉換的警告。

在某些罕見的情況下,預處理器對數值常量值的解釋可能與C不同,因為無論寬度說明符如何,將所有整數值視為最寬的可用有符號或無符號類型的副作用。 但是,給定生成的類型化數值,其評估條件表達式的規則明確與C的相同:

由此產生的標記構成控制常數表達式,該表達式根據[Section] 6.6的規則進行評估。

(C99,第6.10.1節)

第6.6節介紹C的常數表達規則,其中(第11段)

用於評估常量表達式的語義規則與非常量表達式相同。

因此,評估規則是全面的。 特別地,當二元運算符的操作數的類型不同時,在每種情況下應用相同的“通常算術轉換”。 其他答案說明了這些細節。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM