1. 程式人生 > >C語言中易犯的BUG收集

C語言中易犯的BUG收集

是否遇到過寫C時邏輯正確,語法正確,但還是莫名其妙的出錯,可能是下面的原因造成的:

1.第二行會被當成註釋,原因是”在C中,“\” 代表此行沒有結束,於是,後面的程式碼也成了註釋。“

//    Microsoft's version of tmpfile() creates the file in C:\
   g = fname ? fopen(fname, "w+") : tmpfile();
2.漏打空格

第一行原本是想num除以* pInt,因為"/"與”*“沒空格,結果被當成註釋了。

float result = num/*pInt;
/*  some comments */
-x<10 ? f(result):f(-result);

-----------------------------------------------------------------------------------------------------

2012-03-13

1.     虛解構函式的使用(C++)

    考慮下面的情況:

        class Base

        {

        public:

            Base(){ cout << "Base()" << endl; }

            ~Base(){ cout << "~Base()" << endl; }

            virtual void print(){}

        };

        class Derived : public Base

        {

        public:

            Derived()

            {

                cout << "Derived()" << endl;

                m_point = new char[10];

            }

            ~Derived()

            {

                cout << "~Derived()" << endl;

                delete m_point;

            }

            void print(){ cout << m_point << endl; }

        private:

            char* m_point;

        };

        Base* p = new Derived;

        delete p;

按照預期,當delete指標p的時候,應先呼叫子類解構函式,再呼叫父類解構函式。但是由於此時p雖然指向一個子類物件,但其實際型別卻是父類指標。並且由於此時的解構函式並非虛解構函式,因此當直接對p做delete操作的時候,子類的解構函式並不會被呼叫,從而導致記憶體洩露。

所以,在需要利用到C++的多型性質時,不要忘記將基類的解構函式定義為虛解構函式。

2.     memset的使用(win32 API)

memset()一般用於資料的初始化。但是,在下面的情況下使用memset()可能會導致異常出現。

class Sample

        {

        public:

            Sample()

            {

                cout << "Sample()" << endl;

                m_point = new char[10];

            }

            ~Sample()

            {

                cout << "~Sample()" << endl;

                delete m_point;

            }

            void print(){ cout << m_point << endl; }

        private:

            char* m_point;

        };

        Sample ins;

     memset( &ins, 0, sizeof(ins) );

對於Sample型別的例項ins來說,其在建構函式中進行了記憶體申請。如果在構造完例項後對ins進行memset()初始化,會導致m_point被強制賦值為NULL,從而出現某些Sample類的操作abort或者記憶體洩露。

3.     類的拷貝建構函式(C++)

類的拷貝建構函式在什麼情況下應該重寫?看下面的例子:

class Sample

        {

        public:

            Sample()

            {

                cout << "Sample()" << endl;

                m_point = new char[10];

            }

            ~Sample()

            {

                cout << "~Sample()" << endl;

                delete m_point;

            }

            void print(){ cout << m_point << endl; }

        private:

            char* m_point;

        };

        Sample p1;

        Sample p2(p1);

    此時p2的m_point實際指向的記憶體和p1相同,因此當p1,p2在釋放時,會導致同一塊記憶體區域被delete兩次,從而abort。

4.     條件分支始終走到?(C++)

    這個問題一般由兩種粗心的原因導致:

①     判斷語句的“==”被誤寫成“=”

②     判斷語句後誤加“;”

5.     const用法總結(C++)

const出現的場合包括以下幾種:

①     宣告變數:變數為常量

②     宣告函式返回值:返回值為常量

③     宣告函式引數:函式引數在函式內部不可更改值

④     宣告函式本身:函式本身為類的成員函式,該函式不可更改類的成員變數值

const的修飾規則:

從左往右,觀察const右部:

const int a;      // 修飾int a,a值不可改變

int const a;      // 修飾a,a值不可改變

int const *a;     // 修飾*a,a指向的內容不可變,a本身可變

int* const a;     // 修飾a,a指向的內容可變,a本身不可變

6.     malloc和new的區別(C++)

對於類或結構體,new會呼叫建構函式,malloc不會。delete和 free同樣有這個區別。

7.     new和delete,new[]和delete[](C++)

最好匹配起來使用,new對應的一定是delete,new[]對應的則一定是delete[]。這是因為用new[]構造出來的物件如果用delete進行釋放可能會造成未完全釋放(只釋放第一個元素的空間),

8.     巨集定義與const(C++)

比較一下:

① #define DATA 1000

② const int DATA 1000

就作用而言,都表示了一個值為1000的常量。

對於①,它在程式中的作用就是在所有用到DATA的地方用1000這個值去替換,不佔用記憶體空間,編譯期完成替換工作。

對於②,它在程式中是一個實實在在的變數,但變數的值不可變。它會佔用記憶體空間。

因此,在使用②的時候,儘量在.cpp檔案中使用,.h檔案中使用的話應僅宣告,而在.cpp檔案中定義,以避免重複佔用空間。

9.     巨集定義與inline函式(C++)

比較一下:

① #define MAX( a, b ) (a)>(b)?(a):(b)

② inline int MAX( int a, int b ){ return a>b?a:b }

兩者同樣是對引用到的地方進行替換,但是巨集定義不會進行型別匹配。

10.  巨集定義(C++)

   #define MAX( a, b ) a*b

   乍看下去,上述的巨集定義是沒用問題的,計算a*b的值。但是由於巨集定義所作的僅僅是進行簡單的替換,所以該巨集定義在某些情況下會出現一些意想不到的問題。比如說下面的呼叫:MAX( 2+3, 1+1 )。理想中的結果應該是10,但實際結果是6,因為在替換後表示式變為:2+3*1+1。所以在書寫巨集定義的時候對於其引數需加()限定。

    #define MAX( a, b ) a*b è #define MAX( a, b ) (a)*(b)

11.  類中的引用型別變數和const成員變數(C++)

這兩類成員變數的初始化必須在類的初始化列表裡完成。

        class Sample

        {

        public:

            Sample():a(10),b(11),c(b)

            {

            }

        private:

            const int a;

            int  b;

            int& c;

        };

12.  template的編譯問題(STL)

template的宣告和定義一般均寫於同一個標頭檔案中,否則會發生連結錯誤

13.  iterater的使用(STL)

對於會引起iterater無效的函式,例如vector中的erase(),在使用後不能對原iterator進行直接操作,以避免abort現象。

14.  類模板的使用(STL)

任何時候使用到類模板都必須顯式標註上該類模板在當前應用下的引數型別。

15.  函式模板的使用(STL)

函式模板可以在使用的時候顯式標註上該函式模板在當前應用下的引數型別,也可以不進行標註由編譯器根據引數型別自行推導。

16.  野指標(資源管理)

產生野指標的原因有以下兩種:

①     使用未初始化的指標

②     使用指向的記憶體空間已被釋放後的指標

使用野指標會造成堆被損壞。

17.  堆損壞(資源管理)

堆損壞是由於非法對堆上資料進行寫入的操作引起的,可能是野指標上的操作,也可能是由於指標越界操作等原因造成。

沒有一個完美的方案解決堆損壞問題(除非換java或者c#實現…)。一個良好的程式設計習慣會大幅降低這個問題出現的頻率,例如指標必須進行初始化,指標在刪除後必須賦為NULL等等。

出現堆損壞的問題時,可以藉助工具進行檢查,也可以利用微軟提供的_CrtCheckMemory()函式進行排查。

18.  記憶體洩露(資源管理)

記憶體洩露是由於申請的空間沒有釋放造成的。最常見的原因是new得到的記憶體在之後忘記使用delete進行釋放。其他還有很多原因會造成記憶體洩露,例如執行緒,互斥鎖等核心物件在使用後沒有進行關閉同樣會造成記憶體洩露。

出現記憶體洩露之後可以利用工具進行檢查,也可以利用微軟提供的_CrtDumpMemoryLeaks()函式進行排查。

19.  new出的物件用free釋放(資源管理)

free()不會對所釋放的指標物件呼叫解構函式,因此可能會造成類成員變數的記憶體洩露。

20.  CString物件的記憶體洩露(資源管理)

使用Cstring物件的某些操作時,如GetBuffer(),如沒有進行相對應的ReleaseBuffer()操作會造成記憶體洩露。

21.  純虛擬函式(C++)

純虛擬函式是類中形式如下的函式:virtual function() = 0;無論純虛擬函式在基類中有無定義,繼承類如果需要例項化都必須實際定義出該純虛擬函式的實體,否則會造成編譯錯誤。

22.  標頭檔案的相互包含(Other)

有時會遇到這樣的問題:class A是class B的某一成員變數源型別,class B同時也是class A的某一成員變數源型別。這種情況造成的後果是包含class A的標頭檔案和包含class B的標頭檔案相互引用。為了解決這個問題,可以利用前向宣告解決。即在A或B的標頭檔案中先期宣告class B或者class A,而在.cpp中實際引用對應的標頭檔案。

但是,需要注意的是,此種解決方案只適用於指標型別(形如 A* member)的情況。如果該變數為非指標型別(形如A member),則必須要求有型別的實際定義。