当我们在c ++中对类使用malloc / free时,不会创建对象。那为什么这段代码有效呢? 如果没有创建对象,则它不能给出下面提到的输出。
class Test
{
public:
Test()
{
cout << "Test : ctor\r\n";
}
~Test()
{
cout << "Test : dtor\r\n";
}
void Hello()
{
cout << "Test : Hello World\r\n";
}
};
int main()
{
cout << "2\n";
Test* t2 = (Test*) malloc(sizeof Test);
t2->Hello();
free(t2);
return 0;
}
OUTPUT:
Hello World
答案 0 :(得分:6)
未定义的行为是简短的答案。答案很长,因为你的类没有直接调用它们的虚函数只是一个带有隐式this
参数的函数调用,它指向你分配的内存。这就是说,如果您的任何方法访问this
,它将导致更多未定义的行为,因为该对象尚未构建。
从Columbo和LRIO公然偷走:
[C++11: 3.8/1]:
对象的生命周期是对象的运行时属性。如果一个对象属于类或聚合类型,并且它或其成员之一由一个除了普通默认构造函数之外的构造函数初始化,则称该对象具有非平凡初始化。 [注意:通过简单的复制/移动构造函数进行初始化是非平凡的初始化。 -end note]
关键位是它指向对象的指针可以包含除存储成员的位置之外的数据。由实现决定是什么。
答案 1 :(得分:4)
当我们在c ++中对类使用malloc / free时,不会创建对象。那为什么这段代码有效呢?
这取决于你如何定义“作品”,这是一个令人难以置信的模糊术语,你通常应该避免这样的问题。
我会认为代码不“工作”:它有一个错误。它被打破。在任何给定的执行运行中,它可能会失败。它可能会崩溃。它可以打开一个虫洞。
错误是什么?错误是您正在调用未定义的行为。您为Test
对象分配了内存,但您从未创建过Test
对象。然后,您对内存执行操作,就好像它包含Test
对象一样,但它没有。
[C++11: 12.1/5]:
[..] 如果默认构造函数不是由用户提供的,则默认构造函数是微不足道的 [..] 否则,默认构造函数是非平凡的。
[C++11: 3.8/1]:
对象的生命周期是对象的运行时属性。 如果一个对象属于类或聚合类型,并且或其成员之一由一个普通的默认构造函数以外的构造函数初始化,则称该对象具有非平凡的初始化。 [注意:通过简单的复制/移动构造函数初始化是非平凡的初始化。 -end note] 类型为T
的对象的生命周期始于:
- 获取具有
T
类型的正确对齐和大小的存储空间- 如果对象具有非平凡的初始化,则其初始化已完成。
类型
T
的对象的生命周期在以下时间结束:
- 如果
T
是具有非平凡析构函数(12.4)的类类型,则析构函数调用将启动,或者- 对象占用的存储空间被重用或释放。
[C++11: 3.8/3]:
本国际标准中归属于对象的属性仅适用于给定对象的生命周期。 [..]
[C++11: 3.8/5]:
在对象的生命周期开始之前但是在对象占用的存储之后已经分配了 [..] 任何指向对象的存储位置的指针将被或可能被使用但只能以有限的方式使用。 [..] 可以取消引用这样的指针,但是得到的左值只能以有限的方式使用,如下所述。如果出现以下情况,该程序具有未定义的行为:
- [..]
- 指针用于访问非静态数据成员或调用对象的非静态成员函数
- [..]
您所看到的是偶然成功:您的程序没有错误的错误外观。这是因为您的计算机不执行C ++程序,而是机器代码,有时在从C ++抽象到机器代码的转换过程中,前提条件的破坏就会丢失。
更具体地说,您的计算机并不关心您的程序是否以这种特定方式被破坏,因为它不需要做任何依赖于您的对象存在的事情。这些成员函数不会触及任何实例数据,因此您的计算机永远不会意识到代码是错误的。
这并不意味着代码“有效”。
答案 2 :(得分:3)
来自C ++ 11 FD的所有引用。
你的代码几乎没问题。问题是你的类有非平凡的初始化,因为它有一个用户提供的,因此非平凡的默认构造函数 - [basic.life] / 1:
如果一个对象属于a,则称该对象具有非平凡的初始化 类或聚合类型和它[..]由初始化 除了普通的默认构造函数之外的构造函数。
因此整个程序中的对象并不存在,在上述引用后立即澄清:
类型为
T
的对象的生命周期始于:
- 存储用 获得了类型T的正确对齐和大小,
- 如果是对象 具有非平凡的初始化,其初始化已完成。
...因此UB根据[basic.life] / 5:
在对象的生命周期开始之前但在存储之后 对象将占用的是[..]任何指针 指对象所在或存在的存储位置 可以使用但仅限于有限的方式。 [..]否则,这样的指针 指分配存储(3.7.4.2),并使用指针就好了 指针的类型为
void*
,定义明确。这样的指针可能是 取消引用,但得到的左值只能在有限的情况下使用 方式,如下所述。如果出现以下情况,该程序具有未定义的行为:
- 指针用于访问非静态数据成员或调用a 对象的非静态成员函数,或