我遇到的情况是我试图在共享对象构造函数中初始化文件范围的变量std :: string。在代码中可能会更清楚:
#include <string>
#include <dlfcn.h>
#include <cstring>
static std::string pathToDaemon; // daemon should always be in the same dir as my *.so
__attribute__((constructor))
static void SetPath()
{
int lastSlash(0):
Dl_info dl_info;
memset(&dl_info, 0, sizeof(dl_info));
if((dladdr((void*)SetPath, &dl_info)) == 0)
throw up;
pathToDaemon = dl_info.dli_fname; // **whoops, segfault here**
lastSlash = pathToDaemon.find_last_of('/');
if(std::string::npos == lastSlash)
{
// no slash, but in this dir
pathToDaemon = "progd";
}
else
{
pathToDaemon.erase(pathToDaemon.begin() + (lastSlash+1), pathToDaemon.end());
pathToDaemon.append("progd");
}
std::cout << "DEBUG: path to daemon is: " << pathToDaemon << std::endl;
}
我有一个非常简单的程序可以做同样的事情:如果你愿意,可以使用概念的测试驱动程序。其中的代码如下所示:一个“共享对象ctor”,它使用dladdr()在加载文件时存储* .so文件的路径。
我尝试过的修改:
namespace {
std::string pathToDaemon;
__attribute__((constructor))
void SetPath() {
// function def
}
}
或
static std::string pathToDaemon;
__attribute__((constructor))
void SetPath() { // this function not static
// function def
}
和
std::string pathToDaemon; // variable not static
__attribute__((constructor))
void SetPath() { // this function not static
// function def
}
您在上面看到的示例位于一个编译为静态对象库和DLL的文件中。编译过程:
我必须在更大的项目中跳过的箍比我的小测试驱动程序需要的构建过程复杂得多。这让我觉得问题就在那里。任何人都可以对我失踪的内容有所了解吗?
谢谢, 安迪
答案 0 :(得分:2)
我认为值得给出我找到的答案。问题是由于共享库加载的复杂性。我在一些挖掘后发现,在编译启用了优化的代码时,我可以在我的测试床程序中重现问题。这证实了这一假设,即当构造函数访问变量时,该变量确实不存在。
GCC包含一些额外的C ++工具,允许开发人员在代码初始化期间强制某些事情发生。更准确地说,它允许某些事情按特定顺序而不是特定时间发生。
例如:
int someVar(55) __attribute__((init_priority(101)));
// This function is a lower priority than the initialization above
// so, this will happen *after*
__attribute__((constructor(102)))
void SomeFunc() {
// do important stuff
if(someVar == 55) {
// do something here that important too
someVar = 44;
}
}
即使启用了优化,我也能够使用这些工具在测试台程序中取得成功。当应用于我更大的图书馆时,随之而来的幸福是短暂的。最终,问题是由于如此大量的代码的性质以及变量产生的问题方式。使用这些机制并不可靠。
因为我想避免重复考虑评估路径,即
std::string GetPath() {
Dl_info dl_info;
dladdr((void*)GetPath, &dl_info);
// do wonderful stuff to find the path
return dl_info.dli_fname;
}
解决方案比我试图制作它简单得多:
namespace {
std::string PathToProgram() {
Dl_info dl_info;
dladdr((void*)PathToProgram, &dl_info);
std::string pathVar(dl_info.dli_fname);
// do amazing things to find the last slash and remove the shared object
// from that path and append the name of the external daemon
return pathVar;
}
std::string DaemonPath() {
// I'd forgotten that static variables, like this, are initialized
// only once due to compiler magic.
static const std::string pathToDaemon(PathToProgram());
return pathToDaemon;
}
}
正如你所看到的,正是我想要的,而不是混乱。除了调用DaemonPath()之外,一切都只发生一次,并且所有内容都保留在翻译单元中。
我希望这可以帮助将来遇到这种情况的人。
安迪
答案 1 :(得分:0)
也许你可以尝试在你的程序上运行valgrind
答案 2 :(得分:0)
在上面的自我发布的解决方案中,您已经将»interface«(用于读取您的pathToDaemon / DaemonPath()的代码)从»访问文件范围变量«更改为»调用匿名命名空间中的函数« - 到目前为止确定。
但是DaemonPath()的实现不是以线程安全的方式完成的。我认为线程安全很重要,因为你的问题写了»-lboost_thread«。所以你可能会考虑改变实现线程安全。关于单例模式和线程安全性的讨论和解决方案有很多,例如:
事实是,在加载库完成后,您的DaemonPath()将被调用(可能很远)。请注意,在多线程环境中,只有第一次调用单例模式才是至关重要的。
作为替代方案,您可以为您的DaemonPath()函数添加一个简单的“早期”调用,如下所示:
namespace {
std::string PathToProgram() {
... your code from above ...
}
std::string DaemonPath() {
... your code from above ...
}
__attribute__((constructor)) void MyPathInit() {
DaemonPath();
}
}
或以更便携的方式:
namespace {
std::string PathToProgram() {
... your code from above ...
}
std::string DaemonPath() {
... your code from above ...
}
class MyPathInit {
public:
MyPathInit() {
DaemonPath();
}
} myPathInit;
}
当然,这种方法不会使您的单例模式具有线程安全性。但有时,在某些情况下,我们可以确定没有并发线程访问(例如,在加载共享库时的初始化时)。如果这个条件匹配你,这种方法可以绕过线程安全性问题,而不使用线程锁定(互斥...)。