[轉]C,C++開源專案中的100個Bugs

weixin_33941350發表於2013-04-19

http://tonybai.com/2013/04/10/100-bugs-in-c-cpp-opensource-projects/

俄羅斯OOO Program Verification Systems公司用自己的靜態原始碼分析產品PVS-Studio對一些知名的C/C++開源專案,諸如Apache Http ServerChromiumClangCMakeMySQL等的原始碼進行了分析,找出了100個典型的Bugs。 個人覺得這份列表對C/C++ 程式設計師有一定參考意義。與其說事後用靜態工具分析,倒不如在編碼時就提高自知自覺,避免這份列表上的錯誤發生在你的程式碼中,因此這裡將部分摘錄一些 Bugs(Bug編號這裡不連續,為的是對應原文的編號)並做簡要說明。原文將這份Bug列表分為了幾類,這裡也將沿用這個思路。

一、陣列和字串處理錯誤

陣列和字串處理錯誤是C/C++程式中最多的一類缺陷型別。這也可以看作是我們為擁有高效地底層記憶體操作能力而付出的代價。

[#1] Wolfenstein 3D專案 -"只有部分物件被clear了"

void CG_RegisterItemVisuals( int itemNum ) {
    …
    itemInfo_t *itemInfo;
    …
    memset( itemInfo, 0, sizeof( &itemInfo ) );
    …
}

這裡的Bug出現在memset那一行。程式碼的真實意圖是clear iteminfo這塊記憶體,但呼叫memset時,第三個引數傳入的卻是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出現在memcpy一行。程式的原意是將clear src[3][3]這個二維陣列。但這裡有個坑:那就是作為函式形式引數的陣列名已經退化為指標了,對其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呼叫中,程式碼將一個指標的size傳入strncmp作為第三個引數,導致 strncmp實際只是比較了字串的前4 or 8個位元組,而不是字串的全部內容。

[#6] CPU Identifying Tool專案 – 陣列越界

#define FINDBUFFLEN 64  // Max buffer find/replace size

int WINAPI Sticky (…)
{
    …
    static char findWhat[FINDBUFFLEN] = {'\0'};
    …
    findWhat[FINDBUFFLEN] = '\0';
    …
}

bug出在"findWhat[FINDBUFFLEN] = ‘\0′;”這一行。陣列的最大長度為FINDBUFFLEN,但下標的最大值應該是FINDBUFFLEN-1,而不是FINDBUFFLEN。因此這 行程式碼顯然應該改為findWhat[FINDBUFFLEN-1] = '\0';

[#7] Wolfenstein 3D專案 – 陣列越界

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 )]"這行的結果值已經超出了陣列的最大邊界,正確的程式碼是:

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個位元組(依Compiler使用的字符集編碼而定),因此這裡只 copy 7個位元組顯然是不夠的,應該是7 * sizeof(wchar_t)。

[#9] CMake專案 – 迴圈內的陣列越界

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迴圈的次數等於陣列的元素個數,但sizeof(doserrors)返回的卻是陣列佔用的位元組個數,這遠遠大於陣列元素個數,因此造成陣列越界。正確的寫法:

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字串將自己列印到自己裡面,這是十分危險的,將導致無法預知的錯誤結果,可能會導致棧溢位等嚴重問題。

[#12] Notepad++專案 – 陣列區域性clear

#define CONT_MAP_MAX 50
int _iContMap[CONT_MAP_MAX];

DockingManager::DockingManager()
{
    …
    memset(_iContMap, -1, CONT_MAP_MAX);
    …
}

程式碼的原本試圖將陣列_iContMap清零,但memset的第三個引數CONT_MAP_MAX並不能代表陣列的真正大小,而只是陣列的元素個數而已,顯然其忘記乘以sizeof(int)了。

二、未定義行為

在C/C++的語言規範中,我們常常能看到“xx is undefined”。規範中並沒有明確表明這類錯誤是什麼樣子的,只是說取決於Compiler的實現,也許Compiler會給出正確的結果,但這麼使用卻是不可移植的。

[#1] Chromium專案 – 智慧指標的誤用

void AccessibleContainsAccessible(…)
{
    …
    auto_ptr<VARIANT> child_array(new VARIANT[child_count]);
    …
}

這裡的問題在於使用new[]分配的記憶體,在智慧指標釋放時卻用了delete,這將會導致未定義行為。看看autoptr的destructor就知道了:

~auto_ptr() {
    delete _Myptr;
}

我們可以找一些更合適的類來fix這個問題,比如boost::scopedarray。

[#2] IPP Sample專案 – 經典未定義行為

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++"這行,對於這個程式碼編譯器會產生兩種結果截然不同的翻譯:

pTemp = pTemp + 1;
pTemp = pTemp;

TMP = pTemp;
pTemp = pTemp + 1;
pTemp = TMP;

到底是哪種呢?依賴於編譯器的實現,甚至是優化級別的設定。

三、與運算優先順序相關的錯誤

[#1] MySQL工程 – !和&的運算優先順序

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),但由於!的運算優先順序高於&,實際邏輯變成了 (!create_info->options) & HA_LEX_CREATE_TMP_TABLE了。如果想要這段程式碼如期工作,就不要吝嗇小括號了。

[#2] Emule工程 – *和++的運算優先順序

STDMETHODIMP
CCustomAutoComplete::Next(…, ULONG *pceltFetched)
{
  …
  if (pceltFetched != NULL)
    *pceltFetched++;
  …
}

顯然作者原意是想對pceltFetched所指向的long型變數進行++操作,但由於*和++的運算優先順序沒有搞對,導致實際上執行了*(pceltFetched++)的操作,而不是(*pceltFetched)++操作。

[#3] Chromium專案 – &和!=的運算優先順序

#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位的值來判定是否是目錄,邏輯上應該是(file_info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0,但由於!=優先順序高於&,原始碼中無括號,結果邏輯變成了file_info.dwFileAttributes & (FILE_ATTRIBUTE_DIRECTORY != 0),導致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顯然會得到錯誤的結果,fix solution是將%c換位%C。

[#2] Intel AMT SDK專案 – 缺少%s

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]);
  …
}

 

不解釋了,自己慢慢數和對照吧。

[#3] Intel AMT SDK專案 – 未使用的引數

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”顯然是筆誤,應該是“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的那行很可能是有問題的,正確的應該是:

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;
  }
  …
}

由於括號放錯了地方,導致memcmp最後的引數變成了sizeof(Matrix4) == 0,這行程式碼的正確寫法應該是:

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))應該寫為sizeof(SECURITY_ATTRIBUTES)才對。

[#10] Notepad++專案 – 在本來應該用&的地方使用了&&

TCHAR GetASCII(WPARAM wParam, LPARAM lParam)
{
  …
  result=ToAscii(wParam,
                 (lParam >> 16) && 0xff, keys,&dwReturnedValue,0);
  …
}

(lParam >> 16) && 0xff沒有什麼意義,求值結果總是true。這裡的程式碼應該是(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;
    }
}

但實際執行程式碼邏輯卻是:

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;
}

這一切都是那個;導致的。

六、對基本函式和類的誤用

[#2] TortoiseSVN專案 – remove函式的誤用

STDMETHODIMP CShellExt::Initialize(….)
{
  …
  ignoredprops = UTF8ToWide(st.c_str());
  // remove all escape chars ('\\')
  std::remove(ignoredprops.begin(), ignoredprops.end(), '\\');
  break;
  …
}

作者意圖刪除所有'\\',但他用錯了函式,remove函式只是交換元素的位置,將要刪除的 元素交換到尾部trash,並且返回指向trash首地址的iterator。正確的做法應該是"v.erase(remove(v.begin(), v.end(), 2), v.end())"。

[#5] Pixie專案 – 在迴圈中使用alloca函式

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函式在棧上分配記憶體,因此在迴圈中使用alloca可能會很快導致棧溢位。

七、無意義的程式碼

[#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)這句程式碼顯然沒啥意義,正確的程式碼應該是:

if (mask[i] < 0) continue;

[#2] QT專案 – 重複的檢查

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,導致整個if condition總是為False,與預期邏輯不符。

[#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')這個條件永遠為真。也許這塊本意是想用&&。

九、程式碼漏洞

導致漏洞的程式碼錯誤實際上也都是筆誤、不正確的條件以及不正確的陣列操作等。但這裡還是想將一些特定錯誤劃歸為一類,因為入侵者可以利用這些錯誤來攻擊你的程式碼,獲取其利益。

[#1] Ultimate TCP/IP專案 – 空字串的錯誤檢查

char *CUT_CramMd5::GetClientResponse(LPCSTR ServerChallenge)
{
  …
  if (m_szPassword != NULL)
  {
    …
    if (m_szPassword != '\0')
    {
  …
}

第二個if condition check意圖檢查m_szPassword是否為空字串,但卻錯誤的將指標與'\0'進行比較,正確的程式碼應該是這樣的:

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將導致程式崩潰。正確的做法應該是:

if (plugin_instance &&
        (plugin_instance->automation_client_.get()))
  return false;

[#5] Apache httpd Server專案 – 不完整的緩衝區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]得到的應該是一個地址,給一個地址賦值為’\0′顯然是有誤的。正確的寫法應該是 (*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)這行成了擺設。

[#12] Ultimate TCP/IP專案 – 不正確的迴圈結束條件

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;
  }
  …
}

迴圈中的結束條件loop >= 0將永遠為True,因為loop變數的型別是size_t是unsigned型別,永遠不會小於0。

十、拷貝貼上

和筆誤不同,程式設計師們決不因該低估拷貝貼上問題,這類問題發生了太多。程式設計師們花費了大量時間在這些問題的debug上。

[#1] Fennec Media Project專案 – 處理陣列元素時出錯

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專案 – 處理陣列元素時出錯

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];
}

 

編寫這類程式碼時,我猜絕大多數人會選擇Copy-Paste,然後再逐行修改,問題就發生在修 改過程中,上面的程式碼中當處理a[5] != b[5]時就忘記修改一個下標了:return (int) a[1] – (int) b[5];顯然這裡的正確程式碼應該是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寫錯檔名了。應該用"IMAGEHLP.DLL"就對了。

[#4] Clang專案 – 等同的函式體

MapTy PerPtrTopDown;
MapTy PerPtrBottomUp;

void clearBottomUpPointers() {
  PerPtrTopDown.clear();
}

void clearTopDownPointers() {
  PerPtrTopDown.clear();
}

我們看到雖然兩個函式名不同,但是函式體的內容是相同的,顯然又是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前已經使用過item了,一旦item真的為NULL,那程式必然崩潰。

十二、其他雜項

[#1] Image Processing 專案 – 八進位制數

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;
}

第二個函式,程式設計師原意是使用713這個十進位制整數,但0713 != 713,在C中,0713是八進位制的表示法,Compiler會認為這是個八進位制數。

[#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中,這會導致只有部分資料被處理,或外部迴圈中止。

[#3] Notepad++專案 – 怪異的條件表示式

int Notepad_plus::getHtmlXmlEncoding(….) const
{
  …
  if (langT != L_XML && langT != L_HTML && langT == L_PHP)
    return -1;
  …
}

程式碼中的那行if條件等價於 if (langT == L_PHP),顯然似乎不是作者原意,猜測正確的程式碼應該是這樣的:

int Notepad_plus::getHtmlXmlEncoding(….) const
{
  …
  if (langT != L_XML && langT != L_HTML && langT != L_PHP)
    return -1;
  …
}

© 2013, bigwhite. 版權所有.

Related posts:

    1. 也談C語言的Struct Hack
    2. 簡析指標與多維陣列
    3. 也談指標運算
    4. 使用svn pre-commit hook
    5. Transaction模式的C實現

相關文章