簡體   English   中英

是在編譯時還是在運行時評估常量C表達式?

[英]Are constant C expressions evaluated at compile time or at runtime?

如果我編寫一個使用其他預處理器常量執行操作的#define ,那么每次宏在運行時出現時計算的最終值是多少? 這取決於編譯器中的優化,還是屬於標准?

例:

#define EXTERNAL_CLOCK_FREQUENCY    32768
#define TIMER_1_S                   EXTERNAL_CLOCK_FREQUENCY
#define TIMER_100_MS                TIMERB_1_S / 10

每次都會十分之三萬二千七百六十八在運行期間發生的操作我用的是TIMER_100_MS宏?

我想避免以下情況:

#define EXTERNAL_CLOCK_FREQUENCY    32768
#define TIMER_1_S                   EXTERNAL_CLOCK_FREQUENCY
#define TIMER_100_MS                3276

摘要

編譯器需要能夠計算常量積分表達式,因為它們是在編譯時計算數組大小等內容所必需的。 但是,標准只說他們“可以” - 而不是“必須” - 這樣做。 因此,只有腦死亡編譯器不會在編譯時評估常量積分表達式,但是對非常規編譯器的匯編輸出的簡單檢查將驗證每種情況。

宏是簡單的文本替換,所以在你的例子寫TIMER_100_MS的計划是寫的一個奇特的方式32768 / 10

因此,問題是編譯器何時評估32768 / 10 ,這是一個常量積分表達式。 我不認為標准在這里需要任何特定的行為(因為運行時和編譯時評估在效果上是無法區分的),但任何中途正常的編譯器都會在編譯時對其進行評估。

這里的大多數答案都集中在宏觀替代的影響上。 但我想他想知道是否

32768 / 10

在編譯時進行評估。 首先,這是一個算術常量表達式,另外還有一個整數常量表達式(因為它只有整數類型的文字)。 該實現可以在運行時自由計算,但它也必須能夠在編譯時計算它,因為

  1. 如果常量表達式在其表達式所具有的類型中無法表示,則它必須提供診斷消息
  2. 在翻譯時需要值的上下文中允許使用此類表達式,例如,如果用作數組維度的大小。

如果編譯器可以在編譯時主要計算結果,那么它應該使用該值,而不是在運行時重新計算它。 但也許有一些理由仍然這樣做。 我不知道。

編輯 :對不起,我已經回答了這個問題,好像它是關於C ++的。 今天你注意到了C。表達式中的溢出被認為是C中的未定義行為,無論它是否發生在常量表達式中。 當然,第二點在C中也是如此。

編輯 :作為評論說明,如果宏被替換為像3 * TIMER_100_MS這樣的表達式,那么這將評估(3 * 32768) / 10 因此,簡單而直接的答案是“不,它不會在每次運行時發生,因為由於優先級和關聯性規則,可能根本不會發生划分” 我上面的回答假設宏總是被替換,以便實際發生划分。

我不知道有任何標准可以保證它會被優化。 預處理器將替換32768/10替換TIMER_100_MS,您可以通過運行gcc -c看到它。 要查看編​​譯器是否進一步優化,請運行gcc -S並檢查匯編程序。 使用gcc 4.1,即使沒有任何優化標志,這也會在編譯期間減少到常量:

#include <stdlib.h>
#include <stdio.h>

#define EXTERNAL_CLOCK_FREQUENCY    32768
#define TIMER_1_S                   EXTERNAL_CLOCK_FREQUENCY
#define TIMER_100_MS                TIMER_1_S / 10

int main(int argc, char **argv)
{
  printf("%d\n", TIMER_100_MS);

  return(0);
}

gcc -S test.c
cat test.s

...
    popl    %ebx
    movl    $3276, 4(%esp)
    leal    LC0-"L00000000001$pb"(%ebx), %eax
    movl    %eax, (%esp)
    call    L_printf$stub
...

編譯器應優化該表達式。 我不認為這是標准所要求的,但我從未見過不會執行該任務的編譯器。

但是,你不應該寫:

#define TIMER_100_MS      TIMERB_1_S / 10

...因為這是一個等待發生的錯誤。 你應該總是將涉及表達式的#defines括起來。

#define TIMER_100_MS      (TIMERB_1_S / 10)

考慮:

i = 10 * TIMER_100_MS;

第一種情況是32768((10 * TIMERB_1_S)/ 10),第二種情況是32760(10 *(TIMERB_1_S / 10))。 這里不是一個關鍵的區別,但你必須意識到它!

每次使用TIMERB_100_MS宏時,操作32768/10都會在運行時發生嗎?

在代碼中的每個地方,你使用TIMERB_100_MS ,它會被替換32768 / 10由預處理器。

該表達式是否得到進一步優化(它的計算結果為常量)取決於編譯器。

來自WG14 / N1124委員會草案 - 2005年5月6日ISO / IEC 9899:TC2

6.6常量表達式

句法

常量表達式:
條件表達式

描述

可以在轉換期間而不是運行時期間評估常量表達式,並且因此可以在常量可以在任何地方使用。

約束

常量表達式不應包含賦值,遞增,遞減,函數調用或逗號運算符,除非它們包含在未評估的子表達式中.9)

每個常量表達式應計算為其類型的epresentable值范圍內的常量。

伙計們,這種轉變被稱為“不斷折疊”,甚至大多數學生編制者也會這樣做。 只要你有一個由你或你的大學室友建立的編譯器並且你正在編譯一個靜態類型的語言,即使沒有打開優化,你也可以依靠它。 如果你正在處理一些允許改變/的含義的古怪動態語言,那就不一樣了。

這是不正確的,編譯器無法在編譯時操作浮點數。 如果您在編譯時對3276值感到滿意,那么您可以繼續使用,但編譯器無法在編譯時使用浮點精度對此進行評估。 浮點數對於編譯器來說太難以優化,因為優化浮點數會導致數學表達式出現意外結果,所以一個不錯的編譯器(任何gcc版本,任何clang版本,任何msvc版本,任何icc版本)都不會將其簡化為3276.8,故事結束。

問題的另一部分,你問過它是否會針對每次宏擴展進行評估。 再次,如果你沒有3276值,那么答案是否定的,它取決於編譯器,優化級別和背景,它可以放在常量表中,也可以在代碼中內聯。 永遠不會為每個宏擴展在運行時計算相同的表達式。 同樣,如果您希望浮點精度得到3276.8,那么將在運行時為每個宏擴展計算該表達式。

請查看更多編譯和優化方面: http//www.agner.org/optimize/#manuals

在編譯時。 這是一種語言標准(並且一直都是)並且獨立於編譯器。

編輯

一位評論者要求參考 - 引用“C編程語言”第2版附錄A12.3(第229頁):

表格的控制線

 #define identifier token-sequence 

使預處理器用給定的令牌序列替換標識符的后續實例; roken序列周圍的前導和尾隨空格被刪除

編輯結束

暫無
暫無

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

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