天天看點

BCB常見錯誤和運作時異常

寫在前面:當你在百度、谷歌上都搜尋不到解決方案時,證明你的思路是錯誤的,應改變解決思路

1、unresolve external ''

 這些錯誤是由于: (1)工程中沒有包含相應的cpp檔案;(2).h中有函數聲明,.cpp中沒有函數實作;(3)類的CPP檔案中,成員函數沒有在方法名前加類名;(4)類靜态成員變量沒有在cpp檔案中初始化;(5)這種情況出現在命名空間中,即.h檔案中聲明函數,并把函數放入命名空間中,但是.cpp檔案中沒有把函數放入命名空間中。(6)如果程式中使用了其他的.lib檔案,那麼這些編譯生成這些.lib文 件的Runtime Library和程式應該是相同的,即都是/MT(/MTd)或都是/MD(/MDd)。如果不同,極有可能會出現這種連結錯誤,參考:https://blog.csdn.net/socrates/article/details/3704929和https://qimo601.iteye.com/blog/1550348,因為.lib和exe分别使用了兩個不同類型運作時庫的相同簽名的函數(/MT和/MD)。

2、[C++ Error] QueryCommon.cpp(1273): E2094 'operator+' not implemented in type 'Property' for arguments of type 'const char *'

由const辨別符引起的,可能在Propery前加了const,然後程式中對Propery對象使用了下标操作符

3、[C++ Warning] Unit1.cpp(232): W8037 Non-const function ebank::Row::operator [](const std::string &) called for const object

由于對const Row對象使用了下标運算符,而下标運算符有可能改變Row對象的值

4、[C++ Error] algorithm(30): E2093 'operator==' not implemented in type 'string' for arguments of the same type

這是由于沒有加入相應的頭檔案,或者頭檔案在#include <algorithm>語句之後加入的模闆函數的定義要放在頭檔案中,詳見”C++ Primer 3rd Edition 中文版.pdf    10.5.“

5、raised exception class EOSError with message 'System Error. Code: 1400. 無效的視窗句柄。'.這是由于在子線程中操作了vcl元件,而vcl元件不是線程安全的,隻能在主線程中操作。

6、運作時異常:xxx raised exception class EAccessViolation with message 'Access violation at address xxxx in module 'rtl140.bpl.' Read of adrress xxxx.

這種異常一般是由于寫入或讀取了不可通路的記憶體,比如數組越界讀寫、引用的指針為空等;還有一種情況是exe與dll通信的時候,對方釋放了不是自己申請的記憶體(即exe釋放了dll中申請的記憶體或者dll釋放了exe申請的記憶體),究其原因是由于dll和exe各自有自己的一套記憶體管理機制造成的(使用的堆不同),dll申請的記憶體必須由相應的dll釋放,反之亦然;而且這種錯誤非常難跟蹤,出現AV的地方并不是根源所在。

7、釋出程式(生産獨立運作的exe)

要生成可獨立運作的exe,方法如下:

1)c++ builder 2009中,打開Project>>Options

2)Diretories and Conditionals中選擇Base

3)C++ Linker中獎Dynamic RTL設定為False(預設為true)

4)Packages中去掉Build with runtime packages前面的勾(預設勾選)

5)可以在指令行使用tdump xxx.exe > a.txt把可執行程式依賴的庫資訊輸出到文本檔案中

6) 動态庫程式也要如上設定,否則如果動态庫有依賴庫的時候LoadLibrary就會失敗,用GetLastError函數擷取錯誤代碼為126,意思是沒有找到指定的子產品(其實是沒有找到要加載的動态庫依賴的動态庫)。

8、在多線程進行中,最好不要使用局部靜态變量,因為當一個線程修改了此靜态局部變量後,另一個線程如果再讀取此靜态局部變量的值可能不是它想要的,因為這個值是被另一個線程修改的。

9、在更新IDE的時候(比如從BCB6到BCB2010),重新編譯工程時可能會碰到:

[ILINK32 Error] Error: Unresolved external 'wWinMain' referenced from D:\PROGRAM FILES\EMBARCADERO\RAD STUDIO\7.0\LIB\C0W32W.OBJ

這個時候在工程.cpp檔案中加入#include <tchar.h>即可.

10、在使用std::list進行資料存儲時,不要如下順序操作list

        list->remove(*it);

        delete *it;

這樣有可能會發生access violation異常,初步分析可能是因為remove之後,it中的内容就被釋放了,it指向的記憶體是無法預料的。要如下順序寫:

        delete *it;

        list->remove(*it);

11、在子線程中使用com時需要在子線程中初始化com(CoInitializeEx),而且要和CoUninitialize成對出現(詳見CoInitializeEx幫助文檔)。而且com對象的建立和銷毀都要放在子線程中(在CoInitializeEx和CoUninitialize之間建立和銷毀),否則可能會出現ACCESS VIOLATION異常,而且這種異常沒有規律可言,有時出現有時不出現,出現時BCB的IDE也捕獲不了(而且在XP下經常出現,在win7下不經常出現)。特别是在使用智能指針的時候要特别注意,因為智能指針的銷毀隻有在出了智能指針的作用域才會進行,而這個時候通常已經執行了CoUninitialize,這樣也會報ACCESS violation異常,是以這個時候要用大括号“{”把智能指針的作用域括起來,這樣當智能指針出了這個大括号的作用域時就會銷毀,例如:

int _tmain(int argc, _TCHAR* argv[])
{
    DWORD dwCoInit =0;
    CoInitializeEx(NULL, dwCoInit);
    {
        Excel::_ApplicationPtr pExcel;
        pExcel.CreateInstance(_T("Excel.Application"));
        Excel::_WorkbookPtr pBook;
        pBook = pExcel->Workbooks->Open("c:\\test.xls", varOption, varOption, varOption, varOption, varOption, varOption, varOption, varOption, varOption, varOption, varOption, varOption);
        Excel::_WorksheetPtr pSheet = pBook->Sheets->Item[1];
       Excel::RangePtr pRange = pSheet->GetRange(_bstr_t(_T("A1")));
        _variant_t vItem = pRange->Value2;
        printf(_bstr_t(vItem.bstrVal));    
        pBook->Close(VARIANT_FALSE);
        pExcel->Quit();
    }
    CoUninitialize();
    return0;}
這裡pExcel和pBook就是智能指針,用大括号括起來後放入CoInitializeEx(NULL, dwCoInit)與CoUninitialize();之間。

 12、new char(size) 與 new char[size],前者申請一個位元組大小的堆記憶體空間,且此位元組内容為size;後者申請一個大小為size個位元組的連續堆記憶體空間,内容未知。很多人如果在代碼中如果由于失誤,申請數組時使用了前一個,那麼在使用這個記憶體位址時大部分情況下可能不會出錯,特别是在記憶體比較充足的情況下,因為第一個傳回的位址開始的幾個連續位元組可能都沒有被作業系統和其他應用程式使用,是以使用時沒有出問題,這樣就造成一種錯覺,好像new char(size)也能申請size個位元組大小的記憶體空間,其實不然。當記憶體比較緊俏或者系統從堆中頻繁申請記憶體時,就可能出現access violation或者invalid pointer operation等等記憶體錯誤,且這種錯誤都是運作時異常且不易被跟蹤。
13、STL中的容器按存儲方式分為兩類,一類是按以數組形式存儲的容器(如:vector 、deque);另一類是以不連續的節點形式存儲的容器(如:list、set、map)。在使用erase方法來删除元素時,需要注意一些問題。在使用 list、set 或 map周遊删除某些元素時可以這樣使用:
           

正确使用方法1

std::list< int> List;   
std::list< int>::iterator itList;
for( itList = List.begin(); itList != List.end(); )
{         
	if( WillDelete( *itList) )       
	{        
		itList = List.erase( itList);    
	}    
	else        
		itList++;
}
           

正确使用方法2

std::list< int> List;
std::list< int>::iterator itList;
for( itList = List.begin(); itList != List.end(); )
{           
	if( WillDelete( *itList) )    
	{            
		List.erase( itList++);    
	}        
	else        
		itList++;
}
           

其中List.erase(itList++);相當于以下語句:

std::list<int>::iterator tmp = itList;

++itList;

List.erase(itList++);

即先将itList傳給erase的參數,然後再進行++運算,最後執行erase函數。

下面是錯誤使用方法。

錯誤使用方法1

std::list< int> List;
std::list< int>::iterator itList;
for( itList = List.begin(); itList != List.end(); itList++)
{     
    if( WillDelete( *itList) )
    {       
       List.erase( itList);
    }  
}
           

上面執行過erase後,itList指向的記憶體已不可通路,再執行for語句的++操作時,會造成AV異常。

錯誤使用方法2

std::list< int> List;
std::list< int>::iterator itList;
for( itList = List.begin(); itList != List.end(); )
{          
    if( WillDelete( *itList) )
    {             
        itList = List.erase( ++itList);
    }     
    else    
        itList++;    
}
           
在使用 vector、deque周遊删除元素時,也可以通過erase的傳回值來擷取下一個元素的位置:
           

正确使用方法

std::vector< int> Vec;
std::vector< int>::iterator itVec;
for( itVec = Vec.begin(); itVec != Vec.end(); )
{ 
    if( WillDelete( *itVec) )  
    {      
        itVec = Vec.erase( itVec); 
    }    
    else    
        itList++; 
}
           

注意:vector、deque 不能像上面的“正确使用方法2”的辦法來周遊删除,即數組容器不能使用erase(itVec++)或erase(++itVec)删除元素,因為數組删除過中間某個元素後,會引起後面元素的前移,指向後面元素的疊代器就變得不可用,解引用指向後面元素的疊代器時會引起AV異常或者容器内部的斷言異常(一般為斷言異常)。

14、Application->Terminate();在有子線程運作時并不能立刻使程式退出,是以在需要立刻退出的時候需要使用下面的函數:
void __fastcall TerminateSelf()
{
    HANDLE hself = GetCurrentProcess();
       TerminateProcess(hself, 0);
}
或者先強制退出子線程,再用Application->Terminate();例如:
HANDLE hThd = ...
   TerminateThread(hThd ,1);//強制結束線程
Application->Terminate();

15. 在使用STL容器的疊代時,必須如下這樣使用:
std::list<string>iterator it = list.begin();
即在BCB環境下std辨別不能省略,即使你使用了:using namespace std;在vc環境下可以省略。 

16.[BCC32 Error] Main.cpp(4086): E2357 Reference initialized with 'string', needs lvalue of type 'string'
此編譯錯誤是因為在BCB下引用必須用變量初始化,而不能用臨時變量初始化,在VC下卻可以,如下所示:
string LTrim(string& str)
{
    string::size_type pos = str.find_first_not_of(" \n\r\t");
    if (pos != string::npos)
    {
        str = str.substr(pos);
    }
    return str;
}
string RTrim(string& str)
{
    string::size_type pos = str.find_last_not_of(" \n\r\t");
    if (pos != string::npos)
    {
        str = str.substr(0,pos+1);
    }
    return str;
}
string trim(string& str)
{
    string tmp = RTrim(str);
    return LTrim(tmp);
//    return LTrim(RTrim(str));//在vc下可用
} 

           

17、在使用TDataSet的時候,DisableControls和EnableControls函數必須配對使用,不能隻使用一個,否則可能會報運作時異常:Dataset not in edit or insert mode。而且就算沒有報異常,軟體運作也會出現奇怪的現象,比如Grid中該顯示的沒有顯示等等奇怪行為,如下所示:

void __fastcall TDumpFrm::RefreshUpGrid()
{    
	DataSetUp->DisableControls();
    DataSetUp->EmptyDataSet();
    WIN32_FIND_DATA find;
    String sWildcardFile = m_dumpJLPath + "*.Fmt";
    HANDLE hSearch = ::FindFirstFile(sWildcardFile.c_str(), &find);
    if (hSearch == INVALID_HANDLE_VALUE)
    {   
		//不要忘記在return分支中調用EnableControls   
		//(即DisableControls一定要與EnableControls配套使用),
		//非常重要!
		DataSetUp->EnableControls();
		return;
    }
    int valueSize = DataSetUp->FieldCount - 1;
    do
    {
		String fileName = find.cFileName;
        if (find.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
        {
			continue;
		}
        TStringList* list = Split(fileName, '_');
        if (list->Count < 4)
        {
			delete list;
            continue;
		}
        String dateTime = list->Strings[0];
        String operid = list->Strings[1];
        String drvid = list->Strings[2];
        String trainNum = list->Strings[3];
        delete list;
        TVarRec row[] = { false, fileName, find.nFileSizeLow };
        DataSetUp->AppendRecord(row, valueSize);
        Application->ProcessMessages();
	}while (::FindNextFile(hSearch, &find));
    FindClose(hSearch);
    if (DataSetUp->RecordCount > 0)
    {
		DataSetUp->RecNo = 1;
	}
    DataSetUp->EnableControls();
}
           
18、E2034 Cannot convert 'unsigned long (__stdcall * (_closure )(void *))(void *)' to 'unsigned long (__stdcall *)(void *)'
 此編譯錯誤是在建立線程的時候,線程函數使用的是類成員函數,但聲明的時候沒有加static辨別符。
19、使用__int64類型時一定要注意,一定要使用顯示強制轉換,盡量不要預設類型強制轉換,如下所示:
UINT32 value32 = ...;
__int64 value = (__int64)value32;//正确
__int64 value64 = value32;//錯誤,轉換後的結果可能為負數
以下也是錯誤的用法:
UINT32 dataLen = 1863;
__int64 offset = -dataLen;
或者
__int64 offset = 0 - dataLen;
此時的offset值并不是-1863,而是一個很大的值,這個比較奇怪,轉換為int都不會出錯;若要轉換,可以使用如下方法:
__int64 offset = 0;
offset -= dataLen;
此時的offset為-1863。 
20、在一個檔案中(UpManager.h)定義枚舉類型 enum RET{OK,NETERR,LOGICERR}; 
編譯的時候會報如下錯誤:
[BCC32 Error] UpManager.h(12): E2184 Enum syntax error
[BCC32 Error] UpManager.h(12): E2040 Declaration terminated incorrectly
[BCC32 Error] UpManager.h(12): E2190 Unexpected }
錯誤指向的地方是枚舉類型定義的地方,僅僅從錯誤資訊你看不出哪兒有問題,好像枚舉類型定義也沒有出錯,怎麼就報錯誤呢?!仔細分析發現,在另外一個頭檔案名中有預定義:#define NETERR             WM_USER + 100 //網絡錯誤
是由于預定義與枚舉定義的成員沖突導緻上述錯誤,但這種錯誤極難發現,隻有根據經驗判斷!
附注:在枚舉類型中定義的常量,屬于定義枚舉的作用域,而不屬于這個枚舉類型。即enum所定義的類型不具備名字空間限定能力(因為不屬于類類型),其所定義的常量子具備和enum類型所在名字空間相同的可見性,由于自身沒有名字限定能力,是以會出現名字沖突現象。
21、在使用消息機制給主界面發送消息時要注意,主界面銷毀後不要再發消息,否則可能會報AV異常。這種情況主要發生在以下情形:主界面建立子線程,子線程中需要給主界面發送消息,當主界面退出銷毀後,子線程還在運作,這個時候子線程再給主界面發送消息時,主界面的視窗句柄都是無效的,是以會發生AV異常。 
           

22、使用ShellExecute函數時應注意,當調用的exe所在檔案夾與調用者不在同一個檔案夾時,應傳遞exe所在的路徑給參數lpDirectory,否則exe不會從它所在的目錄下查找自己需要的資源檔案,而是從宿主程式所在的目錄下查找資源檔案。

23、使用c++builder制作dll時,連結錯誤:[ILINK32 Error] Error: Unresolved external '__fastcall Strhlpr::UnicodeFree(System::UnicodeString&)' referenced from C:\PROGRAM FILES\CODEGEAR\RAD STUDIO\6.0\LIB\DEBUG\VCLE.LIB|ustring這是由于制作動态庫時,沒有選中VCL動态庫,而代碼中使用了VCL類庫所緻。解決方案:(1)去除代碼中的VCL類,比如AnsiString、UnicodeString等;(2)使用VCL格式的動态庫