前段時間出了個三級事件,查下來竟然是因為一個溢位造成的死迴圈,在公司出事件還是挺冒險的一件事,除了大boss要扣錢,還要給
高層一個合理的解釋,如果在小公司幹活,可能就算網站宕了一天估計也沒事,如果在大點的公司每秒都是銀子的流失,也許造成的損失就算
我們白乾一二年也抵不了,所以責任心和程式碼意識真的很重要。
先來看看問題程式碼,在這裡我做了一點點的修改,程式碼的意思很簡單,就是想獲取引數num中二進位制1的個數。
1 static void Run(long num) 2 { 3 int i = 1; 4 5 long num2 = 0; 6 7 List<int> list = new List<int>(); 8 9 while ((num2 = (long)Math.Pow(2, i - 1)) <= num) 10 { 11 if ((num & num2) > 0) 12 { 13 list.Add(i); 14 } 15 i++; 16 } 17 }
如果這是你寫的程式碼,你能一眼看出來問題在哪嗎?我們知道long是8個位元組,也就是64位二進位制,又因為二進位制位中最高位是符號位,所以當
是2的63次方時,顯示的就是long的minvalue,所以上面的程式碼當i=64的時候,又因為強轉成long,所以最後的結果變成了long的MinValue,如
果再迴圈下去的話就會在負數的道路上越走越遠,然後這個範圍溢位並沒有被CLR採納,也就沒有給我們丟擲OverflowException,悲劇就這樣
無情的發生了,問題是發生了,但是否能從這個問題上有一些思考,在基元型別的強轉中,真的適合用(long),(int)這種強轉模式嗎?從這個例子
上我們看到這個(long)模式的強轉根本就不會檢測溢位,所以以後在強轉中最好就不要用這種模式了,因為在.net框架下強轉的方式太多了,在
我瞭解的範圍內唯獨這種沒有溢位檢測,可能有些人認為這種轉換速度是最快的,但是又有多少人可以信誓旦旦的說我的程式絕對不會有溢位,就
算程式有溢出所造成行為異常我也會負全責的?
為了推崇非(long)強轉,下面介紹一下其他的強轉方式。
一:為了更好的理解程式碼,我們先來看看原始不檢測的模式。
1 long i = long.MaxValue; 2 int j = (int)i;
在IL中我們可以欣喜的看到這種不檢測溢位的強轉還有專有的IL指令:conv.i4,他的意思就是:將位於計算堆疊頂部的值轉換為 int32。
二:checked
在我們學C#語言的那天起,我們就知道有一個checked,他的唯一作用就是檢測溢位。如果有則丟擲異常,那我們再看看它和無檢測的
方式在IL中有什麼不同。
1 long i = long.MaxValue; 2 3 checked 4 { 5 int j = (int)i; 6 }
好傢伙,看似大串的程式碼在IL中居然也就一個指令,其實也就多了一個ovf,這個我想你也應該清楚,在轉換的時候多了一個溢位檢測。
如果你從效能上反駁的話,確實這個指令效能一定比無檢測的慢,我想做web的應該是慢的可以接受。
三:Convet.ToXXX。
這個就是C#給我們專用封裝轉換操作的類,這個也是我寫這篇部落格極力推薦的,既然是我推薦的,那肯定是會有檢測溢位的,下面我們來
看看程式碼和IL。
1 long i = long.MaxValue; 2 3 int j = Convert.ToInt32(i);
在IL上我們看到並沒有什麼特殊的轉換指令,那判斷肯定就在Toint32方法裡面了,下面的目光轉移到它的原始碼中去看一看。
從原始碼中,我們發現原來程式碼如此的簡潔,尤其是這個if,如果當時用了這個ToIntXXX,也許這個事件就會在測試環境被攔截了,也許某一
天,這個if就是你的最後一根救命稻草。
四:IConvertible介面
在這個介面中封裝了很多型別轉換的方法,而且所有的基元型別都實現了它,不過沒有意思的是竟然又呼叫了下Convert.ToXXX。。。
1 long i = 1; 2 3 int j = ((IConvertible)i).ToInt32(null);
從上面的IL上可以看到,居然有一個box,也難怪IConvertible是引用型別,怎麼可能不box呢?這個介面方法在值型別轉換場景下不值得提
倡,不方便不說,還有較大的效能損失。
好了,總結性的話也來了。
① 無檢測程式碼模式: 非常不提倡,總有一天會害死你了。
② checked: 這種雖然有檢測,但是寫起來麻煩,當然也可以在vs裡面自己去設定全域性檢測。
③ Convert.Toxxx: 這篇就是為了提倡它而寫的,所以這個重要性我就不說了,總有一天會救你於水火之中的。
④ IConvertible: 在值型別的場景下,效能最爛而且還不好coding。