昨天我又遇到一个类似于的问题,虽然是关于Windows的,但是这是一个通用的design问题:
如前文所说,对于原文所描述的问题有一个通用的万能解决办法:不要有全局变量。把所有的构造和析构都放在两个单独的函数里:一个叫init函数,一个fini函数。谁需要用这个库,谁自己去调用这两个函数。举个例子,Windows上有个动态库叫dbghelp.dll。它就提供了这样两个函数:一个叫SymInitialize。另一个叫SymCleanup。那么,它最大的问题就是这个库实在是太有用了,很多人都想要用它,于是一个程序里可能会有多处去调用SymInitialize和SymCleanup。举个例子,我有一个程序用到了Google的abseil库。abseil有一个Symbolize()函数用于打印当前的函数栈(callstack)。但是呢,另一方面c++23也提供了这个功能。于是c++ runtime也需要调用这两个函数。于是这就存在如下几个问题:
1. SymInitialize能不能被调用多次?当它被调用第二次的时候是应该返回失败,还是什么都不做然后返回成功?
2. 就算我们允许SymInitialize被调用多次,这个函数是有参数的。如果第二次和第一次的参数不一样,怎么办?这意味着第二次调用所传入的参数可能会忽略掉。可能会导致非常难以debug的行为。
3. 允许SymInitialize被调用多次是指允许SymInitialize/SymCleanup/SymInitialize/SymCleanup这样的序列,还是说必须得SymInitialize/SymCleanup/SymInitialize/SymCleanup。
4. dbghelp.dll允许用户设置hooks。就是说使用者可以自己写一个callback函数,然后setup the hook。然后dbghelp.dll会在某些event发生的时候调用这个callback函数。那么,拥有多个使用者就会导致hook的逻辑完全乱套了。
所以我觉得根源在于SymInitialize这个函数不能有任何参数。然后它内部应该采用refcount的形式。就是说,SymCleanup只有在refcount==0的时候才做真正的cleanup。但是呢,cleanup完再做reinitialize 并不总是可行的。这个实现起来还是蛮有挑战的。
--
FROM 107.139.34.*