作者:ciaos | 来源:互联网 | 2024-12-01 09:58
本文探讨了一个在MacMavericks系统上使用Clang++成功编译但通过RCMDSHLIB构建并在R中加载时遇到‘符号未找到’错误的C++程序问题。文章详细分析了错误原因,并提供了有效的解决方案。
在Mac Mavericks系统上,我遇到了一个有趣的问题:一个简单的C++程序虽然可以使用clang++
成功编译,但在尝试通过R CMD SHLIB
构建并使用dyn.load
在R中加载时却失败了。
此C++代码(保存在simple.cpp
文件中)利用了Gurobi优化器:
#include "gurobi_c++.h"
#include
void fxn() {
GRBEnv env = GRBEnv(); // 创建Gurobi环境
GRBModel colgen = GRBModel(env); // 创建空模型对象
colgen.addVar(0, 1, 0.0, GRB_BINARY); // 向模型添加二进制变量
std::cout <<"Hello world" <}
int main(int argc, char **argv) {
fxn();
return 0;
}
使用clang++
编译并运行此代码时,可以成功链接至Gurobi库:
$ clang++ simple.cpp -I/Library/gurobi562/mac64/include \
-L/Library/gurobi562/mac64/lib -lgurobi_c++ -lgurobi56 \
-stdlib=libstdc++ -lpthread -lm
$ ./a.out
Hello world
然而,使用R CMD SHLIB
编译时虽然成功,但在R中加载生成的动态库时却出现了错误:
$ MAKEFLAGS="PKG_CXXFLAGS=-I/Library/gurobi562/mac64/include" R CMD SHLIB \
simple.cpp -L/Library/gurobi562/mac64/lib -lgurobi_c++ -lgurobi56 \
-stdlib=libstdc++ -lpthread -lm
...
Error in dyn.load("simple.so") :
无法加载共享对象 '[路径]/simple.so':
dlopen([路径]/simple.so, 6): Symbol not found: __ZN8GRBModel6addVarEdddcNSt3__112basic_stringIcNS0_11char_traitsIcEENS0_9allocatorIcEEEE
引用自: [路径]/simple.so
预期在: 平坦命名空间
在 [路径]/simple.so
通过c++filt
工具,我们可以看到缺失的符号是GRBModel::addVar(double, double, double, char, std::__1::basic_string, std::__1::allocator >)
,这应由Gurobi库提供。
通常,这类“符号未找到”的错误是因为没有正确链接所需的库。尽管我已成功编译并运行了simple.cpp
,并且在使用R CMD SHLIB
时传递了相同的链接选项,问题仍然存在。
经过进一步调查,我发现问题可能与编译代码时使用的-stdlib=libstdc++
选项有关。移除此选项后,首次编译时就会出现链接错误,指出同样的未定义符号问题。
解决方案
#1
最终,我找到了解决方案,关键在于如何向R CMD SHLIB
传递-stdlib=libstdc++
选项。R CMD SHLIB
会调用clang++
两次,一次用于编译阶段生成对象文件(如simple.o
),另一次用于链接阶段生成共享对象(如simple.so
)。默认情况下,R CMD SHLIB
仅在第二次调用时传递-stdlib=libstdc++
选项,我们需要确保此选项也在第一次调用时生效。通过将-stdlib=libstdc++
添加到PKG_CXXFLAGS
中即可实现这一目标:
$ PKG_CXXFLAGS="-I/Library/gurobi562/mac64/include -stdlib=libstdc++" R CMD SHLIB \
simple.cpp -L/Library/gurobi562/mac64/lib -lgurobi_c++ -lgurobi56 \
-stdlib=libstdc++ -lpthread -lm
...
这样,dyn.load("simple.so")
在R中就可以正常工作了。不过需要注意的是,为了能够从R中调用这些函数,还需要使用extern "C"
或类似的方法暴露函数。
#2
另一个解决方案是调整函数的链接方式,因为R期望的是C链接而非C++链接。可以通过在函数声明前加上extern "C"
来解决这一问题:
#include
extern "C" void fxn() {
std::cout <<"Hello world" <}
然后使用如下命令进行编译和测试:
$ R --vanilla CMD SHLIB tmp.cpp && R --vanilla -e "dyn.load('tmp.so'); .C('fxn')"
...
> dyn.load('tmp.so'); .C('fxn')
Hello world
list()