C++使用sort排序導致的coredump(Strict Weak Ordering)
當我們需要自定義排序規則時,需要實現一個比較函式,該函式類似如下:
bool cmp(int a,int b)
{
return a>b;
}
當cmp返回true時,a將會排在b前面,因此上面的函式將從大到小排序。
換句話說,cmp函式重新定義了“小”的概念(當a>b時,a“小於”b),整個序列將按照這個“小”的規則從“小”到“大”排序。
前幾天同事遇到一個問題:
對一組資料從小到大排序,對於值相等的兩個元素,經過排序之後,原本靠後的元素排在前面。於是他寫出瞭如下的比較函式:
bool cmp(int a,int b)
{
return a<=b;
}
以上寫法有兩個問題:
首先我們回顧下排序演算法穩定性的概念:假定在待排序的記錄序列中,存在多個具有相同的關鍵字的記錄,若經過排序,這些記錄的相對次序保持不變,即在原序列中,ri=rj,且ri在rj之前,而在排序後的序列中,ri仍在rj之前,則稱這種排序演算法是穩定的;否則稱為不穩定的。
而sort內部使用的排序演算法不一定是穩定的(當元素較多時,使用的快速排序是不穩定的),對於一個不穩定的演算法,是無法保證值相同的兩個元素的順序的。
從使用上來說,傳遞給sort的cmp函式,就不應該使用等號。
從後果上來說,上述用法不僅不能解決問題,還可能導致程式coredump——沒錯,就在週五晚上,運營人員配置了一個新的元素之後,觸發了sort函式的coredump,幸虧人還在公司,立馬把觸發coredump的資源下掉,並及時修復了該bug。
coredump原因分析:
對於std::sort(),當容器裡面元素的個數大於_S_threshold 的列舉常量值時,會使用快速排序(stl的這個預設值是16)。
STL快速排序原始碼的關鍵程式碼:
注意這兩行:
while(__comp(*__first,__pivot))
++__first;
這兩行程式碼裡面,__pivot是中間值,__first是迭代器,假設我們的__comp函式對於相等的值返回true,那麼如果一個容器裡面最後一段元素所有值都相等,那麼__comp(*__first,__pivot)就恆為真。迭代器往前移的時候,終會移過最後一個元素,於是迭代器失效,程式core。
可以看到,上面快排的思路是,從左往右找一個比中間值“大”(即cmp函式返回false)的元素,從右往左找一個比中間值“小”元素,然後交換兩個元素的位置,使得大的在右邊,小的在左邊。
當cmp函式return a < b時,如果所有元素已經是有序的了,只要遇到一個值跟中間元素相等的元素(包括中間元素自己),cmp便返回false,__first迭代器右移停止。
而當cmp函式return a <= b時,若中間元素前面的元素都比它小,而後面的元素都跟它相等或者比它小,那麼cmp恆返回true,__first迭代器不斷右移,程式越界導致coredump。
坑踩過了,才能漲知識。
其實類似的問題,在《Effective STL》 的條款21中就有討論:永遠讓比較函式對相同元素返回false!
構建一個set,比較型別用的是less_equal,然後insert一個10:
set<int, less_equal<int> > s; // s is sorted by “<=”
s.insert(10); // insert the value 10
現在嘗試再insert一次10:
s.insert(10);
對於這一個insert的呼叫,set必須先要搞明白10是否已經位於其中。 我們知道它已經位於其中,但set可是木頭木腦的,它必須執行檢查。為了便於弄明白髮生了什麼,我們將已經在set中的10稱為10A,而正試圖insert的那個叫10B。
set遍歷它的內部資料結構以查詢加入10B的位置。 最終,它總要檢查10B是否與10A相同。 關聯容器對“相同”的定義是equivalence(見Item 19)(WQ注: equivalence應指“數學相等”,two elements are equal if neither is less than the other,見《The C++ Standard Library》中文版P82,英文版電子P77;equality指“邏輯等價”,使用operator==(),見《The Standard Template Library》英文電子版P30)。
因此set測試10B是否數學等值於10A。 當執行這個測試時,它自然是使用set的比較函式。在這一例子裡,是operator<=,因為我們指定set的比較函式為less_equal,而less_equal就是operator<=。於是,set將計算這個表示式是否為真:
!(10 A <= 10 B ) && !(10 B <= 10 A ) // test 10 A and 10 B for equivalence
10A和10 B都是10,因此,10A <= 10B 肯定為真。同樣,10A <= 10B。上述的表示式簡化為
!(true) && !(true)
再簡化就是
false && false
結果當然是false。 也就是說,set得出的結論是10A與10B不等值,因此不一樣,於是它將10B加入容器。在技術上而言,這個行動導致未定義的行為,但是通常的結果是set終結於擁有了兩個值為10的元素的拷貝,也就是說它不再是一個set了。通過使用less_equal作為我們的比較型別,我們破壞了容器!
此外,所有對相同的元素返回true的比較函式都會做相同的事情。根據定義,相同的元素,是不等值的!
因此我們需要確保用在關聯容器上的比較函式總是對相同的元素返回false。
要避免掉入這個陷阱,你所要記住的就是比較函式的返回值指明的是在此函式定義的排序方式下,一個元素是否應該位於另一個之前。對於關聯容器,相同的元素絕不該一個領先於另一個,所以比較函式總應該為相同的元素返回false。
以上的討論是針對關聯容器的。從技術上講,用於關聯容器的比較函式必須在它們所比較的物件上定義一個“strict weak ordering“。其實,傳給sort 等泛型演算法的比較函式也有同樣的限制。
如果你對strict weak ordering的含義細節感興趣,可在很多STL指導書籍中找到,比如Josuttis的《The C++ Standard Library》(WQ注:中文版P176,英文版電子P156),Austern的《Generic Programming and the STL 》,和SGI STL的網站。
任何定義了一個strict weak ordering 的函式都必須在傳入相同元素的兩個拷貝時返回false,具體要求如下:
如果一個comp函式要滿足“Strict Weak Ordering”,意味著它應該滿足如下特徵(更多細節可以參見SGI版實現相關描述http://www.sgi.com/tech/stl/StrictWeakOrdering.html):
(a) 反自反性:也即comp(x, x)必須是false
(b) 非對稱性:也即如果comp(x, y)和comp(y, x)的結果必然相反
(c) 可傳遞性:也即如果comp(x, y)為true,comp(y, z)為true,那麼comp(x, z)必然為true
這麼看到,示例程式碼的comp定義明顯違反了(a)(b)兩條,所以sort使用它時就可能工作不正常。解決辦法也很簡單,去掉那個“=”,再對照下”Strict Weak Ordering”的定義,就滿足了。
相關推薦
C++使用sort排序導致的coredump(Strict Weak Ordering)
當我們需要自定義排序規則時,需要實現一個比較函式,該函式類似如下: bool cmp(int a,int b) { return a>b; } 當cmp返回true時,a將會排在b前面,因此上面的函式將從大到小排序。 換句話說,cmp函式重新定義了
c++ stl sort 自定義排序函式cmp要遵循 strict weak ordering
滿足strict weak ordering的運算子能夠表達其他所有的邏輯運算子(logical operator): <(a, b) : (a < b) <=(a, b): !(b < a) ==(a, b): !(a <
C++ sort 排序(降序、升序)使用總結
一、升序 C++ sort 函式十分方便,可以對內建型別也可對自定義型別進行快速排序,內建型別的使用比較簡單,下面主要討論自定義型別的排序,一般有如下幾種使用方法: 1.1 過載比較操作符
C++ sort排序函式用法
最近在刷ACM經常用到排序,以前老是寫冒泡,可把冒泡帶到OJ裡後發現經常超時,所以本想用快排,可是很多學長推薦用sort函式,因為自己寫的快排寫不好真的沒有sort快,所以毅然決然選擇sort函式 用法 1、
C++ sort排序函式
注意事項 1、sort函式可以三個引數也可以兩個引數,必須的標頭檔案#include < algorithm>和using namespace std; 2、它使用的排序方法是類似於快排的方法,時間複雜度為n*log2(n) 3、Sort函式有三個引數:(第
【內部排序】三:希爾排序(Shell Sort)的多種實現(不斷優化+原始碼)
當我們看程式碼時,一時不能理解的話。畫下草圖,用例項來分析下,自己舉個例子,跟著程式碼的執行流程一步步走,回過頭來再看就明白了。 希爾排序也是一種插入排序方法,實際上是一種分組插入方法。 一、基本思
C# Sort排序
List 的Sort方法排序有三種結果 1,0,-1分別表示大於,等於,小於。 1.對於數值型別的List (List<int>),直接使用Sort進行排序。 List<int> scoreList=new List<int>(){89,
電子書 C#高級編程(第9版).pdf
work 並發編程 桌面應用 href 驅動開發 靈活 交互 c# 電子 《C#高級編程(第9版):C# 5.0 & .NET 4.5.1 》由.NET專家的夢幻組合編寫,包含開發人員使用C#所需的所有內容。C#是編寫.NET應用程序的一種語言,本書適合於希望提高編
C++ 何時使用動態分配(即使用newkeyword)?何時使用指針?
指向 delet 問題 con 擁有 才會 屬性 想要 自己 動態分配 在你的問題裏。你用了兩種方式創建對象。這兩種方式基本的不同在於對象的存儲時間。當運行Object myObject;這句代碼時。它作為自己主動變量被創建,這意味著當對象出了作用域時也會自己主動銷毀。
C#調用Java方法(詳細實例)
art dem 關系 進行 網上 auto mar ctr 環境 閱讀目錄 C#調用c++ C#調用JAVA方法 C#可以直接引用C++的DLL和轉換JAVA寫好的程序。最近由於工作原因接觸這方面比較多,根據實際需求,我們通過一個具體例子把一個JAVA方法轉換成可以
C++11 Lambda表達式(匿名函數)
class 訪問 namespace 表達式 span sin clas style col http://www.cnblogs.com/RainyBear/p/5733399.html 匿名函數,好屌的樣子。 Lambda表達式的引入標誌,在‘[]’裏面可以填入‘=’
設計模式C++學習筆記之十三(Decorator裝飾模式)
com img c++ 進行 done 設計 out set 筆記 裝飾模式,動態地給一個對象添加一些額外的職責。就增加功能來說,Decorator模式相比生成子類更為靈活。 13.1.解釋 main(),老爸 ISchoolReport,成績單接口 CFourthGrad
C#文件讀寫(txt 簡單方式)
換行 line string txt 不換行 返回 true text empty 1.文件寫入 // 路徑,寫入內容 System.IO.File.WriteAllText(@".\File.txt", string.Empty); 可更換相應的方法 2.文件讀入 /
c++ 循環簡單說(for)(讀書體會)
left p s margin face href pxn gin 讀書 blank Y宜oab樸祭Q蔚06詞ghttp://www.facebolw.com/space/2103436/following Z煤94o販40鑰qg魄http://www.facebolw.
1014 C語言程序設計教程(第三版)課後習題6.4
content += 教程 print ons ont c語言程序設計 lld cnblogs 題目描述 求Sn=1!+2!+3!+4!+5!+…+n!之值,其中n是一個數字。 輸入 n 輸出 和 樣例輸入 5 樣例輸出 153 1 #include "stdio.h"
1013: C語言程序設計教程(第三版)課後習題6.3
其中a是一個數字 blog += color turn sam c語言程序 [] c語言 題目描述 求Sn=a+aa+aaa+…+aa…aaa(有n個a)之值,其中a是一個數字。 例如:2+22+222+2222+22222(n=5),n由鍵盤輸入。 輸入 a 輸出 和 樣
1024: C語言程序設計教程(第三版)課後習題7.3
c語言程序 print clas 程序 scanf col class pri printf 題目描述 求一個3×3矩陣對角線元素之和。 輸入 矩陣 輸出 主對角線 副對角線 元素和 樣例輸入 1 2 3 1 1 1 3 2 1 樣例輸出 3 7 1 #include
1046: C語言程序設計教程(第三版)課後習題10.4
con n) 順序 調整 style char ++ 輸入數據 include 題目描述 有n個整數,使前面各數順序向後移m個位置,最後m個數變成前面m個數,見圖。寫一函數:實現以上功能,在主函數中輸入n個數和輸出調整後的n個數。 輸入 輸入數據的個數n n個整數 移動的位
c#高級編程(第10版)源碼
高級編程 涵蓋 div ffffff 部署 cor windows 獲得 運算符 下載地址:網盤下載 Visual Studio 2015、ASP.NET Core 1.0和Universal Windows Platform的推出,為開發人員提供了使用C#創建應用程序
C之 #error 和 #line(二十一)
C語言 #error #line 我們今天來講下 C 語言中的兩個比較偏僻的知識點,之所以說偏僻是因為在平時的代碼中我們見得很少。首先來說下 #error,它是用於生成一個編譯錯誤消息。用法如下:#error message;註意 message 不需要用雙引號包圍。#error