你的问题不清楚,你的意思是在单线程场景(递归或相互递归)还是多线程场景(重入)?
在多线程场景中,没有办法在编译时阻止这种情况,因为编译器没有线程的知识.
在单线程场景中,我不知道在编译时阻止递归调用的方法,而不是使用你的大脑.只要您可以分析控制流并证明您的函数不会调用自身,并且它调用的所有函数都不会调用它,那么您应该是安全的.
在编译时检测具有任何数量确定性的递归将是非常困难的.一些静态代码分析工具可能能够做到这一点,但即使这样,您也可以进入涉及代码分析器无法检测的线程的运行时方案.
您需要在运行时检测递归.从根本上说,这样做非常简单:
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)
如果没有静态分析,则无法在编译时执行此操作.这是一个异常安全的递归断言:
#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)
如果没有某种静态分析器,则无法在编译时执行此操作.但是,对此进行简单的运行时检查将起作用:
注意:为了防止多线程并发但非递归调用,您需要更强大的功能.
void myFunc() {
static int locked = 0;
if (locked++)
{
printf("recursion detected\n!");
}
....
locked--;
}
Run Code Online (Sandbox Code Playgroud)
注意:您应将此函数放在.c或.cc文件中,而不是标题中.
如果您确实有多线程,我建议您使用pthread锁来控制对它引用的共享变量的访问.