前言
Protobuf
全称Protocol buffers
,是Google
研发的一种跨语言、跨平台的序列化结构的数据格式,是一个灵活的、高效的用于序列化数据的协议。使用protobuf
时,既可以采用动态链接,也可以采用静态链接。因为protobuf
本身有一个global
的registry
。每个message type都需要去那里注册一下,而且不能重复注册。所以,假如你在A.DLL
中定义了某些message type,那么B.DLL
就只能从A.DLL
的exported的DLL interface中使用这些message type, 而不能从proto文件中重新生成C/C++
代码并包含到B.DLL
里去。并且B.DLL
也不能私自的去修改、扩展这个message type。
最近在调用tensorRT
解析onnx
模型的时候使用到了这个库,遇到了一些折磨了很久的坑,记录一下。
1、运行时内存溢出
在代码运行时,出现以下错误
[libprotobuf ERROR google/protobuf/descriptor_database.cc:641] File already exists in database: ais_msg.proto
[libprotobuf FATAL google/protobuf/descriptor.cc:2021] CHECK failed: GeneratedDatabase()->Add(encoded_file_descriptor, size):
terminate called after throwing an instance of ‘google::protobuf::FatalException’
what(): CHECK failed: GeneratedDatabase()->Add(encoded_file_descriptor, size):
原因:
有人说是因为需要通信多个进程模块都集成了相同的 *.pb.cc
和 *.pb.h
文件进行编译(动态库或者执行文件),且在编译时通过动态库 libprotobuf.so
的方式进行链接,导致在运行时报错,通过网上查找得到:**protobuf
本身有一个 global
的 registry
。每个 message type
都需要去那里注册一下,而且不能重复注册。上述的 Add
错误就是因为注册失败,原因就是因为这几个中重复注册了(多份 *.pb.cc
实现)。
PS:也有可能是
protobuf
的版本原因,我这里出现了这个错误之后,尝试了网上说的各种方法都没办法解决,后来把protobuf
的版本从11.4.3
换成6.1.3
就没报这个错误了。
解决办法:静态库编译,使用 libprotobuf.a
,即多个编译目标通过静态库的方式链接.
在编译protobuf
的时候改 configure
文件再执行 configure
// 改成下面的样子(不同版本位置不对,所以可以搜索 ac_cv_env_CFLAGS_set)
if test "x${ac_cv_env_CFLAGS_set}" = "x"; then
CFLAGS="-fPIC"
fi
if test "x${ac_cv_env_CXXFLAGS_set}" = "x"; then
CXXFLAGS="-fPIC"
fi
$ ./configure --host=arm-linux --disable-shared CFLAGS="-fPIC -fvisibility=hidden" CXXFLAGS="-fPIC -fvisibility=hidden" --prefix=/home/auto/libSrc/protobuf-3.6.1_install
$ make -j;make install
2、编译时链接库报错
**原因:**报错中说我们的工程是一个动态库,但是链接的protobuf
是一个静态库,将静态库编译为动态库时,会引起问题,请重新编译protobuf
**解决方法:**加上-fPIC
选项重新编译protobuf
库即可。
在编译protobuf
的时候改 configure
文件再执行 configure
// 改成下面的样子(不同版本位置不对,所以可以搜索 ac_cv_env_CFLAGS_set)
if test "x${ac_cv_env_CFLAGS_set}" = "x"; then
CFLAGS="-fPIC"
fi
if test "x${ac_cv_env_CXXFLAGS_set}" = "x"; then
CXXFLAGS="-fPIC"
fi
$ ./configure --host=arm-linux --disable-shared CFLAGS="-fPIC -fvisibility=hidden" CXXFLAGS="-fPIC -fvisibility=hidden" --prefix=/home/auto/libSrc/protobuf-3.6.1_install
$ make -j;make install
关于GCC
的编译选项fpic
/fPIC
, fpie
/fPIE
fPIC
的全称是 Position Independent Code
, 用于编译阶段,告诉编译器产生与位置无关代码,代码在被进程加载到内存时使用相对地址,所有对固定地址的访问都通过全局偏移表(GOT)来实现。
编译共享库时使用:
-fPIC
: Generate position-independent code if possible (large mode)
-fpic
: Generate position-independent code if possible (small mode)
编译可执行程序时使用:
-fPIE
: Generate position-independent code for executables if possible (large mode)
-fpie
: Generate position-independent code for executables if possible (small mode)注:还需要在ld时增加-pie选项才能产生这种代码。即
gcc -fpie -pie
来编译程序。单独使用哪一个都无法达到效果。
-pie;
Create a position independent executable
关于large mode
和small mode
的说明:small指程序必须位于2GB以下的地址空间;large指对地址空间没有任何限制。
在编译某个库的时候,加 fPIC 选项
和不加 fPIC 选项
能否生成动态链接库没有必然的联系,即使不加 fPIC
也可以生成 .so
文件,只是对于那种库的源文件又调用了其他的地方的代码,而这个段代码在全局内存上只能有一个实例的时候,如果在多个进程中都调用了这个库那么会产生多个实例就发生了冲突,例如:Protobuf
库。所以,我们在编译共享库的时候,一般都加上fPIC
编译选项,这样多个进程引用同一个 PIC
动态库时,可以共用内存。这一个库在不同进程中的虚拟地址不同,但操作系统显然会把它们映射到同一块物理内存上,这样就不会发生冲突。
对于对于不加 fPIC
,则加载 .so
文件时,需要对代码段引用的数据对象需要重定位,重定位会修改代码段的内容,这就造成每个使用这个 .so
文件代码段的进程在内核里都会生成这个 .so
文件代码段的 copy。每个 copy 都不一样,取决于这个 .so
文件代码段和数据段内存映射的位置。可见,这种方式更消耗内存,但是通常来说它的加载速度会更快。
文章评论