是否应该使用goto语句


goto语句也被称为无条件转移语句,它通常与条件语句配合使用来改变程序流向,使得程序转去执行语句标号所标识的语句。

关于是否应该使用goto语句,历史上也争论不休。恐怕国内大部分教授高级编程语言的课堂上,都会主张在结构化程序设计中不使用goto语句, 以免造成程序流程的混乱,使得理解和调试程序都产生困难。历史上支持goto语句有害的人的主要理由是:goto语句会使程序的静态结构和动态结构不一致,从而使程序难以理解难以查错。并且G·加科皮尼和C·波姆从理论上证明了:任何程序都可以用顺序、分支和重复结构表示出来。这个结论表明,从高级程序语言中去掉goto语句并不影响高级程序语言的编程能力,而且编写的程序的结构更加清晰。

然而伟大的哲学家黑格尔说过:存在即合理。当笔者刚从校园中走出的时候,对于goto语句有害论也深以为然,然后多年之后在自己编写的代码中随处可见goto的身影。如今很多高级编程语言中,似乎是难以看见goto的身影:Java中不提供goto语句,虽然仍然保留goto为关键字,但不支持它的使用;C#中依然支持goto语句,但是一般不建议使用。其实可以很容易发现一点,这些不提倡使用goto语句的语言,大多是有自带的垃圾回收机制,也就是说不需要过多关心资源的释放的问题,因而在程序流程中没有“为资源释放设置统一出口”的需求。然而对于C++语言来说,程序员需要自己管理资源的分配和释放。倘若没有goto语句,那么我们在某个函数资源分配后的每个出错点需要释放资源并返回结果。虽然我们依然可以不使用goto语句完整地写完流程,但是代码将变得又臭又长。譬如我们需要写一个全局函数g_CreateListenSocket用来创建监听套接字,那么如果不使用goto语句,我们的代码将会是这个样子:

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h> #define MAX_ACCEPT_BACK_LOG 5 void g_CloseSocket(int &nSockfd)
{
if ( - == nSockfd )
{
return;
} struct linger li = { , };
::setsockopt(nSockfd, SOL_SOCKET, SO_LINGER, (const char *)&li, sizeof(li));
::close(nSockfd);
nSockfd = -;
} in_addr_t g_InetAddr(const char *cszIp)
{
in_addr_t uAddress = INADDR_ANY; if ( != cszIp && '\0' != cszIp[] )
{
if ( INADDR_NONE == (uAddress = ::inet_addr(cszIp)) )
{
uAddress = INADDR_ANY;
}
} return uAddress;
} int g_CreateListenSocket(const char *cszIp, unsigned uPort)
{
int nOptVal = ;
int nRetCode = ;
int nSocketfd = -;
sockaddr_in saBindAddr; // create a tcp socket
nSocketfd = ::socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
if ( - == nSocketfd )
{
return nSocketfd;
} // set address can be reused
nRetCode = ::setsockopt(nSocketfd, SOL_SOCKET, SO_REUSEADDR, (const char *)&nOptVal, sizeof(nOptVal));
if ( != nRetCode )
{                                                                                
g_CloseSocket(nSocketfd);                                                                        
return nSocketfd;                                                                        
}                                                                             // bind address
saBindAddr.sin_family = AF_INET;
saBindAddr.sin_addr.s_addr = g_InetAddr(cszIp);
saBindAddr.sin_port = ::htons(uPort); nRetCode = ::bind(nSocketfd, (struct sockaddr *)&saBindAddr, sizeof(saBindAddr));
if ( != nRetCode )
{
g_CloseSocket(nSocketfd);
return nSocketfd;
}     // create a listen socket
nRetCode = ::listen(nSocketfd, MAX_ACCEPT_BACK_LOG);
if ( != nRetCode )
{
g_CloseSocket(nSocketfd);
return nSocketfd;
}     return nSocketfd;
}

上面蓝色标记的代码中就包含了出错时候对资源(这里是套接字描述符)进行清理的操作,这里只有单一的资源,所以流程看起来也比较干净。倘若流程中还夹杂着内存分配、打开文件的操作,那么对资源释放操作将变得复杂,不仅代码变得臃肿难看,还不利于对流程的理解。而如果使用了goto语句,那么我们统一为资源释放设定单一出口,那么代码将会是下面这个样子:

int g_CreateListenSocket(const char *cszIp, unsigned uPort)
{
int nOptVal = ;
int nRetCode = ;
int nSocketfd = -;
sockaddr_in saBindAddr; // create a tcp socket
nSocketfd = ::socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
if ( - == nSocketfd )
{
goto Exit0;
}     // set address can be reused
nRetCode = ::setsockopt(nSocketfd, SOL_SOCKET, SO_REUSEADDR, (const char *)&nOptVal, sizeof(nOptVal));
if ( != nRetCode )
{
goto Exit0;
}     // bind address
saBindAddr.sin_family = AF_INET;
saBindAddr.sin_addr.s_addr = g_InetAddr(cszIp);
saBindAddr.sin_port = ::htons(uPort); nRetCode = ::bind(nSocketfd, (struct sockaddr *)&saBindAddr, sizeof(saBindAddr));
if ( != nRetCode )
{
goto Exit0;
}     // create a listen socket
nRetCode = ::listen(nSocketfd, MAX_ACCEPT_BACK_LOG);
if ( != nRetCode )
{
goto Exit0;
}     // success here
return nSocketfd;
Exit0:
// fail and clean up resources here
if (- != nSocketfd)
{
g_CloseSocket(nSocketfd);
}
return nSocketfd;
}

其实可以发现,加入goto语句之后,流程反而变得清晰了。一个函数将拥有两个出口:执行成功返回和执行失败返回。每次在流程某处出错后都跳转到固定标号处执行资源释放操作,这样在主体流程中将不再出现与资源释放相关的代码,那么主体流程只需专注于逻辑功能,代码将变得更易于理解和维护。另外一个好处就是不容易忘记释放资源,只需要养成分配完一个资源后立即在资源统一释放处编写资源释放代码的好习惯即可,对于程序员复查自己的代码也带来好处。

使用宏来简化代码量


仔细观察上面的代码,再结合前面所言的goto语句通常与条件语句配合使用来改变程序流向,可以总结规律:我们总是检查某个条件是否成立,如果条件不成立立即goto到指定的函数执行失败入口处,那么我们可以设计宏如下:

#undef  DISABLE_WARNING
#ifdef _MSC_VER // MS VC++
#define DISABLE_WARNING(code, expression) \
__pragma(warning(push)) \
__pragma(warning(disable:code)) expression \
__pragma(warning(pop))
#else // GCC
#define DISABLE_WARNING(code, expression) \
expression
#endif // _MSC_VER #undef WHILE_FALSE_NO_WARNING
#define WHILE_FALSE_NO_WARNING DISABLE_WARNING(4127, while(false)) #undef PROCESS_ERROR_Q
#define PROCESS_ERROR_Q(condition) \
do \
{ \
if (!(condition)) \
{ \
goto Exit0; \
} \
} WHILE_FALSE_NO_WARNING #undef PROCESS_ERROR
#define PROCESS_ERROR(condition) \
do \
{ \
if (!(condition)) \
{ \
assert(false); \
goto Exit0; \
} \
} WHILE_FALSE_NO_WARNING

那么我们的g_CreateListenSocket函数将最终简化为如下代码:

int g_CreateListenSocket(const char *cszIp, unsigned uPort)
{
int nOptVal = ;
int nRetCode = ;
int nSocketfd = -;
sockaddr_in saBindAddr; // create a tcp socket
nSocketfd = ::socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
PROCESS_ERROR(- != nSocketfd);     // set address can be reused
nRetCode = ::setsockopt(nSocketfd, SOL_SOCKET, SO_REUSEADDR, (const char *)&nOptVal, sizeof(nOptVal));
PROCESS_ERROR( == nRetCode);     // bind address
saBindAddr.sin_family = AF_INET;
saBindAddr.sin_addr.s_addr = g_InetAddr(cszIp);
saBindAddr.sin_port = ::htons(uPort); nRetCode = ::bind(nSocketfd, (struct sockaddr *)&saBindAddr, sizeof(saBindAddr));
PROCESS_ERROR( == nRetCode);     // create a listen socket
nRetCode = ::listen(nSocketfd, MAX_ACCEPT_BACK_LOG);
PROCESS_ERROR( == nRetCode);     // success here
return nSocketfd;
Exit0:
// fail and clean up resources here
if (- != nSocketfd)
{
g_CloseSocket(nSocketfd);
}
return nSocketfd;
}

统一函数出口


如果想统一函数出口,其实方法很简单:只需要加入一个int nResult字段,初始化为false,在函数流程完全走完时标记为true,然后在释放资源处判断该字段是否为false即可。可以参考下面代码:

int g_CreateListenSocket(const char *cszIp, unsigned uPort)
{
    int nResult = false;
    int nRetCode = false;
int nOptVal = ;
int nSocketfd = -;
sockaddr_in saBindAddr; // create a tcp socket
nSocketfd = ::socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
PROCESS_ERROR(- != nSocketfd); // set address can be reused
nRetCode = ::setsockopt(nSocketfd, SOL_SOCKET, SO_REUSEADDR, (const char *)&nOptVal, sizeof(nOptVal));
PROCESS_ERROR( == nRetCode); // bind address
saBindAddr.sin_family = AF_INET;
saBindAddr.sin_addr.s_addr = g_InetAddr(cszIp);
saBindAddr.sin_port = ::htons(uPort); nRetCode = ::bind(nSocketfd, (struct sockaddr *)&saBindAddr, sizeof(saBindAddr));
PROCESS_ERROR( == nRetCode); // create a listen socket
nRetCode = ::listen(nSocketfd, MAX_ACCEPT_BACK_LOG);
PROCESS_ERROR( == nRetCode); // success here
nResult = true;
Exit0:
// fail and clean up resources here
if (!nResult)
{
if (- != nSocketfd)
{
g_CloseSocket(nSocketfd);
}
}     return nSocketfd;
}

测试代码


最后附上上述代码的测试代码:

int main(int argc, char ** argv)
{
socklen_t nAddrLen = sizeof(struct sockaddr_in);
int nListenSocketfd = -;
struct sockaddr_in saRemoteAddr; nListenSocketfd = g_CreateListenSocket("", );
if ( - == nListenSocketfd )
{
return ;
} while (true)
{
::memset(&saRemoteAddr, , sizeof(saRemoteAddr));
int nSocketfd = ::accept(nListenSocketfd, (struct sockaddr *)&saRemoteAddr, &nAddrLen); ::printf("Accept a new connection from [ip - %s, port - %d]\n",
::inet_ntoa(saRemoteAddr.sin_addr),
::ntohs(saRemoteAddr.sin_port)
); g_CloseSocket(nSocketfd);
} return ;
}

正确使用goto语句的更多相关文章

  1. 在程序中,你敢怎样使用“goto”语句!

    用goto是一个个人爱好的问题.“我”的意见是,十个goto中有九个可以用相应的结构化结构来替换.在那些简单情形下,你可以完全替换掉goto,在复杂的情况下,十个中也有九个可以不用:你可以把部分代码写 ...

  2. CMD中goto语句会中断for循环特性详解

    在这个程序里面由于用到了上篇文章中所说的字符串切割,而用到了Goto强制跳转语句 但是在程序中使用的时候却发现一个错误,当把这个字符切割的代码段如果直接作为非嵌套语句执行正常 但是一旦放到for循环的 ...

  3. 通过goto语句学习if...else、switch语句并简单优化

    goto语句在C语言中实现的就是无条件跳转,第二章一上来就介绍goto语句就是要通过goto语句来更加清楚直观的了解控制结构. 我理解的goto语句其实跟switch语句有相似之处,都是进行跳转.不同 ...

  4. [SQL]开启事物,当两条插入语句有出现错误的时候,没有错误的就插入到表中,错误的语句不影响到正确的插入语句

    begin transaction mustt insert into student values(,'kkk','j大洒扫','j','djhdjh') insert into student v ...

  5. goto语句 switch语句

    goto语句 #include <iostream> using namespace std; int main() { int i = 1; number: i++; std::cout ...

  6. 布尔逻辑运算,goto语句

    布尔逻辑 bool类型可以有两个值:true或者false. 布尔比较需要使用布尔比较运算符(关系运算符),下图:var1为布尔类型的变量,var2,var3则可以是不同类型.

  7. Go 语言 goto 语句

    Go 语言的 goto 语句可以无条件地转移到过程中指定的行. goto语句通常与条件语句配合使用.可用来实现条件转移, 构成循环,跳出循环体等功能. 但是,在结构化程序设计中一般不主张使用goto语 ...

  8. C语言 goto语句

    /* goto语句 */ #include <stdio.h> #include <stdlib.h> #include <string.h> /* goto语句也 ...

  9. goto语句引起的crosses initialization of XXX

    1. 背景 goto语句虽然目前已经不提倡使用,但是用起来还是很方便,尤其是老代码中见的比较多. 在改动有goto语句的老代码时需要特别注意,是否跳过来资源的释放.有用变量的初始化等等. 很久之前写c ...

随机推荐

  1. springframework resource

    文件资源操作     Spring 定义了一个 org.springframework.core.io.Resource 接口,Resource 接口是为了统一各种类型不同的资源而定义的,Spring ...

  2. hdu 2019:数列有序!(数据结构,直接插入排序+折半插入排序)

    数列有序! Time Limit : 2000/1000ms (Java/Other)   Memory Limit : 65536/32768K (Java/Other) Total Submiss ...

  3. 配置linux的环境变量

    下面是配置linux的环境变量:(记得source .bash_profile). 修改/etc/profile文件 (全局所有用户) vi 此文件/etc/profile在profile文件末尾加入 ...

  4. Python爬虫(七)

    源码: import requests import re from my_mysql import MysqlConnect # 获取详情页链接和电影名称 def get_urls(page): u ...

  5. 二、Android Studio使用——导入jar包,运行、debug都不是问题

    [新建AndroidStudio工程,lib导入jar包]   我们的项目代码都在app里面,可以看作是一个Model.   src 下面除了我们的代码之外,还有单元测试. 把JAR复制到libs文件 ...

  6. poj_2186 强连通分支

    题目大意 有N头牛,他们中间有些牛会认为另外一些牛“厉害”,且这种认为会传递,即若牛A认为牛B“厉害”,牛B认为牛C“厉害”,那么牛A也认为牛C“厉害”.现给出一些牛的数对(x, y)表示牛x认为牛y ...

  7. 【黑金原创教程】【TimeQuest】【第二章】TimeQuest模型角色,网表概念,时序报告

    声明:本文为黑金动力社区(http://www.heijin.org)原创教程,如需转载请注明出处,谢谢! 黑金动力社区2013年原创教程连载计划: http://www.cnblogs.com/al ...

  8. 『AngularJS』一点小小的理解

    AngularJS 是一个前端的以Javascript为主的MVC框架.与AngularJS相类似的还有EmberJS. 随着时代在进步,各种各样的开发理念与开发框架不断的提出与发展,而就目前来说,除 ...

  9. ios开发-获取手机相关信息

    今天在做客户端的时候,里面有个意见反馈功能. 调用系统带的邮件功能,发送邮件到指定邮箱. 然后我就想,应该在邮件正文部分添加手机相关内容,比如型号,版本,应用程序的版本等等,这样不仅使用者方便,开发者 ...

  10. dao---service---action分层结构

    此文转载于http://blog.csdn.net/jay198746/article/details/4698709 之前有看过一些ssh2中采用dao---service---action分层结构 ...