是否应该使用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. Spring Cloud的子项目,大致可分成两类

    Spring Cloud的子项目,大致可分成两类,一类是对现有成熟框架”Spring Boot化”的封装和抽象,也是数量最多的项目:第二类是开发了一部分分布式系统的基础设施的实现,如Spring Cl ...

  2. 学习:erlang的term反序列化,string转换为term

    一. string_to_term(String) ->    case erl_scan:string(String++".") of        {ok, Tokens ...

  3. PHP实现对站点内容外部链接的过滤方法

    熟悉SEO的朋友都知道,对于网站外部链接失效的情况如果链接带有rel="nofollow"属性可以避免不必要的损失.本文就以实例形式演示了PHP实现对站点内容外部链接的过滤方法.具 ...

  4. hdu 1174:爆头(计算几何,三维叉积求点到线的距离)

    爆头 Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)Total Submissi ...

  5. Spring_day02--AOP概念、原理、操作术语

    AOP概念 hibernate要手动进行事务操作,在spring中通过配置文件来配置事务 1 aop:面向切面(方面)编程,扩展功能不修改源代码实现 2  AOP采取横向抽取机制,取代了传统纵向继承体 ...

  6. 机器学习(Machine Learning)

    机器学习(Machine Learning)是一门专门研究计算机怎样模拟或实现人类的学习行为,以获取新的知识或技能,重新组织已有的知识结构使之不断改善自身的性能的学科.

  7. oracle如何用sql查看触发器?

    ORACLE查出表所有的触发器及触发器详细信息 一.查all_triggers表得到trigger_name Sql代码 select trigger_name from all_triggers w ...

  8. Python闲谈(一)mgrid慢放

    不论是利用Mayavi还是matplotlib绘制三维图表,里面都用到了numpy中的一个函数叫mgrid.本次博客我简单地讲一下mgrid是干什么用的,以及一个三维曲面是如何绘制出来的. 首先说明一 ...

  9. LeetCode-Water and Jug Problem

    You are given two jugs with capacities x and y litres. There is an infinite amount of water supply a ...

  10. Zabbix监控主动模式

    接上篇:Zabbix监控web,MySQL,TCP状态,Nginx 参考官方文档:https://www.zabbix.com/documentation/3.4/zh/manual zabbix默认 ...