http://www.who1753.com/100-bugs-in-c-cpp-opensource-projects/
俄羅斯OOO Program Verification Systems公司用自己的靜態(tài)源碼分析產(chǎn)品PVS-Studio對一些知名的C/C++開源項目,諸如Apache Http Server、Chromium、Clang、CMake、MySQL等的源碼進行了分析,找出了100個典型的Bugs。個人覺得這份列表對C/C++ 程序員有一定參考意義。與其說事后用靜態(tài)工具分析,倒不如在編碼時就提高自知自覺,避免這份列表上的錯誤發(fā)生在你的代碼中,因此這里將部分摘錄一些Bugs(Bug編號這里不連續(xù),為的是對應(yīng)原文的編號)并做簡要說明。原文將這份Bug列表分為了幾類,這里也將沿用這個思路。
一、數(shù)組和字符串處理錯誤
數(shù)組和字符串處理錯誤是C/C++程序中最多的一類缺陷類型。這也可以看作是我們?yōu)閾碛懈咝У氐讓觾?nèi)存操作能力而付出的代價。
[#1] Wolfenstein 3D項目 -"只有部分對象被clear了"
1
2
3
4
5
6
7
8 |
void CG_RegisterItemVisuals( int itemNum )
{
…
itemInfo_t *itemInfo;
…
memset ( itemInfo, 0, sizeof ( &itemInfo ) );
…
}
|
這里的Bug出現(xiàn)在memset那一行。代碼的真實意圖是clear iteminfo這塊內(nèi)存,但調(diào)用memset時,第三個參數(shù)傳入的卻是sizeof(&iteminfo),要知道 sizeof(&itemInfo) != sizeof(itemInfo_t),前者只是一個指針的大小罷了。正確的寫法是:
memset(itemInfo, 0, sizeof(itemInfo_t)); 或memset(itemInfo, 0, sizeof(*itemInfo));
[#2] Wolfenstein 3D項目 -"只有部分Matrix被clear了"
ID_INLINE mat3_t::mat3_t( float src[ 3 ][ 3 ] ) {
memcpy( mat, src, sizeof( src ) );
}
這里的Bug出現(xiàn)在memcpy一行。程序的原意是將clear src[3][3]這個二維數(shù)組。但這里有個坑:那就是作為函數(shù)形式參數(shù)的數(shù)組名已經(jīng)退化為指針了,對其sizeof只能得到一個指針的長度,因此這里的 memcpy只是copy了一個指針的長度,沒有copy全。這里的代碼是C++代碼,原文中給出了正確的改正方法 – 傳reference:
ID_INLINE mat3_t::mat3_t( float (&src)[3][3] )
{
memcpy( mat, src, sizeof( src ) );
}
[#4] ReactOS項目 – "錯誤地計算一個字符串的長度"
static const PCHAR Nv11Board = "NV11 (GeForce2) Board";
static const PCHAR Nv11Chip = "Chip Rev B2";
static const PCHAR Nv11Vendor = "NVidia Corporation";
BOOLEAN
IsVesaBiosOk(…)
{
…
if (!(strncmp(Vendor, Nv11Vendor, sizeof(Nv11Vendor))) &&
!(strncmp(Product, Nv11Board, sizeof(Nv11Board))) &&
!(strncmp(Revision, Nv11Chip, sizeof(Nv11Chip))) &&
(OemRevision == 0×311))
…
}
Bug處在IsVesaBiosOK中那一串strncmp調(diào)用中,代碼將一個指針的size傳入strncmp作為第三個參數(shù),導(dǎo)致 strncmp實際只是比較了字符串的前4 or 8個字節(jié),而不是字符串的全部內(nèi)容。
[#6] CPU Identifying Tool項目 – 數(shù)組越界
#define FINDBUFFLEN 64 // Max buffer find/replace size
…
int WINAPI Sticky (…)
{
…
static char findWhat[FINDBUFFLEN] = {'\0'};
…
findWhat[FINDBUFFLEN] = '\0';
…
}
bug出在"findWhat[FINDBUFFLEN] = ‘\0′;”這一行。數(shù)組的最大長度為FINDBUFFLEN,但下標的最大值應(yīng)該是FINDBUFFLEN-1,而不是FINDBUFFLEN。因此這 行代碼顯然應(yīng)該改為findWhat[FINDBUFFLEN-1] = '\0';
[#7] Wolfenstein 3D項目 – 數(shù)組越界
typedef struct bot_state_s
{
…
char teamleader[32]; //netname of the team leader
…
} bot_state_t;
void BotTeamAI( bot_state_t *bs ) {
…
bs->teamleader[sizeof( bs->teamleader )] = '\0';
…
}
"sizeof( bs->teamleader )]"這行的結(jié)果值已經(jīng)超出了數(shù)組的最大邊界,正確的代碼是:
bs->teamleader[
sizeof(bs->teamleader) / sizeof(bs->teamleader[0]) – 1
] = '\0';
[#8] Miranda IM項目 – 只Copy了部分字符串
struct _textrangew
{
CHARRANGE chrg;
LPWSTR lpstrText;
} TEXTRANGEW;
const wchar_t* Utils::extractURLFromRichEdit(…)
{
…
::CopyMemory(tr.lpstrText, L"mailto:", 7);
…
}
這里的bug在于L"mailto:"是寬字符串,寬字符串中的每個字符占2或4個字節(jié)(依Compiler使用的字符集編碼而定),因此這里只 copy 7個字節(jié)顯然是不夠的,應(yīng)該是7 * sizeof(wchar_t)。
[#9] CMake項目 – 循環(huán)內(nèi)的數(shù)組越界
static const struct {
DWORD winerr;
int doserr;
} doserrors[] =
{
…
};
static void
la_dosmaperr(unsigned long e)
{
…
for (i = 0; i < sizeof(doserrors); i++)
{
if (doserrors[i].winerr == e)
{
errno = doserrors[i].doserr;
return;
}
}
…
}
作者原本意圖la_dosmaperr中for循環(huán)的次數(shù)等于數(shù)組的元素個數(shù),但sizeof(doserrors)返回的卻是數(shù)組占用的字節(jié)個數(shù),這遠遠大于數(shù)組元素個數(shù),因此造成數(shù)組越界。正確的寫法:
for (i = 0; i < sizeof(doserrors) / sizeof(*doserrors); i++)
[#10] CPU Identifying Tool項目 – 打印到自身的字符串
char * OSDetection ()
{
…
sprintf(szOperatingSystem,
"%sversion %d.%d %s (Build %d)",
szOperatingSystem,
osvi.dwMajorVersion,
osvi.dwMinorVersion,
osvi.szCSDVersion,
osvi.dwBuildNumber & 0xFFFF);
…
sprintf (szOperatingSystem, "%s%s(Build %d)",
szOperatingSystem, osvi.szCSDVersion,
osvi.dwBuildNumber & 0xFFFF);
…
}
通過sprintf,szOperatingSystem字符串將自己打印到自己里面,這是十分危險的,將導(dǎo)致無法預(yù)知的錯誤結(jié)果,可能會導(dǎo)致棧溢出等嚴重問題。
[#12] Notepad++項目 – 數(shù)組局部clear
#define CONT_MAP_MAX 50
int _iContMap[CONT_MAP_MAX];
…
DockingManager::DockingManager()
{
…
memset(_iContMap, -1, CONT_MAP_MAX);
…
}
代碼的原本試圖將數(shù)組_iContMap清零,但memset的第三個參數(shù)CONT_MAP_MAX并不能代表數(shù)組的真正大小,而只是數(shù)組的元素個數(shù)而已,顯然其忘記乘以sizeof(int)了。
二、未定義行為
在C/C++的語言規(guī)范中,我們常常能看到“xx is undefined”。規(guī)范中并沒有明確表明這類錯誤是什么樣子的,只是說取決于Compiler的實現(xiàn),也許Compiler會給出正確的結(jié)果,但這么使用卻是不可移植的。
[#1] Chromium項目 – 智能指針的誤用
void AccessibleContainsAccessible(…)
{
…
auto_ptr<VARIANT> child_array(new VARIANT[child_count]);
…
}
這里的問題在于使用new[]分配的內(nèi)存,在智能指針釋放時卻用了delete,這將會導(dǎo)致未定義行為。看看autoptr的destructor就知道了:
~auto_ptr() {
delete _Myptr;
}
我們可以找一些更合適的類來fix這個問題,比如boost::scopedarray。
[#2] IPP Sample項目 – 經(jīng)典未定義行為
template<typename T, Ipp32s size> void HadamardFwdFast(…)
{
Ipp32s *pTemp;
…
for(j=0;j<4;j++) {
a[0] = pTemp[0*4] + pTemp[1*4];
a[1] = pTemp[0*4] – pTemp[1*4];
a[2] = pTemp[2*4] + pTemp[3*4];
a[3] = pTemp[2*4] – pTemp[3*4];
pTemp = pTemp++;
…
}
…
}
很多人一眼就看到了"pTemp = pTemp++"這行,對于這個代碼編譯器會產(chǎn)生兩種結(jié)果截然不同的翻譯:
pTemp = pTemp + 1;
pTemp = pTemp;
或
TMP = pTemp;
pTemp = pTemp + 1;
pTemp = TMP;
到底是哪種呢?依賴于編譯器的實現(xiàn),甚至是優(yōu)化級別的設(shè)定。
三、與運算優(yōu)先級相關(guān)的錯誤
[#1] MySQL工程 – !和&的運算優(yōu)先級
int ha_innobase::create(…)
{
…
if (srv_file_per_table
&& !mysqld_embedded
&& (!create_info->options & HA_LEX_CREATE_TMP_TABLE)) {
…
}
這段代碼原意是想測試create_info->options變量中幾個bit位的值是否set了,即!(create_info->options & HA_LEX_CREATE_TMP_TABLE),但由于!的運算優(yōu)先級高于&,實際邏輯變成了(!create_info->options) & HA_LEX_CREATE_TMP_TABLE了。如果想要這段代碼如期工作,就不要吝嗇小括號了。
[#2] Emule工程 – *和++的運算優(yōu)先級
STDMETHODIMP
CCustomAutoComplete::Next(…, ULONG *pceltFetched)
{
…
if (pceltFetched != NULL)
*pceltFetched++;
…
}
顯然作者原意是想對pceltFetched所指向的long型變量進行++操作,但由于*和++的運算優(yōu)先級沒有搞對,導(dǎo)致實際上執(zhí)行了*(pceltFetched++)的操作,而不是(*pceltFetched)++操作。
[#3] Chromium項目 – &和!=的運算優(yōu)先級
#define FILE_ATTRIBUTE_DIRECTORY 0×00000010
bool GetPlatformFileInfo(PlatformFile file, PlatformFileInfo* info) {
…
info->is_directory =
file_info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY != 0;
…
}
這個程序員的意圖是通過測試file_info.dwFileAttributes的幾個bit位的值來判定是否是目錄,邏輯上應(yīng)該是(file_info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0,但由于!=優(yōu)先級高于&,原代碼中無括號,結(jié)果邏輯變成了file_info.dwFileAttributes & (FILE_ATTRIBUTE_DIRECTORY != 0),導(dǎo)致is_directory將永遠求值為true。
[#4] BCmenu項目 – if和else弄混
void BCMenu::InsertSpaces(void)
{
if(IsLunaMenuStyle())
if(!xp_space_accelerators) return;
else
if(!original_space_accelerators) return;
…
}
這又是C語言的一個“大坑”,無奈這個BCMenu項目的程序員掉坑里了。雖然從代碼縮進上來看,else似乎是與最外層的if配對使用,但實際這段代碼的效果是:
if(IsLunaMenuStyle())
{
if(!xp_space_accelerators) {
return;
} else {
if(!original_space_accelerators) return;
}
}
這顯然不是程序員原意,看來括號必要時還是不能省略的。修改后的代碼如下:
if(IsLunaMenuStyle()) {
if(!xp_space_accelerators) return;
} else {
if(!original_space_accelerators) return;
}
四、格式化輸出錯誤
[#1] ReactOS項目 – 錯誤地輸出WCHAR字符
static void REGPROC_unescape_string(WCHAR* str)
{
…
default:
fprintf(stderr,
"Warning! Unrecognized escape sequence: \\%c'\n",
str[str_idx]);
…
}
%c是用來格式化輸出非寬字符的,這里用來輸出WCHAR顯然會得到錯誤的結(jié)果,fix solution是將%c換位%C。
[#2] Intel AMT SDK項目 – 缺少%s 【VS2010,運行可能會崩潰】
void addAttribute(…)
{
…
int index = _snprintf(temp, 1023,
"%02x%02x:%02x%02x:%02x%02x:%02x%02x:"
"%02x%02x:02x%02x:%02x%02x:%02x%02x",
value[0],value[1],value[2],value[3],value[4],
value[5],value[6],value[7],value[8],
value[9],value[10],value[11],value[12],
value[13],value[14],value[15]);
…
}
不解釋了,自己慢慢數(shù)和對照吧。
[#3] Intel AMT SDK項目 – 未使用的參數(shù)
bool GetUserValues(…)
{
…
printf("Error: illegal value. Aborting.\n", tmp);
return false;
}
顯然tmp是多余的。
五、書寫錯誤
[#1] Miranda IM項目 – 在if中賦值
void CIcqProto::handleUserOffline(BYTE *buf, WORD wLen)
{
…
else if (wTLVType = 0×29 && wTLVLen == sizeof(DWORD))
…
}
“wTLVType = 0×29”顯然是筆誤,應(yīng)該是“wTLVType == 0×29”才對。
[#3] Clang項目 – 對象名書寫錯誤
static Value *SimplifyICmpInst(…) {
…
case Instruction::Shl: {
bool NUW =
LBO->hasNoUnsignedWrap() && LBO->hasNoUnsignedWrap();
bool NSW =
LBO->hasNoSignedWrap() && RBO->hasNoSignedWrap();
…
}
從最后一行先后使用了LBO和RBO來看,前面只用了LBO的那行很可能是有問題的,正確的應(yīng)該是:
bool NUW =
LBO->hasNoUnsignedWrap() && RBO->hasNoUnsignedWrap();
[#6] G3D Content Pak項目 – 一對括號放錯了地方
bool Matrix4::operator==(const Matrix4& other) const {
if (memcmp(this, &other, sizeof(Matrix4) == 0)) {
return true;
}
…
}
由于括號放錯了地方,導(dǎo)致memcmp最后的參數(shù)變成了sizeof(Matrix4) == 0,這行代碼的正確寫法應(yīng)該是:
if (memcmp(this, &other, sizeof(Matrix4)) == 0) {
[#8] Apache Http Server項目 – 多余的sizeof
PSECURITY_ATTRIBUTES GetNullACL(void)
{
PSECURITY_ATTRIBUTES sa;
sa = (PSECURITY_ATTRIBUTES)
LocalAlloc(LPTR, sizeof(SECURITY_ATTRIBUTES));
sa->nLength = sizeof(sizeof(SECURITY_ATTRIBUTES));
…
}
最后一行顯然是筆誤,sizeof(sizeof(SECURITY_ATTRIBUTES))應(yīng)該寫為sizeof(SECURITY_ATTRIBUTES)才對。
[#10] Notepad++項目 – 在本來應(yīng)該用&的地方使用了&&
TCHAR GetASCII(WPARAM wParam, LPARAM lParam)
{
…
result=ToAscii(wParam,
(lParam >> 16) && 0xff, keys,&dwReturnedValue,0);
…
}
(lParam >> 16) && 0xff沒有什么意義,求值結(jié)果總是true。這里的代碼應(yīng)該是(lParam >> 16) & 0xff。
[#12] Fennec Media Project項目 – 額外的分號
int settings_default(void)
{
…
for(i=0; i<16; i++);
for(j=0; j<32; j++)
{
settings.conversion.equalizer_bands.boost[i][j] = 0.0;
settings.conversion.equalizer_bands.preamp[i] = 0.0;
}
}
這又是一個實際邏輯與代碼縮進不符的例子。作者的原意是這樣的:
for(i=0; i<16; i++)
{
for(j=0; j<32; j++)
{
settings.conversion.equalizer_bands.boost[i][j] = 0.0;
settings.conversion.equalizer_bands.preamp[i] = 0.0;
}
}
但實際執(zhí)行代碼邏輯卻是:
for(i=0; i<16; i++)
{
;
}
for(j=0; j<32; j++)
{
settings.conversion.equalizer_bands.boost[i][j] = 0.0;
settings.conversion.equalizer_bands.preamp[i] = 0.0;
}
這一切都是那個;導(dǎo)致的。
六、對基本函數(shù)和類的誤用
[#2] TortoiseSVN項目 – remove函數(shù)的誤用
STDMETHODIMP CShellExt::Initialize(….)
{
…
ignoredprops = UTF8ToWide(st.c_str());
// remove all escape chars ('\\')
std::remove(ignoredprops.begin(), ignoredprops.end(), '\\');
break;
…
}
作者意圖刪除所有'\\',但他用錯了函數(shù),remove函數(shù)只是交換元素的位置,將要刪除的元素交換到尾部trash,并且返回指向trash首地址的iterator。正確的做法應(yīng)該是"v.erase(remove(v.begin(), v.end(), 2), v.end())"。
[#5] Pixie項目 – 在循環(huán)中使用alloca函數(shù)
inline void triangulatePolygon(…) {
…
for (i=1;i<nloops;i++) {
…
do {
…
do {
…
CTriVertex *snVertex =
(CTriVertex *)alloca(2*sizeof(CTriVertex));
…
} while(dVertex != loops[0]);
…
} while(sVertex != loops[i]);
…
}
…
}
alloca函數(shù)在棧上分配內(nèi)存,因此在循環(huán)中使用alloca可能會很快導(dǎo)致棧溢出。
七、無意義的代碼
[#1] IPP Samples項目 – 不完整的條件
void lNormalizeVector_32f_P3IM(Ipp32f *vec[3],
Ipp32s* mask, Ipp32s len)
{
Ipp32s i;
Ipp32f norm;
for(i=0; i<len; i++) {
if(mask<0) continue;
norm = 1.0f/sqrt(vec[0][i]*vec[0][i]+
vec[1][i]*vec[1][i]+vec[2][i]*vec[2][i]);
vec[0][i] *= norm; vec[1][i] *= norm; vec[2][i] *= norm;
}
}
mask是Ipp32s類型指針,這樣if (mask< 0)這句代碼顯然沒啥意義,正確的代碼應(yīng)該是:
if (mask[i] < 0) continue;
[#2] QT項目 – 重復(fù)的檢查
Q3TextCustomItem* Q3TextDocument::parseTable(…)
{
…
while (end < length
&& !hasPrefix(doc, length, end, QLatin1String("</td"))
&& !hasPrefix(doc, length, end, QLatin1String("<td"))
&& !hasPrefix(doc, length, end, QLatin1String("</th"))
&& !hasPrefix(doc, length, end, QLatin1String("<th"))
&& !hasPrefix(doc, length, end, QLatin1String("<td"))
&& !hasPrefix(doc, length, end, QLatin1String("</tr"))
&& !hasPrefix(doc, length, end, QLatin1String("<tr"))
&& !hasPrefix(doc, length, end, QLatin1String("</table"))) {
…
}
這里對"<td"做了兩次check。
八、總是True或False的條件
[#1] Shareaza項目 – char類型的值范圍
void CRemote::Output(LPCTSTR pszName)
{
…
CHAR* pBytes = new CHAR[ nBytes ];
hFile.Read( pBytes, nBytes );
…
if ( nBytes > 3 && pBytes[0] == 0xEF &&
pBytes[1] == 0xBB && pBytes[2] == 0xBF )
{
pBytes += 3;
nBytes -= 3;
bBOM = true;
}
…
}
表達式"pBytes[0] == 0xEF"總是False。char類型的值范圍是-128~127 < 0xEF,因此這個表達式總是False,導(dǎo)致整個if condition總是為False,與預(yù)期邏輯不符。
[#3] VirtualDub項目 – 無符號類型總是>=0
typedef unsigned short wint_t;
…
void lexungetc(wint_t c) {
if (c < 0)
return;
g_backstack.push_back(c);
}
c是unsigned short類型,永遠不會小于0,也就是說if (c < 0)永遠為False。
[#8] MySQL項目 – 條件錯誤
enum enum_mysql_timestamp_type
str_to_datetime(…)
{
…
else if (str[0] != ‘a’ || str[0] != 'A')
continue; /* Not AM/PM */
…
}
if (str[0] != ‘a’ || str[0] != 'A')這個條件永遠為真。也許這塊本意是想用&&。
九、代碼漏洞
導(dǎo)致漏洞的代碼錯誤實際上也都是筆誤、不正確的條件以及不正確的數(shù)組操作等。但這里還是想將一些特定錯誤劃歸為一類,因為入侵者可以利用這些錯誤來攻擊你的代碼,獲取其利益。
[#1] Ultimate TCP/IP項目 – 空字符串的錯誤檢查
char *CUT_CramMd5::GetClientResponse(LPCSTR ServerChallenge)
{
…
if (m_szPassword != NULL)
{
…
if (m_szPassword != '\0')
{
…
}
第二個if condition check意圖檢查m_szPassword是否為空字符串,但卻錯誤的將指針與'\0'進行比較,正確的代碼應(yīng)該是這樣的:
if (*m_szPassword != '\0')
[#2] Chromium項目 – NULL指針的處理
bool ChromeFrameNPAPI::Invoke(…)
{
ChromeFrameNPAPI* plugin_instance =
ChromeFrameInstanceFromNPObject(header);
if (!plugin_instance &&
(plugin_instance->automation_client_.get()))
return false;
…
}
一旦plugin_instance為NULL,!plugin_instance為True,代碼對&&后面的子條件求值,引用plugin_instance將導(dǎo)致程序崩潰。正確的做法應(yīng)該是:
if (plugin_instance &&
(plugin_instance->automation_client_.get()))
return false;
[#5] Apache httpd Server項目 – 不完整的緩沖區(qū)clear
#define MEMSET_BZERO(p,l) memset((p), 0, (l))
void apr__SHA256_Final(…, SHA256_CTX* context) {
…
MEMSET_BZERO(context, sizeof(context));
…
}
這個錯誤前面提到過,sizeof(context)只是指針的大小,將之改為sizeof(*context)就OK了。
[#7] PNG Library項目 – 意外的指針clear
png_size_t
png_check_keyword(png_structp png_ptr, png_charp key,
png_charpp new_key)
{
…
if (key_len > 79)
{
png_warning(png_ptr, "keyword length must be 1 – 79 characters");
new_key[79] = '\0';
key_len = 79;
}
…
}
new_key的類型為png_charpp,顧名思義,這是一個char**類型,但代碼中new_key[79] = ‘\0′這句顯然是要給某個char賦值,但new_key[n]得到的應(yīng)該是一個地址,給一個地址賦值為’\0′顯然是有誤的。正確的寫法應(yīng)該是(*new_key)[79] = '\0'。
[#10] Miranda IM項目 – 保護沒生效
void Append( PCXSTR pszSrc, int nLength )
{
…
UINT nOldLength = GetLength();
if (nOldLength < 0)
{
// protects from underflow
nOldLength = 0;
}
…
}
nOldLength椒UINT類型,其值永遠不會小于0,因此if (nOldLength < 0)這行成了擺設(shè)。
[#12] Ultimate TCP/IP項目 – 不正確的循環(huán)結(jié)束條件
void CUT_StrMethods::RemoveSpaces(LPSTR szString) {
…
size_t loop, len = strlen(szString);
// Remove the trailing spaces
for(loop = (len-1); loop >= 0; loop–) {
if(szString[loop] != ' ')
break;
}
…
}
循環(huán)中的結(jié)束條件loop >= 0將永遠為True,因為loop變量的類型是size_t是unsigned類型,永遠不會小于0。
十、拷貝粘貼
和筆誤不同,程序員們決不因該低估拷貝粘貼問題,這類問題發(fā)生了太多。程序員們花費了大量時間在這些問題的debug上。
[#1] Fennec Media Project項目 – 處理數(shù)組元素時出錯
void* tag_write_setframe(char *tmem,
const char *tid, const string dstr)
{
…
if(lset)
{
fhead[11] = '\0';
fhead[12] = '\0';
fhead[13] = '\0';
fhead[13] = '\0';
}
…
}
咋看一下,fhead[13]做了兩次賦值,似乎沒啥問題。但仔細想一下,最后那行程序員的原意極可能是想寫fhead[14] = '\0'。問題就在這里了。
[#2] MySQL項目 – 處理數(shù)組元素時出錯
static int rr_cmp(uchar *a,uchar *b)
{
if (a[0] != b[0])
return (int) a[0] – (int) b[0];
if (a[1] != b[1])
return (int) a[1] – (int) b[1];
if (a[2] != b[2])
return (int) a[2] – (int) b[2];
if (a[3] != b[3])
return (int) a[3] – (int) b[3];
if (a[4] != b[4])
return (int) a[4] – (int) b[4];
if (a[5] != b[5])
return (int) a[1] – (int) b[5];
if (a[6] != b[6])
return (int) a[6] – (int) b[6];
return (int) a[7] – (int) b[7];
}
編寫這類代碼時,我猜絕大多數(shù)人會選擇Copy-Paste,然后再逐行修改,問題就發(fā)生在修改過程中,上面的代碼中當處理a[5] != b[5]時就忘記修改一個下標了:return (int) a[1] – (int) b[5];顯然這里的正確代碼應(yīng)該是return (int) a[5] – (int) b[5]。
[#3] TortoiseSVN項目 文件名不正確
BOOL GetImageHlpVersion(DWORD &dwMS, DWORD &dwLS)
{
return(GetInMemoryFileVersion(("DBGHELP.DLL"),
dwMS,
dwLS)) ;
}
BOOL GetDbgHelpVersion(DWORD &dwMS, DWORD &dwLS)
{
return(GetInMemoryFileVersion(("DBGHELP.DLL"),
dwMS,
dwLS)) ;
}
GetImageHlpVersion和GetDbgHelpVersion都使用了"DBGHELP.DLL"文件,顯然GetImageHlpVersion寫錯文件名了。應(yīng)該用"IMAGEHLP.DLL"就對了。
[#4] Clang項目 – 等同的函數(shù)體
MapTy PerPtrTopDown;
MapTy PerPtrBottomUp;
void clearBottomUpPointers() {
PerPtrTopDown.clear();
}
void clearTopDownPointers() {
PerPtrTopDown.clear();
}
我們看到雖然兩個函數(shù)名不同,但是函數(shù)體的內(nèi)容是相同的,顯然又是copy-paste惹的禍。做如下修改即可:
void clearBottomUpPointers() {
PerPtrBottomUp.clear();
}
十一、Null指針的校驗遲了
這里的“遲了”的含義是先使用指針,然后再校驗指針是否為NULL。
[#1] Quake-III-Arena項目 – 校驗遲了
void Item_Paint(itemDef_t *item) {
vec4_t red;
menuDef_t *parent = (menuDef_t*)item->parent;
red[0] = red[3] = 1;
red[1] = red[2] = 0;
if (item == NULL) {
return;
}
…
}
在校驗item是否為NULL前已經(jīng)使用過item了,一旦item真的為NULL,那程序必然崩潰。
十二、其他雜項
[#1] Image Processing 項目 – 八進制數(shù)
inline
void elxLuminocity(const PixelRGBus& iPixel,
LuminanceCell< PixelRGBus >& oCell)
{
oCell._luminance = uint16(0.2220f*iPixel._red +
0.7067f*iPixel._blue + 0.0713f*iPixel._green);
oCell._pixel = iPixel;
}
inline
void elxLuminocity(const PixelRGBi& iPixel,
LuminanceCell< PixelRGBi >& oCell)
{
oCell._luminance = 2220*iPixel._red +
7067*iPixel._blue + 0713*iPixel._green;
oCell._pixel = iPixel;
}
第二個函數(shù),程序員原意是使用713這個十進制整數(shù),但0713 != 713,在C中,0713是八進制的表示法,Compiler會認為這是個八進制數(shù)。
[#2] IPP Sample工程 – 一個變量用于兩個loop中
JERRCODE CJPEGDecoder::DecodeScanBaselineNI(void)
{
…
for(c = 0; c < m_scan_ncomps; c++)
{
block = m_block_buffer + (DCTSIZE2*m_nblock*(j+(i*m_numxMCU)));
// skip any relevant components
for(c = 0; c < m_ccomp[m_curr_comp_no].m_comp_no; c++)
{
block += (DCTSIZE2*m_ccomp
[/c]
.m_nblocks);
}
…
}
變量c用在了兩個loop中,這會導(dǎo)致只有部分數(shù)據(jù)被處理,或外部循環(huán)中止。
[#3] Notepad++項目 – 怪異的條件表達式
int Notepad_plus::getHtmlXmlEncoding(….) const
{
…
if (langT != L_XML && langT != L_HTML && langT == L_PHP)
return -1;
…
}
代碼中的那行if條件等價于 if (langT == L_PHP),顯然似乎不是作者原意,猜測正確的代碼應(yīng)該是這樣的:
int Notepad_plus::getHtmlXmlEncoding(….) const
{
…
if (langT != L_XML && langT != L_HTML && langT != L_PHP)
return -1;
…
}