C语言中易犯的BUG收集

5 篇文章 0 订阅

是否遇到过写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),则必须要求有类型的实际定义。





  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值