CMake下共享对象文件的-fPIC使用疑问:编译未加链接加了是否可行?
-fPIC的疑问解答 先给你理清楚核心背景:-fPIC的作用是生成位置无关代码,让共享库在加载时能被映射到进程地址空间的任意位置,这是共享库实现内存复用、高效加载的基础。
结合你的CMake配置和编译命令,逐个解答你的问题:
我们创建了一个共享对象文件并将其链接到可执行文件,对应的CMake代码如下:
add_executable(a ${A_SRC}) add_library(testso SHARED src/mainlib.cc test1.cc test2.cc) target_link_libraries(a, testso)我们发现编译代码时未添加
-fPIC参数,但链接阶段使用了-fPIC:g++ -std=c++11 -g -c -i../../src ./test1.cc g++ -std=c++11 -shared -fPIC ../mainlib.cc test1.o -o testso.so
1. 是否需要为每个文件添加-fPIC参数?
必须的——编译共享库的所有目标文件时都应该加上-fPIC。
你当前的情况有点特殊:编译test1.cc时没加-fPIC生成了test1.o,但链接共享库时直接传入了mainlib.cc源文件,这时候g++会先编译它,因为你指定了-shared -fPIC,所以编译mainlib.cc时会自动带上-fPIC;但test1.o是没有位置无关属性的目标文件。
这种混合PIC和非PIC目标文件的操作是有风险的:虽然32位x86平台可能允许这么做,但x86_64等现代64位平台通常会直接拒绝链接。另外,CMake其实会自动为SHARED类型的库添加-fPIC编译参数,你看到的编译命令没带-fPIC,大概率是日志输出不全或者CMake配置被修改了,正常情况下add_library(testso SHARED ...)会自动给所有源文件的编译步骤加上这个参数。
2. 这种情况会导致程序崩溃吗?
分平台和代码情况:
- 在32位x86平台:非PIC代码可能能被勉强链接进共享库,但加载时会触发大量代码段重定位(修改只读内存页),不仅浪费内存(多个进程无法复用共享库的代码页),如果代码里有地址相关的逻辑(比如全局变量、函数指针),很容易因为地址计算错误导致崩溃。
- 在64位平台(比如x86_64):链接器通常直接报错,根本生成不了共享库;如果侥幸生成成功,加载时几乎一定会因为无法正确重定位而崩溃。
3. 是否属于合规操作?
绝对不属于。
共享库的规范要求所有代码必须是位置无关的,混合非PIC目标文件的做法属于未定义行为——就算在某个平台上能运行,也不代表正确,而且代码的可移植性会极差,换个平台就直接报错或崩溃。
正确的做法
用CMake的话,只要你通过add_library(testso SHARED src/mainlib.cc test1.cc test2.cc)创建共享库,它会自动给所有源文件的编译步骤加上-fPIC,不需要手动操作。如果你的CMake没自动添加,可能是修改了默认编译选项或使用了特殊工具链,这时候可以手动添加:
target_compile_options(testso PRIVATE -fPIC)
内容的提问来源于stack exchange,提问作者Dov

