为什么这段代码会报错

2022-11-17 17:31:26 +08:00
 LuckyPocketWatch

首先是个自定义类

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 却要报错?

1884 次点击
所在节点    C++
9 条回复
hsfzxjy
2022-11-17 17:44:38 +08:00
widget = new Widget(10);

你这句并不是在刚刚 new 出来的空间中初始化一个对象,而是另创建了一个对象,并把它的地址赋给 widget ,之前 new 的空间已经被泄漏了。

理解了这一点你就知道第二段为什么错了。
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 都不一样。
L4Linux
2022-11-17 19:11:26 +08:00
这段代码问题大到我无力吐嘈了。别用 new/delete 得了。不直接和 kernel 打交道的场景,用 std::make_unique/shared 完全可以。
newmlp
2022-11-17 19:16:15 +08:00
要使用偏移量必须是连续分配的内存吧,for 里面 new 出来的内存不一定就是连续的啊
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 啊
DiamondY
2022-11-17 20:02:37 +08:00
5 楼是对的,widget_tmp = new Widget(i)后,widget 与 widget_tmp 就没啥关系了,widget 是个野指针
leonshaw
2022-11-17 20:24:50 +08:00
new 是申请新内存构造对象,placement new 是在给定内存构造对象
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 块赋给一个新的指针
请特别注意,之前第一步操作的时候存的指针值已经被破坏了!
wanmyj
2022-11-18 10:30:46 +08:00
代码无力吐槽,OP 的 C++是参考 CSharp 学的吗,C++的 new 和指针都没弄清楚,从哪里抄的代码直接就用了,没有理解代码背后每个变量都在干嘛,这种条件下写代码,就算练习也只能加深错误的理解,用在产品上更非常危险的,不只是导致各种问题,更给后面的改正带来巨大 tech debt

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/896000

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX