2011-05-11 33 views
1

我无法找到我编写的下面的代码中的错误[虽然没有任何用途]。 只有在派生类时才给出SEG FAULT的代码!

 

#include < iostream > 
#include < cstdlib > 

using namespace std; 


class Base{ 
public: 
     Base(){cout << "Base class constructor" << endl;} 
     void funv() {}; 
     ~Base(){cout << "Base class destructor" << endl;} ; 
}; 

class Derived:public Base{ 
public: 
     char *ch; 
     Derived():ch(new char[6]()){} 
     ~Derived(){ 
       cout << "before" << endl; 
       delete [] ch; 
       ch = NULL; 
       cout << "after" << endl; 
     } 
}; 

int main(){ 

     Derived * ptr = new Derived; 

     //memcpy(ptr -> ch,"ar\0",4); // Works when class Derived is derved from base and also when not derived from base 

     ptr -> ch = const_cast < char* >("ar0"); // Works only when class Derived is not derived from class Base 

     cout << ptr -> ch[1] << endl; 

     ptr -> funv(); 

     delete ptr; 

     return 0; 
} 

 

我评论的代码行嫌上。

我使用的Sun Studio 12

+0

-1:不知道为什么你认为这将是安全的。 – 2014-07-07 17:21:03

+0

@LightnessRacesinOrbit:是的,在问了这个问题超过3年后,即使我现在不知道为什么我认为它会安全:)。感谢您使它注意到 – Arunmu 2014-07-07 18:49:50

回答

2

这是一个未定义行为。无论你是否得到它,它都会导致问题。 当分配const char*char*象下面这样:

ptr -> ch = const_cast < char* >("ar0"); 

这意味着,要分配其在非堆段(主要是在数据段)中所定义的字符串。应该只有delete内存,它被分配在堆段上。

另外,上面的赋值语句被执行,它会在之前泄漏内存指向ch。为了避免这样的问题的一个方法是只要你尝试ch分配一些变量声明为,

private: char* const ch; 

,它会给编译错误。所以它会让你编写包装来分配ch,在那里你可以照顾取消分配。

+0

是的。你是对的。但是当我不是从基类派生出来的时候,为什么我没有得到seg错误?或者是由于UB我没有收到任何seg故障? – Arunmu 2011-05-11 05:55:30

+0

是的,这是由于未定义的行为。在其他编译器中可能会出现段错误。但它是一个纯粹的UB。 – iammilind 2011-05-11 05:57:54

-1
memcpy(ptr -> ch,"ar\0",4); 

memcpy copys从源缓冲区的内容,目标缓冲区指针ptr->ch点。

ptr -> ch = const_cast < char* >("ar0"); 

您将重新分配值给指针本身。这很危险,因为您无法再获取堆上的原始缓冲区。这是内存泄漏。另外,在你的析构函数中你删除了这个指针,它现在并不指向堆上的缓冲区。它没有定义。

+0

不留言评论是非常专业的。没有理由? – 2011-05-11 06:00:12

+0

我一次只执行一项操作。不能同时进行。对于测试puposes我留下两个陈述之一评论。 – Arunmu 2011-05-11 06:03:32

+0

@Eric,我认为有人可能因为你对'memcpy'的引用而被拒绝了;该行在代码中被注释;所以它没有任何关联。我建议,你应该从你的答案中删除该行。 – iammilind 2011-05-11 06:05:25

1

我认为你对理解Undefine Behavior的含义有严重的问题。

未定义行为并不一定意味着一个段错误

遗憾的呼喊,但是这是一个非常importan点。

未定义的行为意味着,如名称所示,“未定义”。这意味着你不知道会发生什么。实际上,段错是你可以期待的最好的事情......但不幸的是,90%的UB在C++中只是沉默。

该应用程序将正常运行,甚至会给你你期望的结果。一切都会好起来的......直到当天演示当天,在百人面前,应用程序将非常糟糕地崩溃,只是为了让你在YouTube上眩晕的表情。

未定义的行为是通过实验学习C或C++是一个坏主意的原因之一。当你在C++中犯错误时,语言不会帮助你......沉默的假设基本上是你不会犯任何错误......如果你正在学习语言,这当然是一个非常困难的要求。

未定义的行为也是在C或C++编写时,在测试套件中放置太多希望也是一个坏主意的原因。在这些语言中(以及UB存在的其他语言),编写代码时不需要太多思考(因此会导致错误),然后希望稍后将其删除。虽然这种将思考时间首先节省时间并且随后用调试时间进行交易的想法是海事组织通常是一种糟糕的做法(错误消除的成本/努力总是会更高),但是在允许UB的语言中,这是一种真正的自杀,因为错误可以隐藏也落后于非确定性行为。

显然,我不是说测试是一个糟糕的主意......这当然是一个伟大的(基本上是,如果你想保持一个重构的可能性必须具备的),如果它不作为,但只有在编写代码时请原谅降低您的注意力。

你应该基本上避免混淆错误(即UB)与什么是崩溃(即段错误)。崩溃是朋友......并且您希望尽可能多地崩溃,因为崩溃是指出现错误的信号。要添加更多可能的崩溃点,例如,您应该使用断言...... segfault只是环境自动放置的断言,但您需要更多。当你发生崩溃时,你知道有一个错误,你可以开始寻找它。不幸的是,当你没有崩溃时,这并不意味着没有错误......它只是意味着没有错误落在你为他们准备的陷阱中。

一段代码可以很好地编译(零错误和零警告),它可以通过整个测试套件......但仍然可能同时不正确。当然,即使在高级语言中,确定避免UB的性能价格值得运行时检查(例如Java),但是更高的逻辑级别也是如此。由于非确定性,C和C++中的UB使事情变得更加困难。

在您的代码中,UB被触发是因为您在调用new ... [](字符串文字)时没有获得指针时调用delete[]。这当然可能会或不会产生段错误。

您的代码也有一些其他的法律,但很值得怀疑部分:

  1. 的基类的析构函数不是虚拟的。你不是使用指向基的指针来销毁派生的对象,所以这是合法的,但不管怎么说都不是一个坏主意。如果一个类是要派生的,那么析构函数应该是虚拟的。

  2. 派生类定义了析构函数,但没有复制构造函数和赋值运算符。 These three methods should always go together:要么你没有定义它们,要么你定义(或者至少声明)它们三个。原因是这三种方法是由编译器自动创建的,如果它们不存在的话,那么自动生成的代码在一种情况下是正确的,而在其他情况下则不太可能。例如,在你的代码中,如果有人用Derived *der2 = new Derived(*der1);复制了派生对象,那么指向动态分配内存的指针将被复制并且可能被释放两次(两个对象被销毁一次)。如果使用*der2 = *der1;进行分配,除了内存泄漏之外,您还可能会发生双重释放。禁止一个类的拷贝构造函数和分配可能是有意义的,但在这种情况下,常见的习惯用法是将这些方法声明为私有的并使它们不能实现(如果其他人试图使用它们,这会产生编译错误,并且链接错误,如果一种方法错误地使用它们)。

+0

写得很好,但从我得到的答案是10%的图片,即“由于UB的Seg故障”。除了将字符串文字分配给指向已分配内存的字符指针以外,还有其他可能的缺陷吗? (我认为这个缺陷很大,但仍然:)) – Arunmu 2011-05-11 06:45:47

+1

如果你认为没有虚析构函数是一个错误,那么你错了。当然,虚拟析构函数对于派生类来说总是一个好主意......但是,如果您从不使用指向基的指针销毁派生对象,那么这不是语言要求。 – 6502 2011-05-11 06:51:28

+0

我编辑了另外一些关于你的C++代码的评论。 – 6502 2011-05-11 07:28:29