如何处理从未执行的代码

10
我有一些像这样的代码,我不确定如何处理永远不会被执行的部分,因为这段代码的一部分在无限循环中运行以等待连接,当我终止程序时,它只从那里退出。
main(){

// do some stuff....

    while(1) {
        int newFD =
            accept(sockFD, (struct sockaddr *)&client_addr, &client_addr_size);
        if(newFD == -1) {
            std::cerr << "Error while Accepting on socket" << std::endl;
            continue;
        }

        if(!fork()) {

            close(sockFD); // close child's sockfd - not needed here

            // lalala do stuff send message here                

            close(newFD);  // finally close its newFD - message sent, no use 
            return 0;
        }
        close(newFD);  // close parent's newFD - no use here
    }

    // now execution never reaches here
    close(sockFD);      // so how to handle this?
    freeaddrinfo(res);  // and this?

    return 0;
}

我让它保持原样,我猜操作系统应该会处理它的? - user964843
5
针对这个情况,我希望钩入终止信号并使用它来干净地退出程序。不过总的来说,对于从未执行的代码,应该将其删除,让版本控制跟踪旧的死代码。 - Niall
1
你的问题是关于如何处理关闭套接字,还是关于代码本身该怎么办? - rhughes
也许这有点简单,但你能否只是检查一个非常大的数字然后关闭呢? - DavidTheDev
在关闭了newFD之后,为什么不将return 0更改为continue呢? - Rob
@Rob,我不明白,continue 怎么能帮助这里呢? - Abhinav Gauniyal
2个回答

14

如果你的代码将被其他人使用或者你自己想让它更加清晰,你可以并且应该添加退出处理程序。在退出处理程序中,你可以切换一个标志,使得while()循环终止。以下代码对于这种使用情况将完美地工作,并且是可靠和跨平台的,但如果你想做更复杂的事情,你应该使用正确的线程安全的操作系统特定函数或像Boost或C++11这样的内容。

首先声明两个全局变量,使它们成为易变量,这样编译器就会强制我们总是读取或写入它的实际内存值。如果我们没有声明易变量,那么编译器可能会把它的值放到寄存器中,这将导致这个方法不能正常工作。设置易变量后,它将在每次循环中读取内存位置并正常工作,即使有多个线程。

volatile bool bRunning=true;
volatile bool bFinished=false;

而不是使用 while(1) {} 循环,改成这样

while(bRunning)
{
    dostuff
}
bFinished=true;

在您的退出处理程序中,只需设置bRunning=false;

void ExitHandler()
{
    bRunning=false;
    while(bFinished==false) { Sleep(1); }
}

您没有指定操作系统,但看起来您是基于Linux的,要在Linux上设置一个处理程序,您需要这样做。

void ExitHandler(int s)
{
    bRunning=false;
}

int main()
{
    struct sigaction sigIntHandler;
    sigIntHandler.sa_handler = ExitHandler;
    sigemptyset(&sigIntHandler.sa_mask);
    sigIntHandler.sa_flags = 0;
    sigaction(SIGINT, &sigIntHandler, NULL);
    while(bRunning)
    {
        dostuff
    }
    ...error_handling...
}

当您在Windows上使用控制台应用程序时,可以按照以下方式进行操作。

BOOL WINAPI ConsoleHandler(DWORD CEvent)
{
    switch (CEvent)
    {
        case CTRL_C_EVENT:
        case CTRL_BREAK_EVENT:
        case CTRL_CLOSE_EVENT:
        case CTRL_LOGOFF_EVENT:
        case CTRL_SHUTDOWN_EVENT:
            bRunning = false;
            while (bFinished == false) Sleep(1);
            break;
    }
    return TRUE;
}

int main()
{
    SetConsoleCtrlHandler(ConsoleHandler, TRUE);
    while(bRunning()
    {
        dostuff
    }
    ...error_handling...
}

注意在这里需要测试和等待bFinished。 如果在Windows上不这样做,您的应用程序可能没有足够的时间关闭,因为退出处理程序是由单独的特定于操作系统的线程调用的。 在Linux上,这是不必要的,您需要从处理程序退出以使主线程继续运行。

另一个需要注意的是,默认情况下,Windows只给您大约5秒钟的时间来关闭应用程序,否则将终止您的进程。 在许多情况下,这是不幸的,如果需要更多时间,则需要更改注册表设置(不建议)或实现具有更好钩入此类事物的服务。 对于简单的情况,这应该足够了。


4
哈哈,我只是添加了Windows的代码,因为对于可能在Windows上搜索相同答案的其他人来说,它是相关的。希望它有用! - Ryler Sturden
4
不要使用(PHANDLER_ROUTINE)ConsoleHandler,这样做很麻烦。您不需要进行强制转换。此外,仅使用volatile是不可取的。相反,请使用Interlocked*函数族。最后,控制-C处理程序是否在现有线程的上下文中执行?如果您等待完全终止该线程,则可能会导致致命错误。 - Ulrich Eckhardt
引入一个额外的变量来控制流程?总体来说不是个好主意。 - Deduplicator
volatile 的作用就是防止糟糕的编译器对不该优化的内容进行优化,这是过去存在的一个真正的问题。一个好的编译器应该能够看到你在另一个位置设置了它,因此它将始终从内存中读取。现代编译器都应该可以胜任这项工作。因此,它只用于确保有缺陷的编译器不会将其优化掉。因此,在这些编译器上,需要确保从内存位置读取数据。如果我误解了您的意思,请见谅。 - Ryler Sturden
在单个执行线程中,volatile 访问不能被重新排序或优化掉。这使得 volatile 对象适用于与信号处理程序进行通信,但不适用于另一个执行线程。如果您仍然认为 volatile 的目的是解决编译器问题,并且无法看到这明显违背了记录的意图,那么我无法帮助您,我不知道如何让它更清晰明了。 - Ulrich Eckhardt
显示剩余9条评论

9
对于这些东西,操作系统会在关闭时负责适当释放资源。然而,更普遍的情况是,在程序执行期间仍需要确保已分配的资源不会堆积,即使它们被操作系统自动回收,因为这样的资源泄漏仍会影响程序的行为和性能。
现在,对于手头的资源,没有理由不像C++中的所有资源一样对待它们。接受的规则是将它们绑定到一个对象上,在其析构函数中释放它们,也可以看到RAII习惯用法。这样,即使在以后的某个阶段添加了break语句,代码仍将正常运行。
顺便说一下:我在这里看到的更严重的问题是缺乏适当的错误处理机制。

3
您能评论一下“错误处理”部分吗?我很想根据您的建议改进我的代码,因为我以前从未做过这样的工作。 - Abhinav Gauniyal
1
简单示例:fork()。如果您查看手册页,它返回三个不同的值组,但您只区分其中两个。如果fork()失败,我会默认使用throw std::runtime_error("fork() failed")作为错误处理概念,以便故障不被忽略。这也适用于此处的其他函数,只需阅读相应的文档以了解它们如何发出错误信号。您还可以在codereview.stackexchange.com上发布您的代码以获得进一步建议。 - Ulrich Eckhardt
@UlrichEckhardt提出了很好的观点,我建议尽可能使用RAII进行错误处理。您可能希望查看ASIO以进行网络编程,而不是使用原始套接字函数,因为它将进入C++17标准,并允许您的应用程序跨平台运行,而无需更多努力。Windows套接字和Linux套接字存在一些差异,这使得移植比必要的麻烦! - Ryler Sturden
@RylerSturden 感谢您的建议。如果我的大学课程没有强制要求我以这种低级方式完成,我肯定会使用它。 - Abhinav Gauniyal
无论如何,了解低级方式总是很好的,这样你就可以真正欣赏优秀的网络库。 ;) 你可以自己将那些代码封装成类,比如CSocket,在析构函数中优雅地处理关闭套接字和其他需要做的事情。这样你自己的代码就是RAII的。我认为所有的网络C++程序员在某个时候都写过类似CSocket的类。;) - Ryler Sturden

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接