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

C++常見問題小結(http://www.programup.com/article/80.html

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),則必須要求有類型的實際定義。





發佈了22 篇原創文章 · 獲贊 3 · 訪問量 9萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章