函数中随处return,是造成我们资源泄露和程序死锁的主要根源。很多同志写过类似的代码,函数中创建了和引用了多个资源,中间使用的过程中出错了,程序return,经典的代码是这样的:
void fun()
{
Lock(mutex);
mem = malloc(size);
if (null == mem)
{
return; //死锁
}
fh = fopen(“test.txt”);
if (fh)
{
return; //死锁+内存泄露
}
if (fwrite(“abc”, fh) < 0)
{
return; //死锁+内存泄露+句柄泄露
}
fclose(fh);
free(mem);
Unlock(mtex);
}
上述代码中的资源泄露是显而易见的,编程新手却很容易写出这样的代码,但问题却不容易发现,因为异常流程通常不会发生,而一旦发生了,就是一场噩梦:不好跟踪也不好重现。对吃过这种亏编程老手而言,处理起来会格外小心,而是有了如下版本的代码(我们的工程中类似的代码随处可见):
void fun()
{
Lock(mutex);
mem = malloc(size);
if (null == mem)
{
Unlock(mutex); //锁解除了,很好
return;
}
fh = fopen(“test.txt”);
if (fh)
{
Free(mem);
Unlock(mutext); //错误处理加了一行
return;
}
if (fread(mem, size, fh) < 0)
{
Free(mem);
Flcose(fh);
Unlock(mutext); //错误处理又加了一行…
return;
}
fclose(fh);
free(mem);
Unlock(mtex);
}
这个版本的代码没什么逻辑问题,在每个异常环节,把该处理的都处理完了。看起来非常完美,却依然存有大坑。首先,我们的异常处理流程越来越长,重复的代码像火车车厢一样越串越长,代码维护非常不方便,某天其中一个异常处理流程要修改了,我们需要修同步多处,万一漏了某处(事实上不是万一,而是经常),问题没有修改彻底;更为严重的是,冗于的异常处理会让大家感到疲劳,某一天函数中又要加入新的资源引用或异常处理逻辑时,在先驱的引导下,我们的人依然还是会犯同样的错误:要么什么都不管提前返回,或者处理不充分就走了,结果还是同样的资源泄露。
如何解决,Linux内核代码给了我们很好的解决方案,合理地使用goto语句,将异常处理定向到统一的地方,既解决了资源泄露隐患,也保持了正常流程代码的简洁性,这是改进后的版本:
void fun()
{
Lock(mutex);
mem = malloc(size);
if (null == mem)
{
goto ERR_EXIT1;
}
fh = fopen(“test.txt”);
if (fh)
{
goto ERR_EXIT2;
}
if (fread(mem, size, fh) < 0)
{
goto ERR_EXIT3;
}
ERR_EXIT3:
fclose(fh);
ERR_EXIT2:
free(mem);
ERR_EXIT1:
Unlock(mtex);
}
总结:以后新写C代码的异常处理,统一采用上面的模版。C++/java有更好的异常处理机制,上述方案只供参考,但解决思路应该是一致的,随处return与冗余异常处理应该被严厉禁止。