登录 用户中心() [退出] 后台管理 注册
   
您的位置: 首页 >> 程序员学前班[不再更新,只读] >> 主题: 寻找悬挂指针(野指针)由《C++:谁动了我的指针》想到的     [回主站]     [分站链接]
标题
寻找悬挂指针(野指针)由《C++:谁动了我的指针》想到的
我是马甲
浏览(0) + 2007-08-06 16:05:16 发表 编辑

关键字:

C++:谁动了我的指针 出处:csdn

[ 2005-07-18 09:48:37 ] 作者:lifanxi 责任编辑:moningfeng


译者序:

  本文介绍了一种在调试过程中寻找悬挂指针(野指针)的方法,这种方法是通过对new和delete运算符的重载来实现的。

  这种方法不是完美的,它是以调试期的内存泄露为代价来实现的,因为文中出现的代码是绝不能出现在一个最终发布的软件产品中的,只能在调试时使用。

  在VC中,在调试环境下,可以简单的通过把new替换成DEBUG_NEW来实现功能更强更方便的指针检测,详情可参考MSDN。DEBUG_NEW的实现思路与本文有相通的地方,因此文章中介绍的方法虽然不是最佳的,但还算实用,更重要的是,它提供给我们一种新的思路。

简介:

  前几天发生了这样一件事,我正在调试一个程序,这个程序用了一大堆乱七八糟的指针来处理一个链表,最终在一个指向链表结点的指针上出了问题。我们预计它应当指向的是一个虚基类的对象。我想到第一个问题是:指针所指的地方真的有一个对象吗?出问题的指针值可以被4整除,并且不是NULL的,所以可以断定它曾经是一个有效的指针。通过使用Visual Studio的内存查看窗口(View->Debug Windows->Memory)我们发现这个指针所指的数据是FE EE FE EE FE EE ...这通常意味着内存是曾经是被分配了的,但现在却处于一种未分配的状态。不知是谁、在什么地方把我的指针所指的内存区域给释放掉了。我想要找出一种方案来查出我的数据到底是怎么会被释放的。

背景:
  我最终通过重载了new和delete运算符找到了我丢失的数据。当一个函数被调用时,参数会首先被压到栈上后,然后返回地址也会被压到栈上。我们可以在new和delete运算符的函数中把这些信息从栈上提取出来,帮助我们调试程序。

代码:
  在经历了几次错误的猜测后,我决定求助于重载new和delete运算符来帮我找到我的指针所指向的数据。下面的new运算符的实现把返回地址从栈上提了出来。这个返回地址位于传递过来的参数和第一个局部变量的地址之间。编译器的设置、调用函数的方法、计算机的体系结构都会引响到这个返回地址的实际位置,所以您在使用下面代码的时候,要根据您的实际情况做一些调整。一旦new运算符获得了返回地址,它就在将要实际分配的内存前面分配额外的16个字节的空间来存放这个返回地址和实际的分配的内存大小,并且把实际要分配的内存块首地址返回。

  对于delete运算符,你可以看到,它不再释放空间。它用与new同样的方法把返回地址提取出来,写到实际分配空间大小的后面(译者注:就是上面分配的16个字节的第9到第12个字节),在最后四个字节中填上DE AD BE EF(译者注:四个十六进制数,当成单词来看正好是dead beef,用来表示内存已释放真是很形象!),并且把剩余的空间(译者注:就是原本实际应该分配而现在应该要释放掉的空间)都填上一个重复的值。

  现在,如果程序由于一个错误的指针而出错,我只需打开内存查看窗口,找到出错的指针所指的地方,再往前找16个字节。这里的值就是调用new运算符的地址,接下来四个字节就是实际分配的内存大小,第三个四个字节是调用delete运算符的地址,最后四个字节应该是DE AD BE EF。接下的实际分配过的内存内容应该是77 77 77 77。

  要通过这两个返回地址在源程序中分别找到对应的new和delete,可以这样做:首先把表示地址的四个字节的内容倒序排一下,这样才能得到真正的地址,这里因为在Intel平台上字节序是低位在前的。下一步,在源代码上右击点击,选“Go To Diassembly”。在反汇编的窗口上的左边一栏就是机器代码对应的内存地址。按Ctrl + G或选择Edit->Go To...并输入你找到的地址之一。反汇编的窗口就将滚动到对应的new或delete的函数调用位置。要回到源程序只需再次右键单击,选择“Go To Source”。您就可以看到相应的new或delete的调用了。

  现在您就可以很方便的找出您的数据是何时丢失的了。至于要找出为什么delete会被调用,就要靠您自己了。
程序代码

  #include

  void * :perator new(size_t size)
  {
    int stackVar;
    unsigned long stackVarAddr = (unsigned long)&stackVar;
    unsigned long argAddr = (unsigned long)&size;

    void ** retAddrAddr = (void **)(stackVarAddr/2 + argAddr/2 + 2);

    void * retAddr = * retAddrAddr;

    unsigned char *retBuffer = (unsigned char*)malloc(size + 16);

    memset(retBuffer, 0, 16);

    memcpy(retBuffer, &retAddr, sizeof(retAddr));

    memcpy(retBuffer + 4, &size, sizeof(size));

    return retBuffer + 16;
  }

  void :perator delete(void *buf)
  {
    int stackVar;
    if(!buf)
      return;

    unsigned long stackVarAddr = (unsigned long)&stackVar;
    unsigned long argAddr = (unsigned long)&buf;

    void ** retAddrAddr = (void **)(stackVarAddr/2 + argAddr/2 + 2);

    void * retAddr = * retAddrAddr;

    unsigned char* buf2 = (unsigned char*)buf;

    buf2 -= 8;

    memcpy(buf2, &retAddr, sizeof(retAddr));

    size_t size;

    buf2 -= 4;

    memcpy(&size, buf2, sizeof(buf2));

    buf2 += 8;

    buf2[0] = 0xde;
    buf2[1] = 0xad;
    buf2[2] = 0xbe;
    buf2[3] = 0xef;

    
    buf2 += 4;

    memset(buf2, 0x7777, size);

    // deallocating destroys saved addresses, so dont
    // buf -= 16;
    // free(buf);
  }

其它值得关注的地方:

  这段代码同样可以用于内存泄露的检测。只需修改delete运算符使它真正的去释放内存,并且在程序退出前,用__heapwalk遍历所有已分配的内存块并把调用new的地址提取出来,这就将得到一份没有被delete匹配的new调用列表。

  还要注意的是:这里列出的代码只能在调试的时候去使用,如果你把它段代码放到最终的产品中,会导致程序运行时内存被大量的消耗。

我是马甲
2007-8-6 16:07:05 发表 编辑

在 vc6 下调用 free 函数后内存也会自动设置为 EE FE 。其他例如 gcc 不一定会这样。
我是马甲
2007-8-6 16:29:44 发表 编辑

另外 new[]和delete[]操作 是需要另外重载的,看看下面这篇文章。

--------------------------------------------------
C++内存管理基础之new & delete
http://www.csai.cn 作者:如水随风 来源:http://dev.csdn.net 2007年1月12日 发表评论 进入社区

  内存管理的基础是要知道怎么获得以及释放内存,如你所知,在C/C++中就是调用new和delete操作。

  1. 分清operator new和new operator

   全局函数operator new通常这样声明:
  void * operator new(size_t size);
  返回值类型是void*,表示其返回的是一个未经处理(raw)的指针,指向未初始化的内存。参数size_t确定分配多少内存。你能增加额外的参数重载函数operator new,但是第一个参数类型必须是size_t。头文件中有一个很好的重载的例子,那就是placement new,它看上去象这样:
  void * operator new(size_t, void *location)
  {
   return location;
  }

  这初看上去有些陌生,但它却是new操作符的一种常见重载方法,使用一个额外的变量buffer,当new操作符隐含调用operator new函数时,把这个变量传递给它。被调用的operator new函数除了持有强制的参数size_t外,还必须接受void*指针参数,指向构造对象占用的内存空间。未被使用的(但是强制的)参数size_t没有参数名字,以防止编译器警告说它未被使用。在使用placement new的情况下,调用者已经获得了指向内存的指针,因为调用者知道对象应该放在哪里。placement new需要做的就是返回传递给它的指针。

  我们更经常使用的new是new操作符(new operator),而非操作符new(operator new),如当你使用new操作符构建一个对象的时候,实际上做了两件事情,一是调用operator new函数获取内存,二是调用对象的构造函数,如:
string *ps = new string("Hello, world!");
它完成与下面代码相似的功能:
  void *memory = operator new(sizeof(string)); // 为String对象得到未经处理的内存
  call string::string("Hello, world!") on *memory; // 调用构造函数初始化内存中的对象
  string *ps = static_cast(memory); // ps指针指向新的对象

注意第二步中构造函数的调用只能由编译器完成,用户是不允许这样操作的,也就是说如果你想建立一个堆对象就必须用new操作符,不能直接像上面一样调用构造函数来初始化堆对象。

new操作符(new operator)是编译器内置的,其行为被语言固定下来,不受用户控制。但是它们所调用的内存分配函数也就是操作符new(operator new)则可以根据需要进行重载。试着回顾new操作符(new operator)与操作符new(operator new)的关系,如果你想在堆上建立一个对象,应该用new操作符。它既分配内存又为对象调用构造函数。如果你仅仅想分配内存,就应该调用operator new函数,它不会调用构造函数。如果你想定制自己独有的内存分配过程,你应该重载全局的operator new函数,然后使用new操作符,new操作符会调用你定制的operator new。如果你想在一块已经获得指针的内存里建立一个对象,应该使用placement new。

最后需要记住的一点是,delete和new一样具有以上的特性,只是需要注意的一点是delte操作符中是首先调用对象的析构函数,然后再调用operator delete函数的。

  2. 针对数组的new[]和delete[]操作

建立数组时new操作符(new[])的行为与单个对象建立(new)有少许不同:
第一是内存不再调用用operator new函数进行分配,代替以operator new[]函数(常称作array new)。它与operator new一样能被重载,允许定制数组的内存分配,就象定制单个对象内存分配一样。
第二个不同是new[]操作时调用构造函数的数量。对于new[]而言,在数组里的每一个对象的构造函数都必须被调用。

delete[]操作符的语义基本上和new[]相同,他们的实现类似这样:
     void * operator new[](size_t size)
     {
     cout << "new size of array in new[](): " << size << endl;
     int *g =(int *) malloc(sizeof(size));
     return g;
     }

     void operator delete[](void* p)
     {
      cout << "delete address of array pointer in delete[](): " << p << endl;
      free(p);
     }

  3. operator new和delete函数的实现

operator new实际上总是以标准的C malloc()完成,虽然并没有规定非得这么做不可。同样,operator delete也总是以标准得C free()来实现,不考虑异常处理的话他们类似下面的样子:
     extern void* operator new( size_t size )
     {
      if( size == 0 )
      size = 1; // 这里保证像 new T[0] 这样得语句也是可行的

      void *last_alloc;
      while( !(last_alloc = malloc( size )) )
      {
      if( _new_handler )
      ( *_new_handler )();
      else
      return 0;
      }
      return last_alloc;
     }

     extern void operator delete( void *ptr )
     {
      if(ptr) // 从这里可以看出,删除一个空指针是安全的
      free( (char*)ptr );
     }

我是马甲
2007-8-6 17:05:04 发表 编辑

操作符重载手册

来自 http://www.adintr.com/program/article/05.oper.html

一、重载规则

I.可以重载的操作符
+ - * / %
^ & | ~ !
= > < += -=
*= /= %= ^= &=
|= >> << >>= <<=
== != >= <= &&
|| ++ -- ->* ,
-> [] () operator new operator new[]
operator delete operator delete []


II.不能重载的操作符
:: . .* ? :
sizeof typeid new delete
static_cast dynamic_cast const_cast reinterpret_cast


III.基本规则

1.一元操作符可以是不带参数的成员函数[1]或带一个参数的非成员函数[1]。
2.二元操作符可以是带一个参数的成员函数[1]或带两个参数的非成员函数[1]。
3.operator=、operator[]、operator()、operator->只能定义为成员函数[1]。
4.operator->的返回值必须是一个指针或能使用->的对象。
5.重载 operator++ 和 operator-- 时带一个 int 参数表示后缀,不带参数表示前缀。
6.除 operator new 和 operator delete 外,重载的操作符参数中至少要有一个非内建数据类型。
7.x@y 搜索范围为:x 成员函数--> 全局函数/X所在名字空间中的函数/Y所在名字空间中的函数/X的友元函
数/Y的友元函数。
8.重载的的操作符应尽量模拟操作符对内建类型的行为。

二、使用重载


I.操作符重载的一些建议

1.只将会改变第一个参数的值的操作符(如: +=)定义为成员函数,而将返回一个新对象的操作符(如: +)定义为非成员函数(并使用 += 来实现)。
2.只有非成员函数才能在左参数上实施性别转换,如果需要进行转换则应将操作符定义为非成员函数。
3.对一元操作符, 为避免隐式转换最好将其重载为成员函数。
4.对二元操作符, 为能在左操作数上能进行和右操作数一样的隐式转换, 最好将其重载为非成员函数。
5.为了遵照使用习惯,operator>>、operator<< 应定义为非成员函数。
6.重载 operator[] 之类的操作符, 应尽量提供 const 版本和非 const 版本。
7.关于将操作符定义为成员或非成员可参考以下建议:操作符 建 议

所有一元操作符 成员
= () [] -> 必须为成员
+= -= /= *= ^= &= != %= >>= <<= 成员
其它二元操作符 非成员



8.如果默认操作符已经可以施用于你的型别上, 则应尽量避免重载此操作符. 如 operator, 、operator&(取地址) 等等.

II. 重载 operator new

1.为什么要重载 operator new ?

[效率问题] 通常系统默认提供的分配器速度极慢, 而且分配小型对象时空间浪费严重.
[改变行为] 默认的分配器失败时会抛出异常, 或许你想改变这种行为.

2. operator new 的行为

[区分三个不同的 new]
new 操作符(new 表达式, new operator, new expression): 通常我们调用 X * pX = new X 时使用的就是这个操作符, 它由语言内建, 不能重载, 不能改变其行为. 它包括分配内存的 operator new 和调用构造函数的 placement new.
operator new :opeator new 是一个函数, void * operator new(size_t size) . 它分配指定大小的内存, 可以被重载, 可以添加额外的参数, 但第一个参数必须为 size_t. operator new 除了被 new operator 调用外也可以直接被调用: void * rawMem = operator new(sizeof(X)).
placement new : placement new 在一块指定的内存上使用构造函数, 包含头文件 之后也可以直接使用 placement new: X * pX = new (rawMem) X. [2]

与 new operator 类似, 对于 delete operator, 也存在 operator delete: void operator delete(void *), 析构方法 pX->~X().

[operator new 的错误处理]
默认的 operator new 在内存分配失败时将会抛出 std::bad_alloc 异常; nothrow new [3]
(X * pX = new (nothrow) X) 在内存分配失败时将会返回 0 . 这种行为可以通过设置 new-handler 来改变. new-handler 是一个回调函数指针, typedef void(*new_handler)(void). 通过 set_new_handler(new_handler) 函数设置回调句柄后, 如果分配内存失败, operator new 将会不断的调用 new-handler 函数, 直到找到足够的内存为止. 为了避免死循环, new-handler 函数必须具备以下行为之一:
(1).找到可用的内存.
(2).安装其它的 new-handler 函数.
(3).卸除 new-handler, 即 set_new_hanlder(0), 这样下此循环将恢复默认行为抛出异常或返回 0.
(4).抛出异常.
(5).保存错误日志, 退出程序.

3.准备重载 operator new

重载 operator new 时需要兼容默认的 operator new 错误处理方式. 另外, C++ Standard 规定当要求的内存为 0 byte 时也应该返回有效的内存地址. 所以 operator new 的重载实现应大致如下:
void * ... operator new(size_t size ... )
{
if(size == 0)
size = 1;

while(1)
{
... // allocate memery
if(allocate sucessfull)
return ... // return the pointer.

new_handler curhandler = set_new_handler(0);
set_new_handler(curhandler); // get current new handler

if(curhandler == 0)
(*curhandler)();
else
throw std::bad_alloc();
}
}
重载 operator delete 简单许多, 只需注意 C++ Standard 要求删除一个 NULL 是安全的即可.

4.重载 operator new

opeator new 的重载和其它操作符大不相同.首先, 即使你不重载, 默认的 operator new 也可施用于你的自定义型别上(operator, 也具有此特性), 而其它操作符如果不进行重载就无法使用. 其次, 其它重载其它操作符时参数个数都是固定的, 而 operator new 的参数个数是可以任意的, 只需要保证第一个参数为 size_t, 返回类型为 void * 即可, 而且其重载的参数类型也不必包含自定义类型. 更一般的说, operator new 的重载更像是一个函数的重载, 而不是一个操作符的重载.

[★ 用不同的参数重载 operator new]

通过使用不同的参数类型, 可以重载 operator new, 例如 :
void * operator new(size_t size, int x, int y, int z)
{
...
}

X * pX = new (1, 2, 3) X;
你还可以为 operator new 的重载使用默认值, 其原则和普通函数重载一样, 只要不造成和已存在的形式发生冲突即可. 可能你已经想到了, 你甚至还可以在 operator new 中使用不定参数, 如果你真的需要的话.
void * operator new(size_t size, int x, int y = 0, int z = 0)
{
...
}

X * pX = new (10) X;
Y * pY = new (10, 10) Y;
Z * pZ = new (10, 10, 10) Z;

...
void * operator new(size_t size, ...)
...
在全局空间中也可直接重载 void * operator new(size_t size) 函数, 这将改变所有默认的 new 操作符的行为, 不建议使用.

[★ 重载 class 专属的 operator new]

为某个 class 重载 operator new 时必须定义为类的静态函数[4], 因为 operator new 会在类的对象被构建出来之前调用. 即是说调用 operator new 的时候还不存在 this 指针, 因此重载的 operator new 必须为静态的. 当然在类中重载 operator new 也可以添加额外的参数, 并可以使用默认值.另外, 和普通函数一样, operator new 也是可以继承的.
class X{
...
static void * operator new(size_t size); // ... (1)
static void * operator new(size_t size, int); // ... (2)
};

class Y : public X{
...
};

class Z : public X{
...
static void * operator new(size_t size); // ... (3)
};

X * pX1 = new X; // call (1)
X * pX2 = ::new X; // call default operator new
X * pX3 = new (0) X; // call (2)

Y * pY1 = new Y; // call (1)

Z * pZ1 = new Z; // call (3)
Z * pZ2 = ::new Z; // call default operator new
Z * pZ3 = X::new Z; // error, no way to call (1)
Z * pZ4 = new (0) Z; // error, no way to call (2)

5.重载 operator delete

如果你重载了一个 operator new, 记得一定要在相同的范围内重载 operator delete. 因为你分配出来的内存只有你自己才知道如何释放. 如果你忘记了, 编译器不会给你任何提示, 它将会使用默认的 operator delete 来释放内存. 这种忘记的代价是惨重的, 你得时刻在写下 operator new 的同时写下 operator delete.
如果在类中使用 operator delete, 也必须将其声明为静态函数. 因为调用 operator delete 时对象已经被析构掉了. operator delete 的重载可以有两种形式:
(1) void operator delete(void * mem)
(2) void operator delete(void * mem, size_t size)
并且这两种形式的 operator delete 可以同时存在, 当调用 delete px 时, 如果 (1) 式存在的话将调用 (1) 式. 只有在 (1) 式不存在时才会调用 (2) 式. 对第 (2) 种形式的 operator delete, 如果用基类指针删除派生类对象, 而基类的析构函数没有虚拟的时候, size 的值可能是错误的.

三、重载参考

展开全部

+ const Carrot operator+(const Carrot& lhs, const Carrot& rhs)
· const Carrot operator-(const Carrot& lhs, const Carrot& rhs)
+ const Carrot operator*(const Carrot& lhs, const Carrot& rhs)
· const Carrot operator/(const Carrot& lhs, const Carrot& rhs)
· const Carrot operator%(const Carrot& lhs, const Carrot& rhs)
· const Carrot operator^(const Carrot& lhs, const Carrot& rhs)
+ const Carrot operator&(const Carrot& lhs, const Carrot& rhs)
· const Carrot operator|(const Carrot& lhs, const Carrot& rhs)
+ const Carrot Carrot::operator-() const
· const Carrot Carrot::operator~() const
· bool Carrot::operator!() const
· bool operator>(const Carrot& lhs, const Carrot& rhs)
· bool operator<(const Carrot& lhs, const Carrot& rhs)
+ Carrot& Carrot::operator=(const Carrot& rhs)
+ Carrot& Carrot::operator+=(const Carrot& rhs)
· Carrot& Carrot::operator-=(const Carrot& rhs)
· Carrot& Carrot::operator*=(const Carrot& rhs)
· Carrot& Carrot::operator/=(const Carrot& rhs)
· Carrot& Carrot::operator%=(const Carrot& rhs)
· Carrot& Carrot::operator^=(const Carrot& rhs)
· Carrot& Carrot::operator&=(const Carrot& rhs)
· Carrot& Carrot::operator|=(const Carrot& rhs)
+ istream& operator>>(istream& _IStr, Carrot& rhs)
+ ostream& operator<<(ostream& _OStr, const Carrot& rhs)
+ const Carrot operator>>(const Carrot& lhs, int rhs)
· const Carrot operator<<(const Carrot& lhs, int rhs)
+ Carrot& Carrot::operator>>=(int rhs)
· Carrot& Carrot::operator<<=(int rhs)
+ bool operator==(const Carrot& lhs, const Carrot& rhs)
· bool operator!=(const Carrot& lhs, const Carrot& rhs)
· bool operator>=(const Carrot& lhs, const Carrot& rhs)
· bool operator<=(const Carrot& lhs, const Carrot& rhs)
+ bool operator&&(const Carrot& lhs, const Carrot& rhs) X
· bool operator||(const Carrot& lhs, const Carrot& rhs) X
+ Carrot& Carrot::operator++()
+ const Carrot Carrot::operator++(int)
· Carrot& Carrot::operator--()
· const Carrot Carrot::operator--(int)
+ const Carrot operator,(const Carrot& lhs, const Carrot& rhs) X
+ const PMFC Carrot::operator->*(ReturnType (T::*pmf)()) const
+ const Carrot* Carrot::operator&() const X
+ Coca& Carrot::operator*()
· const Coca& Carrot::operator*() const
+ Coca* Carrot::operator->()
· const Coca* Carrot::operator->() const
+ Coca& Carrot::operator[](KeyType index)
· const Coca& Carrot::operator[](KeyType index) const
+ AnyType Carrot::operator()(...)
+ static void* Carrot::operator new(size_t size, ...)
· static void* Carrot::operator new[](size_t size, ...)
+ static void Carrot::operator delete(void * memory, size_t size)
· static void Carrot::operator delete[](void * momery, size_t size)

▲注:
1.本文中成员函数仅指非静态成员函数;非成员函数包括自由函数,静态成员函数,友元函数。←
2.这种调用方法使用了 operator new 的一种重载形式: void * operator new(size_t, void * buff) . 这种重载方式直接返回了传入的内存地址, 所以这种调用方式中 operator new 其实什么也没做, 于是这个 new operator 调用只使用了其中调用构造函数的部分, 相当于是在调用 placement new. 该重载在 VC7.1 中定义如下:
#define __PLACEMENT_NEW_INLINE
inline void *__cdecl operator new(size_t, void *_Where) _THROW0()
{ // construct array with placement at _Where
return (_Where);
}

3.和 placement new 一样, nothrow new 也是 operator new 的一种重载形式, nothrow 是 中声明的一个对象. 该重载在 VC7.1 中的声明为:
...
struct nothrow_t
{ // placement new tag type to suppress exceptions
};

extern const nothrow_t nothrow; // constant for placement new tag
...
void *__cdecl operator new(size_t, const std::nothrow_t&)
_THROW0();

4.在 VC7.1 中, 不将 operator new 声明为静态函数仍然可以通过编译, 但是如果在函数里面使用了非静态成员则会产生编译错误. 可见, 即使不将 operator new 声明为静态函数编译器也会将其作为静态函数来处理的. 使用是即使编译器默认把 operator new 当作静态函数也应该显式的将其声明为 static 函数. ←
5. 即对 && 操作符如果左操作数为 false, 则直接返回 false 而不会去检测右操作数; 对 || 操作符如果左操作数为 true, 则直接返回 true 而不会检测右操作数的值. 于是类似这样的代码可以是安全的:
if( (p != NULL) && strlen(p) < 10 )...
如果 p == NULL, strlen 就不会被调用. 但是重载的 operator&& 和 operator|| 是作为一个函数, 要在所有的参数求值完成后才会调用此操作符.←

▲参考资料:

1. Bjarne Stroustrup.The C++ Programming Language(Special Edition)
2. Scott Meyers.Effective C++(2nd Edition)
3. Andrei Alexandrescu.Modern C++ Design
4. Robert B.Murray.C++ Strategies and Tactics
5. Scott Meyers.More Effective C++
6. John Lakos.Large-Scale C++ Software Design
7. Scott Meyers.Implementing operator->* for Smart Pointers

我是马甲
2007-8-8 11:42:36 发表 编辑

文中的 __heapwalk() 应该是笔误. 看下文.

--------------------------------------------------
以下来自 http://learn.gxtc.edu.cn/NCourse/VC/yxk-h.htm
--------------------------------------------------

heapadd
heapchk
hapmin
heapset
heapwalk


heapadd
将存储器加到该堆中。
int heapadd(void *memblock,sizet size);
例程 需要的头文件 可选的头文件 兼容性
heapadd
对于另外兼容性的信息,参见引言中的兼容性

LIBC.LIB 单线程静态库,零售版本
LIBCMT.LIB 多线程静态库,零售版本
MSVCRT.LIB MSVCRT.DLL的输入库,零售版本
返回值
如果成功,heapadd返回0;
否则该函数返回-1并设置errno为ENOSYS。
参数
memblock
堆存储器的指针。
Size
加入存储器的尺寸,以字节为单位。
说明
开始于Visual C++ 4.0版本的低层堆结构移到了C运行库来支持新的调试特征。
结果,heapadd不再支持任何Win32平台,当从这种类型的应用调用时,它立即返回-1。参见
free,heapchk,heapmin,heapset,heapwalk,malloc,realloc

返回顶端


heapchk
在堆中运行一致性检测。
int heapchk(void);
例程 需要的头文件 可选的头文件 兼容性
heapchk Win NT
对于另外兼容性的信息,参见引言中的兼容性

LIBC.LIB 单线程静态库,零售版本
LIBCMT.LIB 多线程静态库,零售版本
MSVCRT.LIB MSVCRT.DLL的输入库,零售版本
返回值
heapchk
返回如下显式常量之一,它们定义在MALLOC.H中
HEAPBADBEGIN
初始头信息是坏的或不能找到。
HEAPBADNODE
坏结点已找到或堆损坏了。
HEAPBADPTR
堆的指针无效。
HEAPEMPTY
堆没有初始化。
HEAPOK
堆是一致的。
另外,如果出现一个错误,heapchk设置errno为ENOSYS。
说明
heapchk
函数通过检测堆的最小一致性来帮助调试堆有关的问题。
例子/*
HEAPCHK.C: This program checks the heap for *
consistency and prints an appropriate message.
#include
#include
void main( void )
{ intheapstaus;
char *buffer;
/* Allocate and deallocate some memory */
if( (buffer = (char *)
malloc( 100 )) != NULL )free( buffer );/*
Check heap status */
heapstatus = heapchk();
switch( heapstatus )
{ case HEAPOK:printf(" OK - heap is fine\n" );
break; case HEAPEMPTY:printf(" OK - heap is empty\n" );
break;
case HEAPBADBEGIN:printf( "ERROR - bad start of heap\n" );
break;
case HEAPBADNODE:printf( "ERROR - bad node in heap\n" );
break;
}
}
输出结果
OK - heap is fine
参见
heapadd,heapmin,heapset,heapwalk

返回顶端


hapmin
释放未用的堆存储器给操作系统。
int hapmin(void);
宏 需要的头文件 可选的头文件 兼容性
hapmin Win NT
对于另外兼容性的信息,参见引言中的兼容性

LIBC.LIB 单线程静态库,零售版本
LIBCMT.LIB 多线程静态库,零售版本
MSVCRT.LIB MSVCRT.DLL的输入库,零售版本
返回值如果成功,heapmin返回0;否则该函数返回-1并设置errno为ENOSYS。
说明
heapmin函数通过释放未用的堆存储器给操作系统来最小化堆。
注意:在Visual C++ 4.0版本的低层堆结构移到了C运行库来支持新的调试特征。结果,heapmin
仅支持的Win32平台是Windows NT。当从其它非Win32平台调用时,它立即返回-1并设置errno为ENOSYS。
参见free,heapadd,heapchk,heapset,heapwalk,malloc

返回顶端


heapset
检测堆的最小一致性并设置自由项为一个指定的值。
int heapset(unsigned int fill);
例程 需要的头文件 可选的头文件 兼容性
heapset Win NT
对于另外兼容性的信息,参见引言中的兼容性

LIBC.LIB 单线程静态库,零售版本
LIBCMT.LIB 多线程静态库,零售版本
MSVCRT.LIB MSVCRT.DLL的输入库,零售版本
返回值
heapset
返回如下显式常量之一,它们定义在MALLOC.H中
HEAPBADBEGIN
初始头信息是坏的或不能找到。
HEAPBADNODE
堆损坏了或坏结点已找到。
HEAPEMPTY
堆没有初始化。
HEAPOK
堆是一致的。
另外,如果出现一个错误,heapset设置errno为ENOSYS。
说明
heapchk
函数通过检测堆的最小一致性来帮助调试堆有关的问题。
参数
fill
填充字符。
说明
heapset
函数给出自由存储器位置或己经无意覆盖的结点。
heapset检测堆的最小一致性,然后设置该堆的自由项的每个字节为fill值。
这个己知值显示包含自由结点的堆的存储器位置,该结点包含无意写到自由存储器的数据注意:在Visual C++ 4.0版本的低层堆结构移到了C运行库来支持新的调试特征。
结果,
heapset
仅支持的Win32平台是Windows NT。
当从其它非Win32平台调用时,它返回HEAPOK并设置errno为ENOSYS
例子
/* HEAPSET.C: This program checks the heap and *
fills in free entries with the character 'Z'.
*/#include
#include
#include
void main( void )
{ intheapstatus;
char *buffer;
if( (buffer = malloc( 1 )) == NULL )/*
Make sure heap is*/exit( 0 );
/*initialized
*/ heapstatus = heapset( 'Z' );/*
Fill in free entries */
switch( heapstatus )
{ case HEAPOK:printf(" OK - heap is fine\n" );
break;
case HEAPEMPTY:printf(" OK - heap is empty\n" );
break;
case HEAPBADBEGIN:printf(" ERROR - bad start of heap\n" );
break;
case HEAPBADNODE:printf(" ERROR - bad node in heap\n" );
break;
}
free( buffer );
}
输出结果
OK - heap is fine
参见
heapadd,heapchk,heapmin,heapwalk

返回顶端


heapwalk
遍历堆并返回下一个项的信息。
int heapwalk(HEAPINFO *entryinfo);
例程 需要的头文件 可选的头文件 兼容性
heapwalk Win NT
对于另外兼容性的信息,参见引言中的兼容性
库 LIBC.LIB单线程静态库,零售版本
LIBCMT.LIB 多线程静态库,零售版本
MSVCRT.LIB MSVCRT.DLL的输入库,零售版本
返回值
heapwalk
返回如下整数显式常量,它们定义在MALLOC.H中:
HEAPBADBEGIN
初始头信息是坏的或不能找到。
HEAPBADNODE
坏结点已找到或堆损坏了。
HEAPBADPTRHEAPINFO
结构的pentry域不包含堆的有效指针。
HEAPEND
成功到达堆的末尾。
HEAPEMPTY
堆没有初始化。
HEAPOK
迄今还没有错误;
HEAPINFO
结构包含下一个项的信息。
另外,如果出现一个错误,heapchk设置errno为ENOSYS。
参数
entryinfo包含堆信息的缓冲区。
说明
heapwalk
函数帮助调试程序中堆有关的问题。该函数通过堆,每次调用遍历一个项并返回类型HEAPINFO结构的指针,该结构包含下一个堆项的信息。
HEAPINFO
类型定义在MALLOC.H中包含如下元素:
int*pentry堆项指针。
sizetsize堆项尺寸。
Intuseflag
指出堆项是否正在使用的标志。
调用heapwalk返回HEAPOK时则在size域中存储项的尺寸并设置useflag域为FREEENTRY或USEDENTRY(这两个常量定义在MALLOC.H中)。为了获取堆中第一个项的信息,传送给heapwalk一个其pentry成员为NULL的HEAPINFO结构的指针。注意:从Visual C++ 4.0版本开始,低层堆结构移到了C运行库来支持新的调试特征。
结果,
heapwalk
仅支持的Win32平台是Windows NT。
当从其它非Win32平台调用时,它返回HEAPEND并设置errno为ENOSYS。
例子/*
HEAPWALK.C: This program "walks" the heap, starting *
at the beginning (pentry = NULL). It prints out each *
heap entry's use, location, and size. It also prints *
out information about the overall state of the heap as *
soon as heapwalk returns a value other than HEAPOK. *
/#include
#include void heapdump( void );
void main( void );
{ char *buffer; heapdump();
if( (buffer = amlloc( 59 )) != NULL )
{heapdump();
free( buffer );
} heapdump();
}
void heapdump( void )
{ HEAPINFO hinfo;
int heapstatus; hinfo.pentry = NULL;
while( ( heapstatus = heapwalk( &hinfo ) ) == HEAPOK )
{ printf( "%6s block at %Fp of size %4.4X\n",( hinfo.useflag == USEDENTRY ? "USED" : "FREE" ),hinfo.pentry, hinfo.size );
}
switch( heapstatus )
{ case HEAPEMPTY:printf(" OK - empty heap\n" );
break;
case HEAPEND:printf(" OK - end of heap\n" );
break; case HEAPBADPTR:printf(" ERROR - bad pointer to heap\n" );
break; case HEAPBADBEGIN:printf(" ERROR - bad start of heap\n" );
break; case HEAPBADNODE:printf(" ERROR - bad node in heap\n" );break;
}
}
输出
USED block at 002C0004 of size 0014
USED block at 002C001C of size 0054
USED block at 002C0074 of size 0024
USED block at 002C009C of size 0010
USED block at 002C00B0 of size 0018
USED block at 002C00CC of size 000C
USED block at 002C00DC of size 001C
USED block at 002C00FC of size 0010
USED block at 002C0110 of size 0014
USED block at 002C0128 of size 0010
USED block at 002C013C of size 0028
USED block at 002C0168 of size 0088
USED block at 002C01F4 of size 001C
USED block at 002C0214 of size 0014
USED block at 002C022C of size 0010
USED block at 002C0240 of size 0014
USED block at 002C0258 of size 0010
USED block at 002C026C of size 000C
USED block at 002C027C of size 0010
USED block at 002C0290 of size 0014
USED block at 002C02A8 of size 0010
USED block at 002C02BC of size 0010
USED block at 002C02D0 of size 1000
FREE block at 002C12D4 of size ED2C
OK - end of heap
参见heapadd,heapchk,heapmin,heapset

我是马甲
2007-8-8 11:50:17 发表 编辑

一个相关帖子
--------------------------------------------------
如果判断一个指针是否是无效指针?

我用的是VC6

使用NULL判定指针无效~~

望前辈指点~
--------------------------------------------------

用标准的 new ( 未经重载作特别处理 )来分配内存的话, 下面这个比较是无效的:

const unsigned int num = 100;
int* pInt = new int[ num ];

if ( pInt != NULL )
{
// NOTE!!!, maybe the pointer invalid
// do something ...
}

因为默认的 new 在分配内存不成功的时候也不会返回 NULL( C++ 标准如此, 大师们也如是说 :) ), 真要作类似的辨断,可以这样:

const unsigned int num = 100;
int* pInt = new( nothrow ) int[ num ];

if ( pInt != NULL )
{
// do something ...
}

在 Windows 下或许可以用API IsBadReadPointer / IsBadWritePointer ( 不知有没拼错哟 ) 来检测有效性.


总数:5 页次:1/1 首页 尾页  
总数:5 页次:1/1 首页 尾页  


所在合集/目录



发表评论:
文本/html模式切换 插入图片 文本/html模式切换


附件:



NEWBT官方QQ群1: 276678893
可求档连环画,漫画;询问文本处理大师等软件使用技巧;求档softhub软件下载及使用技巧.
但不可"开车",严禁国家敏感话题,不可求档涉及版权的文档软件.
验证问题说明申请入群原因即可.

Copyright © 2005-2020 clq, All Rights Reserved
版权所有
桂ICP备15002303号-1