C++ 在C++;如何防止递归调用函数
我有一个函数,它利用了堆上的内存,如果在同一个函数的另一个实例完成之前调用它,那么它将出现严重错误。C++ 在C++;如何防止递归调用函数,c++,C++,我有一个函数,它利用了堆上的内存,如果在同一个函数的另一个实例完成之前调用它,那么它将出现严重错误。 如何防止这种情况在编译时发生?如果没有某种静态分析器,您无法在编译时执行此操作。但是,对其进行简单的运行时检查即可: 注意:为了防止多线程并发但非递归调用,您需要一些更健壮的东西 void myFunc() { static int locked = 0; if (locked++) { printf("recursion detected\n!"); } ....
如何防止这种情况在编译时发生?如果没有某种静态分析器,您无法在编译时执行此操作。但是,对其进行简单的运行时检查即可: 注意:为了防止多线程并发但非递归调用,您需要一些更健壮的东西
void myFunc() {
static int locked = 0;
if (locked++)
{
printf("recursion detected\n!");
}
....
locked--;
}
注意:您应该将此函数放在.c
或.cc
文件中,而不是放在标题中
如果您有多线程,我建议您使用pthread锁来控制对其引用的共享变量的访问。您的问题不清楚,您是指单线程场景(递归或交互递归)还是多线程场景(重入) 在多线程场景中,由于编译器不知道线程,因此无法在编译时防止这种情况
在单线程场景中,除了使用大脑,我不知道有什么方法可以防止在编译时进行递归调用。只要您能够分析控制流并证明您的函数不调用自身,并且它调用的函数都不会回调,您就应该是安全的。这个问题在任何图灵完全语言中都是无法确定的。但我无法证明这一点。我只是知道。你最好的选择是使用互斥锁。您可以使用信号量,但我个人更喜欢这里的互斥。这将允许您防止其他线程调用它 *编辑* 啊,你希望它在编译时发生吗
我的朋友,你在那里无所事事。你不能在编译时这样做,你需要遵循整个应用程序的完整控制流程来完成这一点(如果函数调用另一个函数,该函数调用另一个函数,该函数调用另一个函数,该函数又调用了另一个函数,该函数又调用了原始函数……) 而是在思考的时候去做。在函数的文档中添加大量注释。另外可能还有其他响应提供的一个运行时解决方案-用于调试构建
只需添加另一个:使用静态互斥来保护函数体(boost的作用域锁定将大大简化它)。如果没有静态分析,您无法在编译时执行此操作。下面是一个异常安全的递归断言:
#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();
}
#包括
类简单锁
{
公众:
简单锁定(bool&pLock):
姆洛克(普洛克)
{
断言(!mLock&“递归调用”);
mLock=true;
}
~simple_lock(无效)
{
mLock=false;
}
私人:
简单锁(const simple锁&);
简单锁&运算符=(常量简单锁&);
布尔&姆洛克;
};
#定义断言递归静态bool\u lockFlag=false\
简单锁定(锁定标志)
无效foo(无效)
{
断言递归;
foo();
}
内部主(空)
{
foo();
//foo();
}
函数调用堆栈是在运行时创建的,在编译时,您只能检查函数本身是否是递归的,即它是否调用自身?在类似情况下有一些很好的建议:编写一条注释,说明在执行此类操作时可能出现问题:
// We'll fire you if you try recursion here
我还没有看到这个建议是否也适用于递归在编译时检测具有任意数量确定性的递归将是相当困难的。一些静态代码分析工具可能能够做到这一点,但即使这样,您也可以进入代码分析器无法检测到的涉及线程的运行时场景 您需要在运行时检测递归。从根本上说,这样做非常简单:
bool MyFnSimple()
{
static bool entered = false;
if( entered )
{
cout << "Re-entered function!" << endl;
return false;
}
entered = true;
// ...
entered = false;
return true;
}
如果您希望函数在重新输入函数时返回错误,可以在获取之前先测试critsec:
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;
}
除了使用静态bools和critsecs之外,可能还有无数种方法可以剥下这只猫的皮。想到的一个方法是将测试本地值与Windows中的一个互锁函数结合起来:
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;
}
当然,您必须考虑异常安全和死锁。您不希望函数中的故障使任何代码都无法输入它。您可以包装上面的任何构造,以确保在函数中发生异常或提前退出时释放锁
更新:
在阅读了这些评论之后,我意识到我可以包含一些代码来说明如何实现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;
}
#包括
#包括
#包括
#包括
类CritSecLock
{
公众:
CritSecLock(手柄cs):cs(手柄cs)
{
DWORD ret=WaitForSingleObject(cs_3;,无穷大);
if(ret!=等待对象0)
抛出std::runtime_错误(“无法获取互斥”);
std::cout实现这一点的唯一方法是确保客户端代码无法引用您不希望重新输入的函数。这可以通过使函数为静态的
或在匿名名称空间或某种类似技术中实现
不幸的是,为了确保这一点能够正常工作,从main
到您的函数的所有函数也必须以这种方式声明,并且这种情况很难很快得到管理
虽然C++技术上说从代码中调用主< /代码>是不明确的,你不应该这样做,但是大多数实现都会愉快地递归<代码>主< /代码>。并且<代码>主<代码>必须有一个可以从程序中的任何地方访问的名称。
所以,在现实中这是不可能的
我还以为你想让编译器在你尝试的时候给你一个错误,而不是确保没有简单的错误
#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;
}
#include <stdio.h>
int testFunc() {
#define testFunc
printf("Ok\n");
}
#undef testFunc
int main() { testFunc(); }
#include <stdio.h>
int testFunc() {
#define testFunc
printf("Ok\n");
testFunc();
}
#undef testFunc
int main() { testFunc(); }
#include <stdio.h>
int testFunc1() {
#define testFunc1
printf("1\n");
testFunc2();
}
int testFunc2() {
#define testFunc2
printf("2\n");
//uncomment to cause error: `test.c:13: error: expected expression before ‘)’ token`
//testFunc1();
}
#undef testFunc1
#undef testFunc2
int main() { testFunc1(); }
struct NestingTracker
{
NestingTracker(uint32_t &cnt) : _cnt(cnt) { ++_cnt; }
~NestingTracker() { assert(_cnt); --_cnt; }
uint32_t& _cnt;
};
#define NO_REENTRY(x) \
static uint32_t cur_nesting = 0; \
if (cur_nesting) \
return (x); \
NestingTracker nesting_track(cur_nesting)
void f(...)
{
NO_REENTRY(void());
...
}