首先是个自定义类
class Widget{
private:
int n;
public:
Widget(int v):n(v) {std::cout<<"第"<<n<<"已经构造";}
~Widget(){std::cout<<"第"<<n<<"已经析构";}
void show_n()const{std::cout<<"当前值为"<<n;}
然后首先调用
Widget* widget = static_cast<Widget*>(::operator new(sizeof(Widget)));
widget = new Widget(10); //语句 1
widget->show_n();
widget->~Widget();
::operator delete(widget);
这段代码编译运行正常,win10+VS2019,64 位,运行结果为
第 10 已经构造 当前值位 10 第 10 已经析构
然后另一段测试代码,用 operator new 开辟可以存放多个 Widget 的内存
Widget* widget = static_cast<Widget*>(::operator new(sizeof(Widget)*10));
Widget* widget_tmp = widget;
for(int i = 0 ; i < 10 ; ++i){
widget_tmp = new Widget(i); //语句 2
widget_tmp += 1;
}
这段代码编译运行同样没有问题,运行把每个构造函数中的语句都正确的打印出来了
然后是
(widget + 5) -> show_n(); //语句 3
这句直接报错,VS 提示我访问了错误的内存
然后我把语句 2 改成了
new (widget_tmp) People(i); //语句 4
这样就语句 3 就不会报错,也正确的打印的输出信息,开始以为是申请多个对象内存要用 new[],于是我把申请内存的语句改为
Widget* widget = static_cast<Widget*>(::operator new[](sizeof(Widget)));
我发现问题还是一样,如果使用语句 2 生成对象就报错,必须采用语句 4 的方式
我向问下为什么语句 1 和语句 2 用相同的方法生成对象,但语句 3 却要报错?
1
hsfzxjy 2022-11-17 17:44:38 +08:00 via Android 1
widget = new Widget(10);
你这句并不是在刚刚 new 出来的空间中初始化一个对象,而是另创建了一个对象,并把它的地址赋给 widget ,之前 new 的空间已经被泄漏了。 理解了这一点你就知道第二段为什么错了。 |
2
ysc3839 2022-11-17 18:00:37 +08:00
请发完整代码,我自己测试语句 3 并没有问题。
“为什么语句 1 和语句 2 用相同的方法生成对象” 并不是相同的方法。语句 1 那段代码,先 new 了一块内存,把地址保存到 widget 变量中,又 new 了一个 Widget 对象,覆盖了 widget 变量,最后手动调用 Widget 的析构函数,然后释放 Widget 对象所用的内存,一开始 new 的那块内存泄漏了。 语句 2 那段代码,先 new 了一块内存,把地址保存到 widget 变量中,再把这个地址赋值给 widget_tmp 变量,然后每次循环 new 了一个 Widget 对象,覆盖了 widget_tmp 变量。 语句 3 是把 widget 那块内存中 +5 偏移量的地址作为 this 指针传递给 Widget::show_n()。这么做是未定义行为,因为没有调用构造函数。 语句 4 是在 widget_tmp 那块内存上调用构造函数,也叫做 placement new ,和语句 1 2 都不一样。 |
3
L4Linux 2022-11-17 19:11:26 +08:00
这段代码问题大到我无力吐嘈了。别用 new/delete 得了。不直接和 kernel 打交道的场景,用 std::make_unique/shared 完全可以。
|
4
newmlp 2022-11-17 19:16:15 +08:00
要使用偏移量必须是连续分配的内存吧,for 里面 new 出来的内存不一定就是连续的啊
|
5
littlewing 2022-11-17 19:43:55 +08:00
widget_tmp = new Widget(i); //语句 2
并没有在 Widget* widget = static_cast<Widget*>(::operator new(sizeof(Widget)*10)); 申请的内存上 new 啊 |
6
DiamondY 2022-11-17 20:02:37 +08:00
5 楼是对的,widget_tmp = new Widget(i)后,widget 与 widget_tmp 就没啥关系了,widget 是个野指针
|
7
leonshaw 2022-11-17 20:24:50 +08:00
new 是申请新内存构造对象,placement new 是在给定内存构造对象
|
8
joshu 2022-11-17 20:32:53 +08:00
这代码槽点确实很多
改成这样至少是能跑的 核心问题就是 5 楼所说的 #include <iostream> class Widget{ private: int n; public: Widget(int v):n(v) {std::cout<<"第"<<n<<"已经构造"<< std::endl;} ~Widget(){std::cout<<"第"<<n<<"已经析构"<< std::endl;} void show_n()const{std::cout<<"当前值为"<<n << std::endl;} }; int main() { Widget** widget = static_cast<Widget**>(::operator new(sizeof(Widget*)*10)); for(int i = 0 ; i < 10 ; ++i){ *(widget+i) = new Widget(i); //语句 2 } (*(widget + 5)) -> show_n(); return 0; } 请注意,原代码里的 Widget* widget = static_cast<Widget*>(::operator new(sizeof(Widget)*10)); 它所执行的操作是分配一个可以供 10 个 Widget 存放的内存空间,一个 Widget 是 4 字节 而你思路上试图做的是 widget=new Widget(i)是试图在把这 10 个 Widget 空间的前两块( 64 位系统指针是 8 字节)赋给一个新的指针,当 widget+=1 的时候,再做 widget=new Widget(i)的时候是把这个空间块的第 2 、3 块赋给一个新的指针 请特别注意,之前第一步操作的时候存的指针值已经被破坏了! |
9
wanmyj 2022-11-18 10:30:46 +08:00
代码无力吐槽,OP 的 C++是参考 CSharp 学的吗,C++的 new 和指针都没弄清楚,从哪里抄的代码直接就用了,没有理解代码背后每个变量都在干嘛,这种条件下写代码,就算练习也只能加深错误的理解,用在产品上更非常危险的,不只是导致各种问题,更给后面的改正带来巨大 tech debt
|