这个函数如果运行,其中n由于是全局静态变量,位于基栈,ch和pBuff这两个函数内部变量,ch位于浮动栈,而pBuff指向的由malloc分配的内存区,则位于堆栈。

  在内存理解上,的例子是线程启动时的参数传递。

  函数启动一个线程,很多时候需要向线程传参数,但是线程是异步启动的,即很可能启动函数已经退出了,而线程函数都还没有正式开始运行,因此,绝不能用启动函数的内部变量给线程传参。道理很简单,函数的内部变量在浮动栈,但函数退出时,浮动栈自动拆除,内存空间已经被释放了。当线程启动时,按照给的参数指针去查询变量,实际上是在读一块无效的内存区域,程序会因此而崩溃。

  那怎么办呢?我们应该直接用malloc函数给需要传递的参数分配一块内存区域,将指针传入线程,线程收到后使用,后线程退出时,free释放。

  我们来看例子:


//这个结构体是参数表
typedef struct _CListen_ListenAcceptTask_Param_
{
    Linux_Win_SOCKET m_nSocket;
    //其他参量… …
}SCListenAcceptTaskParam;
//习惯性写法,设置结构体后,立即声明结构体的尺寸,为后续malloc提供方便
const ULONG SCListenAcceptTaskParamSize = sizeof(SCListenAcceptTaskParam);
//这里接收到连接请求,申请参数区域,将关键信息带入参数区域,帮助后续线程工作。
bool CListen::ListenTaskCallback(void* pCallParam,int& nStatus)
{
    //正常的函数逻辑… …
    //假定s是accept到的socket,需要传入后续线程工作
    //在此准备一块参数区域,从远堆上申请
    SCListenAcceptTaskParam* pParam = (SCListenAcceptTaskParam*) malloc(SCListenAcceptTaskParamSize);
    //给参数区域赋值
    pParam->m_nSocket = s;
    //此处启动线程,将pParam传递给线程… …
    //正常的函数逻辑… …
}
//这是线程函数,负责处理上文accept到的socket
bool CListen::ListenAcceptTask(void* pCallParam,int& nStatus)
{
    //第一句话是强制指针类型转换,获得外界传入的参数区域
    SCListenAcceptTaskParam* pParam= (SCListenAcceptTaskParam*)pCallParam;
    //正常的函数逻辑… …
    //退出前,必须要做的工作,确保资源不被泄露
    close(pParam->m_nSocket); //关闭socket
    free(pCallParam); // free传入的参数区域
    //… …

}
 


  四、内存bug

  无规则的滥用内存和指针会导致大量的bug,程序员应该对内存的使用保持高度的敏感性和警惕性,谨慎地使用内存资源。

  使用内存时容易出现的bug是:

  (1)坏指针值错误:在指针赋值之前用它来引用内存,或者向库函数传送一个坏指针,第三种可能导致坏指针的原因是对指针进行释放之后再访问它的内容。可以修改free语句,在指针释放之后再将它置为空值。


free(p); p = NULL;


  这样,如果在指针释放之后继续使用该指针,至少程序能在终止之前进行信息转储。

  (2)改写(overwrite)错误:越过数组边界写入数据,在动态分配的内存两端之外写入数据,或改写一些堆管理数据结构(在动态分配内存之前的区域写入数据很容易发生这种情况)


p=malloc(256);p[-1]=0;p[256]=0;


  (3)指针释放引起的错误:释放同一个内存块两次,或释放一块未曾使用malloc分配的内存,或释放仍在使用中的内存,或释放一个无效的指针。一个极为常见的与释放内存有关的错误是在 for(p=start;p=p->next) 这样的循环中迭代一个链表,并在循环体内使用 free(p) 语句。这样,在下一次循环迭代时,程序会对已经释放的指针进行解除引用操作,从而导致不可预料的结果。

  我们可以这样迭代:


struct node *p, *tart, *temp;
for(p = start; p ; p = temp)
{
    temp = p->next;
    free(p);
}