某个内存区域,需要在分配空间并成功初始化之后才可以安全地使用。在一个线程中分配内存空间并初始化,其它线程中读或者写该内存空间。
本来的思路是,读写内存时,如果指向该内存的指针为空,则不能使用,只有非空时,才可以使用。这思路本来不错。但是,如果不注意,也会存在一些问题:例如,代码如下:
MyStruct *ptr ; //全局指针变量
//某线程中的分配内存并初始化的代码
{
ptr = (MyStruct *) malloc( sizeof(MyStruct) );
if( NULL==ptr )
{
printf("Error");
return;
}
MyStructInit(ptr);
}
//释放内存的代码
{
if( NULL==ptr )
{
printf("Error");
return;
}
free(ptr);
}
//另一个线程中的读写内存的代码
{
if(NULL==ptr)
{
printf("Error");
return;
}
//读写操作
} |
这段代码问题很多,我们一一来看:
1、首先全局指针变量定义时未初始化,其值不确定,则可能在分配内存之前就为非NULL值了。如此一来,希望通过判断该变量是否为NULL来避免对未分配内存的访问,就无法实现了。应该在定义的同时就进行初始化:
2、我们继续来看内存的分配和初始化,内存分配之前并没有对全局指针变量进行判空操作。也就是说,如果之前曾经分配过内存,由于误用或者流程错误而再度调用该段代码时,该段代码并不能发现并给出警告。而在多线程环境中,如果代码能发现一些这样的错误,很有利于代码的调试。所以,应该在内存分配之前进行一次判断。
if( NULL!=ptr )//注意,这个也很重要,这可以避免内存的重复分配导致的内存泄露 {
printf("Error");
return ; } |
3、内存分配之后,要进行分配成功与否的判断,这很重要,因为内存分配并不是一定能成功的。但是,到此为止,也不是说内存分配和初始化就没有问题了。原因在于,内存分配之后,在对内存初始化之前,直接就赋值给了ptr。在多线程环境中,这样也是不安全的,因为在内存分配成功之后和内存初始化完成之前,有可能发生调度(linux中会吗?),这时候,ptr已经被赋值了,所以,另一个线程中通过判断ptr是否为NULL来进行的保护已经失效了,此时,就有可能去访问这个为被初始化的内存空间。
可以改为如下方式:
//某线程中的分配内存并初始化的代码
{
if( NULL!=ptr )
{
printf("Error");
return ;
}
MyStruct *tmpPtr = (MyStruct *) malloc( sizeof(MyStruct) );
if( NULL==tmpPtr )
{
printf("Error");
return;
}
MyStructInit(tmpPtr);
ptr = tmpPtr;
} |
4、同理,释放代码时,知道先判断代码是否已经释放,也很重要,避免内存的重复释放。当然,这要求在内存释放之后,要及时将ptr置为NULL。最开始给出的代码中,就没有置为NULL这步操作。那么,修改为如下形式,是否就可以了呢?
//释放内存的代码
{
if( NULL==ptr )
{
printf("Error");
return;
}
free(ptr);
ptr=NULL;
} |
仍然,不行。
5、因为free也是非线程安全的,而且在free之后,ptr置为NULL之前,另一线程有可能会去进行读写操作(linux中线程会发生这种情况?)。那会死,因为ptr尚未置为NULL,读写操作是可以进行的,但是实际上内存又已经free了。这会导致错误。代码,可作如下修改:
//释放内存的代码
{
if( NULL==ptr )
{
printf("Error");
return;
}
MyStruct *tmpPtr = ptr;
ptr=NULL;
free(tmpPtr);
} |
但是,这样是否就完全没有问题了呢?在单cpu中,这对线程来说,应该就是安全的了。但是,对于中断来说,则不能保证安全性。因为,赋值操作,并不一定是不可中断的,即使赋值操作是不可中断的,那之后的那些操作也是可中断的。所以,如果是中断和线程或者其它中断要共享某资源,是不能用这种方法进行互斥访问的。
另外,在多cpu系统中,上面的互斥原理,可能也不能行得通。原因就在于,通过这样一个全局变量去进行互斥保护另一资源,可能行不通。因为,判断ptr为非空后,继续操作时,并没有对资源进行锁定。
总之,这种使用一个全局变量进行安全保护的思路,还需要进一步探讨。
阅读(832) | 评论(0) | 转发(0) |