!!!!!---是使用 delete newobject 还是使用 delete []newobject;---

hzhxxx 2007-05-17 10:05:10
typedef struct my_t
{
int m_a;
char m_y[10];
my_t()
{
m_a = 0;
memset(m_y,0,sizeof(m_y));
}
};

void assign(unsigned char *&newobject,int len)
{
my_t t1,t2;
t1.m_a = 2;
strcpy(t1.m_y,"AAA");

t2.m_a = 3;
strcpy(t2.m_y,"BBB");

newobject = new unsigned char[len];
memcpy(newobject,&t1,sizeof(my_t));
memcpy(newobject + sizeof(my_t),&t2,sizeof(my_t));
}

int _tmain(int argc, _TCHAR* argv[])
{
struct my_t *newobject = 0;
assign((unsigned char *&)newobject,sizeof(my_t) * 2);

//是使用 delete newobject 还是使用 delete []newobject;
delete []newobject;
newobject = 0;
}

...全文
893 32 打赏 收藏 转发到动态 举报
AI 作业
写回复
用AI写文章
32 条回复
切换为时间正序
请发表友善的回复…
发表回复
deng2000 2007-05-20
  • 打赏
  • 举报
回复
From hzhxxx:
这样增加一个析构函数,相反,使用 delete[] newobject; 运行的时候报错;
但是如果使用 delete newobject 虽然能正确运行,但是明显析构的对象是一个,存在
内存泄漏,但是我测试创建很多对象,也没有发现内存泄漏.

============================================================

提醒楼主: delete和delete[]都是按顺序作两件事情:
1). 为(一个或多个)对象调用析构函数.
2). 释放对象所占内存.
要分清楚这两步是干完全不同的事情.即析构函数!=释放内存.因为你的~my_t()没干什么实际事情,即使漏调用或多调用了也没有什么大问题.真正导致你的例子中种种问题的是第2步
要使第2步正确执行,需要保证你释放的地址确实是当初申请时的地址.而正如我前面指出的,在某种特定情况下,二者相差4字节导致异常.
hzhxxx 2007-05-20
  • 打赏
  • 举报
回复

这种方式可能不合适吧,有点怪异啊。。。

能加 qq 吗,私下讨论一下
hzhxxx 2007-05-20
  • 打赏
  • 举报
回复

假如我创建 N 个 newobject ,那不是要循环释放啊

//assign((unsigned char *&)newobject,sizeof(my_t) * N);

for(unsigned int i = 0 ;i < N;++i)
{
(newobject + i)->m~y_t();
}
delete [](unsigned char *)newobject;
deng2000 2007-05-20
  • 打赏
  • 举报
回复
就你这个程序而言,以下面方式释放是最稳妥的,不管my_t有没有显式析构函数.

newobject->~my_t();
(newobject+1)->~my_t();
delete [] (unsigned char *)newobject;
hzhxxx 2007-05-20
  • 打赏
  • 举报
回复

但是还是无法确定使用那种释放内存的方式。。
hzhxxx 2007-05-20
  • 打赏
  • 举报
回复

感谢

deng2000() ( ) 信誉:100

这么晚了还坚持发帖,,,
hzhxxx 2007-05-20
  • 打赏
  • 举报
回复

今天看了 more effective c++,综合上面的分析
感觉这样可能比较合适,也是比较通用的释放方法

假如我创建 N 个 newobject ,那不是要循环释放啊
//assign((unsigned char *&)newobject,sizeof(my_t) * N);

for(unsigned int i = 0 ;i < N;++i)
{
(newobject + i)->m~y_t();
}
operator delete [](newobject);
首先依次调用每个对象的析构函数,再调用全局的
operator delete[](void *memoryToBeDeallocated)
函数释放被分配的内存,就是这样比较慢.
biosxjj 2007-05-19
  • 打赏
  • 举报
回复
看不懂 楼主表述清楚点吧 谢谢啦
hzhxxx 2007-05-19
  • 打赏
  • 举报
回复

{
// 把“错误”大小的请求转给::operator new()处理;
// 详见条款8
if (size != sizeof(airplane))
return ::operator new(size);

airplane *p = // p指向自由链表的表头
headoffreelist; //

// p 若合法,则将表头移动到它的下一个元素
//
if (p)
headoffreelist = p->next;

else {
// 自由链表为空,则分配一个大的内存块,
// 可以容纳block_size个airplane对象
airplane *newblock =
static_cast<airplane*>(::operator new(block_size *
sizeof(airplane)));

// 将每个小内存块链接起来形成一个新的自由链表
// 跳过第0个元素,因为它要被返回给operator new的调用者
//
for (int i = 1; i < block_size-1; ++i)
newblock[i].next = &newblock[i+1];

// 用空指针结束链表
newblock[block_size-1].next = 0;

// p 设为表的头部,headoffreelist指向的
// 内存块紧跟其后
p = newblock;
headoffreelist = &newblock[1];
}

return p;
}

如果你读了条款8,就会知道在operator new不能满足内存分配请求时,会执行一系列与new-handler函数和例外有关的例行性动作。上面的代码没有这些步骤,这是因为operator new管理的内存都是从::operator new分配来的。这意味着只有::operator new失败时,operator new才会失败。而如果::operator new失败,它会去执行new-handler的动作(可能最后以抛出异常结束),所以不需要airplane的operator new也去处理。换句话说,其实new-handler的动作都还在,你只是没看见,它隐藏在::operator new里。

有了operator new,下面要做的就是给出airplane的静态数据成员的定义:

airplane *airplane::headoffreelist;

const int airplane::block_size = 512;
hzhxxx 2007-05-19
  • 打赏
  • 举报
回复

有点难于理解

--------------------------------------------------------------------------------


条款10: 如果写了operator new就要同时写operator delete

让我们回过头去看看这样一个基本问题:为什么有必要写自己的operator new和operator delete?

答案通常是:为了效率。缺省的operator new和operator delete具有非常好的通用性,它的这种灵活性也使得在某些特定的场合下,可以进一步改善它的性能。尤其在那些需要动态分配大量的但很小的对象的应用程序里,情况更是如此。

例如有这样一个表示飞机的类:类airplane只包含一个指针,它指向的是飞机对象的实际描述(此技术在条款34进行说明):

class airplanerep { ... }; // 表示一个飞机对象
//
class airplane {
public:
...
private:
airplanerep *rep; // 指向实际描述
};

一个airplane对象并不大,它只包含一个指针(正如条款14和m24所说明的,如果airplane类声明了虚函数,会隐式包含第二个指针)。但当调用operator new来分配一个airplane对象时,得到的内存可能要比存储这个指针(或一对指针)所需要的要多。之所以会产生这种看起来很奇怪的行为,在于operator new和operator delete之间需要互相传递信息。

因为缺省版本的operator new是一种通用型的内存分配器,它必须可以分配任意大小的内存块。同样,operator delete也要可以释放任意大小的内存块。operator delete想弄清它要释放的内存有多大,就必须知道当初operator new分配的内存有多大。有一种常用的方法可以让operator new来告诉operator delete当初分配的内存大小是多少,就是在它所返回的内存里预先附带一些额外信息,用来指明被分配的内存块的大小。也就是说,当你写了下面的语句,

airplane *pa = new airplane;

你不会得到一块看起来象这样的内存块:

pa——> airplane对象的内存

而是得到象这样的内存块:

pa——> 内存块大小数据 + airplane对象的内存

对于象airplane这样很小的对象来说,这些额外的数据信息会使得动态分配对象时所需要的的内存的大小翻番(特别是类里没有虚拟函数的时候)。

如果软件运行在一个内存很宝贵的环境中,就承受不起这种奢侈的内存分配方案了。为airplane类专门写一个operator new,就可以利用每个airplane的大小都相等的特点,不必在每个分配的内存块上加上附带信息了。

具体来说,有这样一个方法来实现你的自定义的operator new:先让缺省operator new分配一些大块的原始内存,每块的大小都足以容纳很多个airplane对象。airplane对象的内存块就取自这些大的内存块。当前没被使用的内存块被组织成链表——称为自由链表——以备未来airplane使用。听起来好象每个对象都要承担一个next域的开销(用于支持链表),但不会:rep域的空间也被用来存储next指针(因为只是作为airplane对象来使用的内存块才需要rep指针;同样,只有没作为airplane对象使用的内存块才需要next指针),这可以用union来实现。

具体实现时,就要修改airplane的定义,从而支持自定义的内存管理。可以这么做:

class airplane { // 修改后的类 — 支持自定义的内存管理
public: //

static void * operator new(size_t size);

...

private:
union {
airplanerep *rep; // 用于被使用的对象
airplane *next; // 用于没被使用的(在自由链表中)对象
};

// 类的常量,指定一个大的内存块中放多少个
// airplane对象,在后面初始化
static const int block_size;

static airplane *headoffreelist;

};

上面的代码增加了的几个声明:一个operator new函数,一个联合(使得rep和next域占用同样的空间),一个常量(指定大内存块的大小),一个静态指针(跟踪自由链表的表头)。表头指针声明为静态成员很重要,因为整个类只有一个自由链表,而不是每个airplane对象都有。

下面该写operator new函数了:

void * airplane::operator new(size_t size)
hzhxxx 2007-05-19
  • 打赏
  • 举报
回复

typedef struct my_t
{
int m_a;
char m_y[1024];
static int m_count;
my_t()
{
m_a = 0;
memset(m_y,0,sizeof(m_y));
}

~my_t()
{
m_count++;
std::cout<<m_count<<endl;
}
};
int my_t::m_count = 0;

这样增加一个析构函数,相反,使用 delete[] newobject; 运行的时候报错;
但是如果使用 delete newobject 虽然能正确运行,但是明显析构的对象是一个,存在
内存泄漏,但是我测试创建很多对象,也没有发现内存泄漏
deng2000 2007-05-19
  • 打赏
  • 举报
回复
应hzhxxx的需求,也为满足自己的好奇心,我在VS2003中试图用下面的简单例子分析delete[]的汇编代码:
A* p = new A[6];
delete [] p;
先分析没有析构函数的情况, 如下定义A:
class A
{
public:
A(){m=0; n=0;};
public:
int m;
int n;
};

delete[]p对应的汇编代码如下:

delete [] p;
00411DEC mov eax,dword ptr [p]
00411DEF mov dword ptr [ebp-0E0h],eax
00411DF5 mov ecx,dword ptr [ebp-0E0h]
00411DFB push ecx
00411DFC call operator delete[] (4110B4h)
00411E01 add esp,4

我们可以看到,此时delete[]p的代码很简单,就是简单以数组的首地址p(也就是ecx)为参数调用operator delete[]释放内存(注意delete[]和operator delete[]是两码事!).插一句题外话,我进一步跟到operator delete[]内后发现它其实就是简单地调用operator delete而已.

而当我给A加上析构函数:
class A
{
public:
A(){m=0; n=0;};
~A() {};
public:
int m;
int n;
};
虽然~A()里面什么也没干,delete[]p已变得大不一样了:

delete [] p;
00411E03 mov eax,dword ptr [p]
00411E06 mov dword ptr [ebp-0E0h],eax
00411E0C mov ecx,dword ptr [ebp-0E0h]
00411E12 mov dword ptr [ebp-0ECh],ecx
00411E18 cmp dword ptr [ebp-0ECh],0
00411E1F je main+0E6h (411E36h)
00411E21 push 3
00411E23 mov ecx,dword ptr [ebp-0ECh]
00411E29 call A::`vector deleting destructor' (4116A9h)
00411E2E mov dword ptr [ebp-10Ch],eax
00411E34 jmp main+0F0h (411E40h)
00411E36 mov dword ptr [ebp-10Ch],0

不再是简单地调用operator delete[]了事,而是调用A::`vector deleting destructor'.从名字就可看出是要多次析构了(其实还包括释放内存). A::`vector deleting destructor'的代码比较长,下面这一段是其中我们感兴趣的部分:

////////// part of A::`vector deleting destructor'
004131D0 mov eax,dword ptr [this]
004131D3 mov ecx,dword ptr [eax-4]
004131D6 push ecx
004131D7 push 8
004131D9 mov edx,dword ptr [this]
004131DC push edx
004131DD call `eh vector destructor iterator' (4116BDh)
004131E2 mov eax,dword ptr [ebp+8]
004131E5 and eax,1
004131E8 je A::`vector deleting destructor'+59h (4131F9h)
004131EA mov eax,dword ptr [this]
004131ED sub eax,4
004131F0 push eax
004131F1 call operator delete[] (4110B4h)

首先注意这一句:
004131D3 mov ecx,dword ptr [eax-4]
eax是p地址,它把p前面4字节作为一个整数赋给ecx. Aha! 这就是传说中的"前缀"了.而且比我想象的还简单,没有什么复杂结构,就是一个整数记录元素个数.此时查看ecx的值确实是6,即数组中有6个元素.

再注意这一句:
004131D7 push 8
这个8就是sizeof(A) (A中有两个整数成员m和n).

再看这一句:
004131DD call `eh vector destructor iterator' (4116BDh)
我们可以看到push给它的三个参数(ecx, 8, edx)分别是数组元素个数,元素size和数组地址.你应该能猜出`eh vector destructor iterator'要干什么吧? 对,就是对每个元素调用析构函数~A().篇幅所限,我就不列出其执行的详细过程了.

最后,看看这几行:
004131EA mov eax,dword ptr [this]
004131ED sub eax,4
004131F0 push eax
004131F1 call operator delete[] (4110B4h)
还是调用operator delete[]释放内存,但注意传给它的参数不是数组地址,而是数组地址往前4字节.跟我们预测的一样,释放内存时考虑了前缀的存在.

经过这些分析我们可得到初步结论如下:
1. 前缀是否存在取决于有没有显式析构函数.
2. 前缀就是4字节整型,记录元素个数.

再次重申,这里分析的是用new []得到的对象数组.而且只是对VS2003的一个简单例子分析.对象更复杂一点时前缀有可能发生变化(虽然我觉得可能性不大).其它编译器就更不知道了.

有了这些分析,再回头看hzhxxx的疑问
delete []newobject 和 delete newobject都没有内存泄漏是因为my_t没有显式析构函数.当你加上~my_t()后,就不只是内存泄漏的问题了,而是会导致内存异常错.因为你释放时的地址参数与申请时的地址参数相差4字节.
deng2000 2007-05-19
  • 打赏
  • 举报
回复
建议: 你给my_t加上分析构函数再试试:

class my_t
{
...
~my_t(){};
}
hzhxxx 2007-05-19
  • 打赏
  • 举报
回复

经过在 VC 中测试

delete [](const *&)newobject 内存不会泄漏,但是它是按一个一个字节删除释放的非常慢、


delete []newobject 和 delete newobject 都没有内存泄漏,很是郁闷,是不是 VC 优化了

看不懂汇编代码,很是失望,那位解释一下..
deng2000 2007-05-18
  • 打赏
  • 举报
回复
补充: 其实还不只是效率的问题.因为我们有个前提:非数组对象如Y*p = new Y;是没有前缀的(否则代价太大了,因为很多情况是new单个对象而不是数组). 这样,在你传给delete一个指针时,它没法知道指针前面的一段是不是数组前缀.
eggqq007 2007-05-18
  • 打赏
  • 举报
回复
支持taodm和deng2000()
看《Effective C++》
taodm 2007-05-18
  • 打赏
  • 举报
回复
去看《Effective C++ 》2e item 5
hzhxxx 2007-05-18
  • 打赏
  • 举报
回复


最终还是没有很好的确定 使用那种方式释放啊。。

delete [](unsigned char)newobject;
我想还可以这样。。。,但是没有理论依据
taodm 2007-05-18
  • 打赏
  • 举报
回复
太长,帖起来太累。
zylthinking 2007-05-18
  • 打赏
  • 举报
回复
taodm, 给你提个建议, 要是知道直接说出来,拐弯抹角的不痛快
加载更多回复(12)

33,321

社区成员

发帖
与我相关
我的任务
社区描述
C/C++ 新手乐园
社区管理员
  • 新手乐园社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧