0

0

是否应该使用goto语句?

黄舟

黄舟

发布时间:2017-06-25 13:25:05

|

2250人浏览过

|

来源于php中文网

原创

是否应该使用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 
#include 
#include 
#include 
#include 
#include 

#define MAX_ACCEPT_BACK_LOG 5

void g_CloseSocket(int &nSockfd)
{
    if ( -1 == nSockfd )
    {
        return;
    }

    struct linger li = { 1, 0 };
    ::setsockopt(nSockfd, SOL_SOCKET, SO_LINGER, (const char *)&li, sizeof(li));
    ::close(nSockfd);
    nSockfd = -1;
}

in_addr_t g_InetAddr(const char *cszIp)
{
    in_addr_t uAddress = INADDR_ANY;

    if ( 0 != cszIp && '\0' != cszIp[0] )
    {
        if ( INADDR_NONE == (uAddress = ::inet_addr(cszIp)) )
        {
            uAddress = INADDR_ANY;
        }
    }

    return uAddress;
}

int g_CreateListenSocket(const char *cszIp, unsigned uPort)
{
    int nOptVal   = 1;
    int nRetCode  = 0;
    int nSocketfd = -1;
    sockaddr_in saBindAddr;

    // create a tcp socket
    nSocketfd = ::socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
    if ( -1 == nSocketfd )
    {
        return nSocketfd;
    }

    // set address can be reused
    nRetCode = ::setsockopt(nSocketfd, SOL_SOCKET, SO_REUSEADDR, (const char *)&nOptVal, sizeof(nOptVal));
    if ( 0 != 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 ( 0 != nRetCode )                                                                                 
    {                                                                                                    
        g_CloseSocket(nSocketfd);                                                                        
        return nSocketfd;                                                                                
    }                                                                                                    

    // create a listen socket
    nRetCode = ::listen(nSocketfd, MAX_ACCEPT_BACK_LOG);                                                 
    if ( 0 != nRetCode )                                                                                 
    {                                                                                                    
        g_CloseSocket(nSocketfd);                                                                        
        return nSocketfd;                                                                                
    }                                                                                                    

    return nSocketfd;
}

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

int g_CreateListenSocket(const char *cszIp, unsigned uPort)
{
    int nOptVal   = 1;
    int nRetCode  = 0;
    int nSocketfd = -1;
    sockaddr_in saBindAddr;

    // create a tcp socket
    nSocketfd = ::socket(AF_INET, SOCK_STREAM, IPPROTO_IP);                                              
    if ( -1 == nSocketfd )                                                                               
    {                                                                                                    
        goto Exit0;                                                                                      
    }                                                                                                    

    // set address can be reused
    nRetCode = ::setsockopt(nSocketfd, SOL_SOCKET, SO_REUSEADDR, (const char *)&nOptVal, sizeof(nOptVal));
    if ( 0 != 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 ( 0 != nRetCode )                                                                                 
    {                                                                                                    
        goto Exit0;                                                                                      
    }                                                                                                    

    // create a listen socket
    nRetCode = ::listen(nSocketfd, MAX_ACCEPT_BACK_LOG);                                                 
    if ( 0 != nRetCode )                                                                                 
    {                                                                                                    
        goto Exit0;                                                                                      
    }                                                                                                    

    // success here
    return nSocketfd;
Exit0:
    // fail and clean up resources here
    if (-1 != nSocketfd)
    {
        g_CloseSocket(nSocketfd);
    }
    return nSocketfd;
}

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

使用宏来简化代码量


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

Simple Groupware0.745
Simple Groupware0.745

Simple Groupware 是一个完整的协同工作套件包。它采用PHP,XML,SQL,HTML,CSS和sgsML开发。Simple Groupware与其它同类型系统不同之处在于使用了新的编程语言sgsML。该语言能够实现快速开发Web应用系统。支持MySQL,Oracle和PostgreSQL。

下载
#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
int g_CreateListenSocket(const char *cszIp, unsigned uPort)
{
    int nOptVal   = 1;
    int nRetCode  = 0;
    int nSocketfd = -1;
    sockaddr_in saBindAddr;

    // create a tcp socket
    nSocketfd = ::socket(AF_INET, SOCK_STREAM, IPPROTO_IP);                                               
    PROCESS_ERROR(-1 != nSocketfd);                                                                       

    // set address can be reused
    nRetCode = ::setsockopt(nSocketfd, SOL_SOCKET, SO_REUSEADDR, (const char *)&nOptVal, sizeof(nOptVal));
    PROCESS_ERROR(0 == 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(0 == nRetCode);                                                                         

    // create a listen socket
    nRetCode = ::listen(nSocketfd, MAX_ACCEPT_BACK_LOG);                                                  
    PROCESS_ERROR(0 == nRetCode);                                                                         

    // success here
    return nSocketfd;
Exit0:
    // fail and clean up resources here
    if (-1 != 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   = 1;
    int nSocketfd = -1;
    sockaddr_in saBindAddr;

    // create a tcp socket
    nSocketfd = ::socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
    PROCESS_ERROR(-1 != nSocketfd);

    // set address can be reused
    nRetCode = ::setsockopt(nSocketfd, SOL_SOCKET, SO_REUSEADDR, (const char *)&nOptVal, sizeof(nOptVal));
    PROCESS_ERROR(0 == 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(0 == nRetCode);

    // create a listen socket
    nRetCode = ::listen(nSocketfd, MAX_ACCEPT_BACK_LOG);
    PROCESS_ERROR(0 == nRetCode);

    // success here
    nResult = true;                  
Exit0:
    // fail and clean up resources here
    if (!nResult)                    
    {                                
        if (-1 != nSocketfd)         
        {                            
            g_CloseSocket(nSocketfd);
        }                            
    }                                

    return nSocketfd;
}

测试代码


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

int main(int argc, char ** argv)
{
    socklen_t nAddrLen        = sizeof(struct sockaddr_in);
    int       nListenSocketfd = -1;
    struct sockaddr_in saRemoteAddr;

    nListenSocketfd = g_CreateListenSocket("", 9999);
    if ( -1 == nListenSocketfd )
    {
        return 0;
    }

    while (true)
    {
        ::memset(&saRemoteAddr, 0, 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 1;
}

 

相关专题

更多
php远程文件教程合集
php远程文件教程合集

本专题整合了php远程文件相关教程,阅读专题下面的文章了解更多详细内容。

21

2026.01.22

PHP后端开发相关内容汇总
PHP后端开发相关内容汇总

本专题整合了PHP后端开发相关内容,阅读专题下面的文章了解更多详细内容。

14

2026.01.22

php会话教程合集
php会话教程合集

本专题整合了php会话教程相关合集,阅读专题下面的文章了解更多详细内容。

8

2026.01.22

宝塔PHP8.4相关教程汇总
宝塔PHP8.4相关教程汇总

本专题整合了宝塔PHP8.4相关教程,阅读专题下面的文章了解更多详细内容。

7

2026.01.22

PHP特殊符号教程合集
PHP特殊符号教程合集

本专题整合了PHP特殊符号相关处理方法,阅读专题下面的文章了解更多详细内容。

6

2026.01.22

PHP探针相关教程合集
PHP探针相关教程合集

本专题整合了PHP探针相关教程,阅读专题下面的文章了解更多详细内容。

6

2026.01.22

菜鸟裹裹入口以及教程汇总
菜鸟裹裹入口以及教程汇总

本专题整合了菜鸟裹裹入口地址及教程分享,阅读专题下面的文章了解更多详细内容。

20

2026.01.22

Golang 性能分析与pprof调优实战
Golang 性能分析与pprof调优实战

本专题系统讲解 Golang 应用的性能分析与调优方法,重点覆盖 pprof 的使用方式,包括 CPU、内存、阻塞与 goroutine 分析,火焰图解读,常见性能瓶颈定位思路,以及在真实项目中进行针对性优化的实践技巧。通过案例讲解,帮助开发者掌握 用数据驱动的方式持续提升 Go 程序性能与稳定性。

9

2026.01.22

html编辑相关教程合集
html编辑相关教程合集

本专题整合了html编辑相关教程合集,阅读专题下面的文章了解更多详细内容。

106

2026.01.21

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Rust 教程
Rust 教程

共28课时 | 4.7万人学习

Kotlin 教程
Kotlin 教程

共23课时 | 2.8万人学习

Go 教程
Go 教程

共32课时 | 4.1万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号