又犯了个错误,记录下来。
最近在做socket通信,数据包头里包含了一些信息,而且数据包是经过H264编码的,需要解码才能显示图片,当时为了图快直接将收到的数据包(unsigned char)放入队列里,然后在主线程里操作,但是做完之后发现用户体验很不好,数据量小还看不出来,数据量大的时候就发现问题了,入队速度很快,读到一个数据包就往里塞,但是出队速度明显跟不上入队速度,因为的到的数据需要解析和解码,这就导致了整个对话框窗口卡死,无法操作,所以后期调整优化的时候自然而然想到使用多线程,并且为了方便显示不能往队列里塞原始的数据包,得先将数据包解析之后把有用的数据放进去,于是想到使用结构体,队列里存的结构体指针,这样显示图片就很简单了,但是当我写完之后发现编译通过了,运行的时候程序总是在结构体那里死掉,提示结构体是未声明的,以前没有碰到过这个问题,自己想了会才弄明白,是一个初学者常犯的错误(好吧我也是初学者),问题以及解决方法如下:
在头文件中声明一个简单的结构体变量:
struct PinData
{
unsigned char *pin;
int len;
};
这里面unsigned char *pin;这个变量很有意思,学c的都知道一碰到指针就该小心,实际上指针变量如果用好了会非常灵活简单,就以我那个项目而言,这个变量必须为指针,因为我们不知道进来的数据到底是多少,所以要动态分配内存,假设pin为咱们将来的接收到的图片数据,len为数据长度,下面是我第一次错的时候写法:
DWORD WINAPI OnPicRecv(LPVOID lp)
{
CMyDlg *pMgr=reinterpret_cast<CMyDlg*>(lp);
/
//
while(1)
{
char *buf = new char[65535];
memset(buf, 0, 65535);
int socklen = sizeof(SOCKADDR_IN);
int len = 0;
len = recvfrom(pMgr->m_picsock, buf, 65535, 0,
(SOCKADDR*)&pMgr->addrPicSock,&socklen);
PinData *pd;
pd->pin = (unsigned char *)buf;
pd->len = len;
///
pMgr->H264Que.push(pd);
/
}
}
编译通过,但是运行程序在pd->pin = (unsigned char *)buf;这句话的时候就死了,提示pd未初始化,然后我自作聪明的把PinData *pd;改成PinData *pd = NULL;结果还是一样的,这个时候在利用vs的调用堆栈往回一步步分析,找到了答案。
我的解决过程是这样的:pd->pin = (unsigned char *)buf;之前是没有问题的,但是我明明声明了指针变量,虽然他不指向任何数据域但是它本身应该有个地址的值才对啊,为什么直接报错说没有初始化?回想在没有用结构体之前,我也是直接把数组指针buf入队的的,为什么那时候没有事?
事实上,这两次的操作有所不同,buf在入队前我们使用new char为它动态分配内存,即使是向队列里存入指针实际上也把指针所指向的内容存了进去, 当我们执行完一次读得过程,你声明的指针生命周期已经结束,系统会将他析构,一个最基本的数据结构是这样,复杂的数据结构虽有所不同但基本原则也应该是如此,所以我猜测如果入队的仅仅是一个指针但是没有初始化,那么也会出现这个问题,所以我做了个测试:
1.char * a;
que.push(a);
2.char a[2] = {0};
que.push(a);
大家可以猜猜那个程序能正常运行。
答案是1不行2可以。
所以在使用结构体指针之前一定要注意他的生命周期和相应的初始化,把PinData *pd;改成PinData *pd = new PinData();问题就完美解决了,最后别忘了释放内存,否则会内存泄漏,这里面要注意的是有两个需要释放,一个是PinData结构体指针本身new的内存,另一个是PinData结构体指针的成员pin所指向的内存,要先释放成员再释放结构体,否则会出现二次delete的错误。