在C++中,如何防止函数被递归调用

jbc*_*coe 5 c++

我有一个在堆上使用内存的函数,如果在同一个函数的另一个实例完成之前调用它将会出现严重错误.如何在编译时防止这种情况发生?

Nic*_*yer 9

你的问题不清楚,你的意思是在单线程场景(递归或相互递归)还是多线程场景(重入)?

在多线程场景中,没有办法在编译时阻止这种情况,因为编译器没有线程的知识.

在单线程场景中,我不知道在编译时阻止递归调用的方法,而不是使用你的大脑.只要您可以分析控制流并证明您的函数不会调用自身,并且它调用的所有函数都不会调用它,那么您应该是安全的.

  • 为大脑+1!"在编译时有一个解决方案 - 思考". (3认同)

Joh*_*ing 9

在编译时检测具有任何数量确定性的递归将是非常困难的.一些静态代码分析工具可能能够做到这一点,但即使这样,您也可以进入涉及代码分析器无法检测的线程的运行时方案.

您需要在运行时检测递归.从根本上说,这样做非常简单:

bool MyFnSimple()
{
    static bool entered = false;
    if( entered )
    {
        cout << "Re-entered function!" << endl;
        return false;
    }
    entered = true;

    // ...

    entered = false;
    return true;
}
Run Code Online (Sandbox Code Playgroud)

当然,最大的问题是它不是线程安全的.有几种方法可以使线程安全,最简单的方法是使用临界区并阻止第二个条目直到第一个条目离开.Windows代码(不包括错误处理):

bool MyFnCritSecBlocking()
{
    static HANDLE cs = CreateMutex(0, 0, 0);
    WaitForSingleObject(cs, INFINITE);
    // ... do stuff
    ReleaseMutex(cs);
    return true;
}
Run Code Online (Sandbox Code Playgroud)

如果希望函数在重新输入函数时返回错误,则可以在获取函数之前先测试它:

bool MyFnCritSecNonBlocking()
{
    static HANDLE cs = CreateMutex(0, 0, 0);
    DWORD ret = WaitForSingleObject(cs, 0);
    if( WAIT_TIMEOUT == ret )
        return false;   // someone's already in here
    // ... do stuff
    ReleaseMutex(cs);
    return true;
}
Run Code Online (Sandbox Code Playgroud)

除了使用静态布尔和暴击之外,可能还有无限的方法可以给这只猫上皮.想到的是将本地值与Windows中的一个Interlocked函数进行测试的组合:

bool MyFnInterlocked()
{
    static LONG volatile entered = 0;
    LONG ret = InterlockedCompareExchange(&entered, 1, 0);
    if( ret == 1 )
        return false;   // someone's already in here
    // ... do stuff
    InterlockedExchange(&entered, 0);
    return false;
}
Run Code Online (Sandbox Code Playgroud)

当然,您必须考虑异常安全和死锁.您不希望函数出现故障,使任何代码无法进入.您可以在RAII中包装上面的任何构造,以确保在函数中发生异常或提前退出时释放锁定.

更新:

在阅读完评论后,我意识到我可以包含说明如何实现RAII解决方案的代码,因为您编写的任何实际代码都将使用RAII来处理错误.这是一个简单的RAII实现,它还说明了在出现问题时在运行时发生的事情:

#include <windows.h>
#include <cstdlib>
#include <stdexcept>
#include <iostream>

class CritSecLock
{
public:
    CritSecLock(HANDLE cs) : cs_(cs)
    {
        DWORD ret = WaitForSingleObject(cs_, INFINITE);
        if( ret != WAIT_OBJECT_0 ) 
            throw std::runtime_error("Unable To Acquire Mutex");
        std::cout << "Locked" << std::endl;
    }
    ~CritSecLock()
    {
        std::cout << "Unlocked" << std::endl;
        ReleaseMutex(cs_);
    }
private:
    HANDLE cs_;
};

bool MyFnPrimitiveRAII()
{
    static HANDLE cs = CreateMutex(0, 0, 0);
    try
    {
        CritSecLock lock(cs);
        // ... do stuff
        throw std::runtime_error("kerflewy!");
        return true;
    }
    catch(...)
    {
        // something went wrong 
        // either with the CritSecLock instantiation
        // or with the 'do stuff' code
        std::cout << "ErrorDetected" << std::endl;
        return false;
    }
}

int main()
{
    MyFnPrimitiveRAII();
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

  • 我发现几乎令人震惊的是你提到RAII作为第二个想法并且它没有出现在代码中的任何地方:( (2认同)

GMa*_*ckG 6

如果没有静态分析,则无法在编译时执行此操作.这是一个异常安全的递归断言:

#include <cassert>

class simple_lock
{
public:
    simple_lock(bool& pLock):
    mLock(pLock)
    {
        assert(!mLock && "recursive call");
        mLock = true;
    }

    ~simple_lock(void)
    {
        mLock = false;
    }

private:
    simple_lock(const simple_lock&);
    simple_lock& operator=(const simple_lock&);

    bool& mLock;
};

#define ASSERT_RECURSION static bool _lockFlag = false; \
                            simple_lock _lock(_lockFlag)

void foo(void)
{
    ASSERT_RECURSION;

    foo();
}

int main(void)
{
    foo();
    //foo();
}
Run Code Online (Sandbox Code Playgroud)


Ale*_*own 5

如果没有某种静态分析器,则无法在编译时执行此操作.但是,对此进行简单的运行时检查将起作用:

注意:为了防止多线程并发但非递归调用,您需要更强大的功能.

void myFunc() {
  static int locked = 0;
  if (locked++)
  {
    printf("recursion detected\n!");
  }

  ....

  locked--;
}
Run Code Online (Sandbox Code Playgroud)

注意:您应将此函数放在.c.cc文件中,而不是标题中.

如果您确实有多线程,我建议您使用pthread锁来控制对它引用的共享变量的访问.