如果不是不可能的话,获得除 Nicholas Wilson 提出的之外的编译时检测将非常困难,但假设“背景”确实指的是函数,而不是多线程(我在问题中没有提到线程,所以我认为这只是一个奇怪的措辞)您可以轻松地使用全局标志和储物柜对象,assert
或者抛出异常。或者,输出调试消息。当然,这将是仅运行时的——但您应该能够非常快速地隔离违规者。调试构建的开销也非常低(几乎可以保证从 L1 缓存运行),而对于发布构建则没有。
使用CaptureStackBackTrace,应该能够捕获违规函数的地址,这样的工具addr2line
(或任何 MS 等效工具)可以直接转换为代码中的一行。甚至可能有一个工具帮助功能可以直接进行这种翻译(虽然我不知道)。
所以,像这样的东西(未经测试!)可能会奏效:
namespace global { int slow_flag = 0; }
struct slow_func_locker
{
slow_func_locker() { ++global::slow_flag; }
~slow_func_locker(){ --global::slow_flag; }
};
#indef NDEBUG
#define REALTIME if(global::slow_flag) \
{ \
void* backtrace; \
CaptureStackBackTrace(0, 1, &backtrace, 0); \
printf("RT function %s called from %08x\n", __FUNCTION__, backtrace); \
}
#define SLOW_FUNC slow_func_locker slow_func_locker_;
#else
#define REALTIME
#define SLOW_FUNC
#endif
foo_class::some_realtime_function(...)
{
REALTIME;
//...
};
foo_class::some_slow_function(...)
{
SLOW_FUNC;
//...
some_realtime_function(blah); // this will trigger
};
唯一真正的缺点(除了不是编译时)是您必须使用任一标记标记每个慢速和实时函数,但由于编译器无法神奇地知道哪个是什么,所以无论如何都没有太多选择。
请注意,全局“标志”实际上是一个计数器,而不是一个标志。这样做的原因是,一个慢速函数可以立即调用另一个返回并清除标志的慢速函数——现在错误地假设一个快速函数(在这种情况下,xgbi 建议的关键部分方法可能会死锁!)。计数器可以防止这种情况发生。在存在线程的情况下,也可以替换int
为std::atomic_int
。
编辑:
现在很清楚确实有2 个线程在运行,并且其中一个(“快速”线程)从未调用“慢”函数很重要,还有另一个简单的工作解决方案(例如使用Win32 API,但无论哪种方式都可以使用 POSIX 完成):
当“快”线程启动时(“慢”线程不需要这样做),将线程 ID 存储在某处,或者作为全局变量,或者作为包含所有快/慢函数的对象的成员——任何地方可以访问的地方:
global::fast_thread_id = GetCurrentThreadId();
用于解决“不受欢迎”函数调用的宏可能如下所示:
#define CHECK_FAST_THREAD assert(GetCurrentThreadID() != global::fast_thread_id)
然后将此宏添加到不应从“快速”线程调用的任何“慢速”函数中。如果快速线程调用了它不能调用的函数,则断言触发并且知道调用了哪个函数。