簡體   English   中英

看起來表達式的某些部分可能在編譯時進行評估,而其他部分則在運行時進行評估

[英]It appears some parts of an expression may be evaluated at compile-time, while other parts at run-time

可能是一個愚蠢的問題,因為我可能已經回答了我的問題,但我只是想確定我沒有遺漏某些東西

在編譯時在已檢查的上下文中計算常量表達式。 我認為不應該在編譯時評估以下表達式,因為我假設C#只在左側的所有操作數都是常量時才將特定表達式視為常量表達式:

int i= 100;
long u = (int.MaxValue  + 100 + i); //error

相反,看起來編譯器會考慮任何子表達式,其中兩個操作數都是常量作為常量表達式,即使表達式中的其他操作數是非常量? 因此編譯器可能只在編譯時計算表達式的一部分,而表達式的剩余部分(包含非常量值)將在運行時得到評估 - >我假設僅在以下示例中(200 +100)在編譯時得到評估

int i=100;
long l = int.MaxValue  + i + ( 200 + 100 ); // works

我的假設是否正確?

感謝名單

doec C#將兩個操作數都是常量的子表達式分類為常量表達式,即使表達式中的其他操作數是非常量的嗎?

你的問題的答案是“是”,但重要的是清楚地理解什么是“子表達”。

我假設在“int.MaxValue + i +(200 + 100)”中只有(200 + 100)在編譯時得到評估

正確。 現在,如果您反而說“int.MaxValue + i + 200 + 100”,那么“200 + 100”將永遠不會被評估,因為這不是由於關聯性而導致的子表達式。

這有點微妙。 讓我詳細解釋一下。

首先,讓我們區分法律上的編譯時常量和事實上的編譯時常量。 讓我給你舉個例子。 考慮這個方法體:

const int x = 123;
int y = x + x;
if (y * 0 != 0) Console.WriteLine("WOO HOO");
M(ref y);

在C#1和2中,如果您使用優化編譯它,那么將編譯為就像您編寫的那樣:

int y = 246;
M(ref y);

常量x消失,y被初始化為常量折疊表達式,算術優化器意識到任何局部整數乘以零永遠不等於零,因此它也優化了它。

在C#3中,我意外地在優化器中引入了一個錯誤,該錯誤在C#4中也沒有修復。 在C#3/4中,我們將其生成為

int y = 246;
bool b = false;
if (b) Console.WriteLine("WOO HOO");
M(ref y);

也就是說,算法被優化掉了,但是我們不再進一步優化掉“if(false)”。

不同之處在於x + x上的常量折疊行為保證在編譯時發生,但是部分變量表達式y * 0上的常量折疊行為不是。

我為這個錯誤感到遺憾並道歉。 但是,我在C#3中更改了這個並且意外地在代碼生成器中引入了一個錯誤的原因是修復了語義分析器中的錯誤。 在C#2.0中,這是合法的:

int z;
int y = 246;
if (y * 0 == 0) z = 123;
Console.WriteLine(z);

那不應該是合法的。 你知道並且我知道y * 0 == 0將永遠為真,因此z在讀取之前被賦值,但是規范說只有在“if”中的表達式是編譯時才能執行此分析。時間常量,這不是編譯時常量,因為它包含一個變量。 我們對C#3.0進行了重大改變。

所以,好吧,讓我們假設您理解必須評估的法律上的常量之間的區別,因為規范說明了這一點,並且因為優化器是智能的,所以評估的事實上的常量。 你的問題是在什么情況下表達在法律上事實上在編譯時部分“折疊”? (“折疊”是指將包含常量的表達式解析為更簡單的表達式。)

我們必須考慮的第一件事是運營商的關聯性。 只有在我們完成關聯性和優先級分析之后,我們才知道什么是子表達式而不是子表達式。 考慮

int y = 3;
int x = 2 - 1 + y;

加法和減法是左關聯的,就像C#中的大多數運算符一樣,因此它們是相同的

int x = (2 - 1) + y;

現在很明顯(2 - 1)是一個法律上的常量表達式,因此它被折疊並變為1。

如果另一方面你說

int x = y + 2 - 1;

那是

int x = (y + 2) - 1;

並且它沒有折疊,因為它們是兩個非常量表達式。

這可以在檢查的上下文中產生實際效果。 如果y是int.MaxValue - 1則第一個版本不會溢出但第二個版本會溢出!

現在,允許編譯器和優化器說“好吧,我碰巧知道這是一個未經檢查的上下文,我碰巧知道我可以安全地將它變成”y + 1“,所以我會這樣做。” C#編譯器不會這樣做,但抖動可能會。 在你的特定例子中,

long l = int.MaxValue + i + 200 + 100 ; 

實際上是由C#編譯器代碼生成的

long l = ((int.MaxValue + i) + 200) + 100 ; 

並不是

long l = (int.MaxValue + i) + 300; 

如果願意,抖動可以選擇進行優化,並且可以證明這樣做是安全的。

long l = int.MaxValue + i + (200 + 100); 

當然會被生成為

long l = (int.MaxValue + i) + 300; 

但是,我們在C#編譯器中對字符串執行所需的優化! 如果你說

string y = whatever;
string x = y + "A" + "B" + "C";

那么你可能會認為,這是一個左聯結表達式,所以:

string x = ((y + "A") + "B") + "C";

因此不會有不斷的折疊。 但是,我們實際上檢測到了這種情況,並且在編譯時將折疊向右移動,因此我們就像您編寫的那樣生成它

string x = y + "ABC";

從而節省了運行時連接的成本。 字符串concat優化器實際上相當復雜,可以識別在編譯時將字符串粘合在一起的各種模式。

對於未經檢查的算術,我們可以做同樣的事情。 我們還沒有找到它。

暫無
暫無

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

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