是否应该使用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. 关于Cocos2d-x中两个场景之间参数的传递

    两个场景之间,有的时候要进行参数传递,如果想通过实例化出一个场景,从而得到属性和方法是不对的想法 你有两个场景,第一场景是用户登录界面,第二场景则是你登录后的界面,你如何将用户登录的值传到第二个场景呢 ...

  2. python中的字典 和 集合

    python中字典是一种key-value的数据类型 字典的特性: 1.无序的 2.key必须的唯一的,so,字典天生去重 语法: 增加 修改 删除 查找 多级字典嵌套及操作 字典的其他用法 #set ...

  3. 工业级别sd卡存贮slc mlc tlc

    slc mlc tlc SLC = Single-Level Cell ,即1bit/cell,速度快寿命长,价格超贵(约MLC 3倍以上的价格),约10万次擦写寿命 MLC = Multi-Leve ...

  4. ThinkPHP项目笔记之RBAC(权限)下篇

    接着谈谈:添加用户以及用户管理列表 e.添加用户

  5. C# 中的treeview绑定数据库(递归算法)

    近日面试的给我两道题目,一道是IQ测试,第二个就是题目所言 总共两个表 department(id int not null primary key,parentid int,name char(50 ...

  6. matlab获取图片的size属性,长宽

    width=size(imread(‘文件名'),2): %获取图像宽length=size(imread(‘文件名'),1): %获取图像长 g=imread(['D:\文件及下载相关\桌面\代码 ...

  7. 【BZOJ3894】文理分科 最小割

    [BZOJ3894]文理分科 Description 文理分科是一件很纠结的事情!(虽然看到这个题目的人肯定都没有纠结过) 小P所在的班级要进行文理分科.他的班级可以用一个n*m的矩阵进行描述,每个格 ...

  8. IOS 十位数0补齐

    NSCalendar *calendar = [NSCalendar currentCalendar]; unsigned unitFlags = NSYearCalendarUnit | NSMon ...

  9. 事务以及MySQL事务隔离级别+MySQL引擎的区别

    1.事务的基本要素:ACID 1.原子性(Atomicity): 事务开始后所有操作,要么全部做完,要么全部不做,不可能停滞在中间环节.事务执行过程中出错,会回滚到事务开始前的状态,所有的操作就像没有 ...

  10. http 服务器编程 适配器

    小结: 1.HandleFunc 只是一个适配器 go http 服务器编程(1) - 云+社区 - 腾讯云 https://cloud.tencent.com/developer/article/1 ...