**Linux 知识补充 -- 程序的静态与动态编译** 1652270 **冯舜** Linux 下的动态编译 (动态链接) ============================ 动态编译更准确来讲应该叫动态链接 (Dynamic Linking), 它与将 `.c` 文件编译为 `.o` 文件的过程并没有关系, 而与将 `.o`、`.a` 和 `.so` 等文件链接为可执行文件的过程有关系. 动态链接即链接时, 不将所需要的库整合到可执行文件中, 而是记录下所需用库的名字, 以期在执行时由操作系统加载处于外部的库文件的链接过程. 它将库文件独立出来作为动态链接库 (通常 `.dll` 文件) 或者共享库 (通常 `.so` 文件). 以 `chelloworld.c` 和 `cpphelloworld.cpp` 文件为例, 这两个源文件编译、链接后皆须调用 "在屏幕上输出" 的函数, 这些函数默认位于系统的共享库中而不在可执行文件中. `gcc` 和 `g++` 默认采用这种链接方式, 无需额外参数: ~~~~~~~~~~~~~~~~~~~~~~~~~bash gcc -ochelloworld chelloworld.c # 编译出 C 可执行文件 chelloworld g++ -ocpphelloworld cpphelloworld.cpp # 编译出 C++ 可执行文件 cpphelloworld ~~~~~~~~~~~~~~~~~~~~~~~~~ 要动态编译之前作业的 `mysql_demo`, 也只需要指定包含目录和库即可: `g++ -omysql_demo $(mysql_config --cflags --libs) path/to/demo.cpp`. 这三个编译和运行过程见//cdynamic//、//cppdynamic//和//mysqldynamic//. !![cdynamic,chelloworld 编译运行] !![cppdynamic,cpphelloworld 编译运行] !![mysqldynamic,mysql_demo 编译运行] 可以采用 `ldd 可执行文件` 命令来查看一个可执行文件需要调用的共享库, 及这些共享库依赖的满足情况, 如//ldd//, 它们链接到 `libc` `libm` `libstdc++` `libpthread` `libmysqlclient` 等共享库. !![ldd,ldd 命令] 这三个可执行文件分别占用 8.4K、8.9K、14K, 如//dynamicsize//. !![dynamicsize,三个可执行文件的大小] Linux 下的静态编译 (静态链接) ============================ 同上一节, 静态链接 (Static Linking) 即将库文件加入可执行文件中的链接过程. 这样产生出来的可执行文件显然更浪费磁盘空间, 但是一个优点是它们可以被复制到没有对应共享库的同架构计算机同操作系统下使用. 要整合的库文件通常是 `.o` (object 文件) 或 `.a` (多个 object 的打包) 或 `.lib` (MS-DOS 或 Windows 系统下常见) 格式. 下面的静态链接的命令有一些需要外部包的帮助, 特别是静态编译 C++ 程序时必须用到 `libstdc++-static` 包. 从下列网址下载适合 CentOS 7.5 x86_64 的 RPM 包: - https://rpmfind.net/linux/rpm2html/search.php?query=glibc-static (`glibc-static` 2.17-222) - https://rpmfind.net/linux/rpm2html/search.php?query=libstdc%2B%2B-static (`libstdc++-static` 4.8.5-28) 下载后的包放在共享文件夹中传递给 Linux 虚拟机如//rpm//. !![rpm,下载后的包] 在 Linux Shell 下切换到共享文件夹, 运行 bash 命令如//rpminstall//. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~bash yum install ./glibc-static-2.17-222.el7.x86_64.rpm ./libstdc++-static-4.8.5-28.el7.x86_64.rpm -y ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ !![rpminstall,安装下载后的包] 以下命令以不同方式静态编译 C 和 C++ 程序, 详见每条命令的注释说明: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~bash linenumbers ######### C 部分 ######### # 需要 glibc-static 静态 C 库, 生成 chelloworld_static 可执行文件 gcc -static -ochelloworld_static chelloworld.c # 无需 glibc-static, 因为系统中自带一个 libc.a. 生成 chelloworld_static_norpm # 前两条命令是变量定义, 用以指定引用的 .a 文件的目录 MYLIBDIR="/usr/lib/x86_64-redhat-linux6E/lib64/" MYLIBDIR2="/usr/lib/gcc/x86_64-redhat-linux/4.8.2/" gcc -static -nostartfiles -nostdlib -ochelloworld_static_norpm ${MYLIBDIR}crt1.o ${MYLIBDIR}crti.o chelloworld.c ${MYLIBDIR}libc.a ${MYLIBDIR2}libgcc_eh.a ${MYLIBDIR}libc.a ${MYLIBDIR}crtn.o # 该命令的思路为: 用 -nostdlib 取消 gcc 默认加上的系统库, 而转为自行模拟 gcc 添加系统库和 C 运行时目标文件 (crt*.o) 的行为, 将所有涉及到 libc 等底层库的库文件替换为 .a 静态库. ######### C++ 部分 ######### # 需要 libstdc++-static 静态 C++ 库, 不需要 glibc-static, 生成 cpphelloworld_static_1, 非完全静态 g++ -ocpphelloworld_static_1 ./cpphelloworld.cpp -static-libstdc++ # 需要 libstdc++-static 和 glibc-static, 生成 cpphelloworld_static_2 g++ -ocpphelloworld_static_2 ./cpphelloworld.cpp -static # 需要 libstdc++-static, 不需要 glibc-static, 生成 cpphelloworld_static_3 MYLIBDIR="/usr/lib/x86_64-redhat-linux6E/lib64/" MYLIBDIR2="/usr/lib/gcc/x86_64-redhat-linux/4.8.2/" g++ -static -nostartfiles -nostdlib -ocpphelloworld_static_3 ${MYLIBDIR}crt1.o ${MYLIBDIR}crti.o ./cpphelloworld.cpp -lstdc++ ${MYLIBDIR}libc.a ${MYLIBDIR2}libgcc_eh.a ${MYLIBDIR}libc.a ${MYLIBDIR}crtn.o # 需要 libstdc++-static 静态 C++ 和 glibc-static, 生成 cpphelloworld_static_4 g++ -ocpphelloworld_static_4 ./cpphelloworld.cpp -Wl,-Bstatic -static-libgcc ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C 程序的所有可执行文件执行测试: !![gccall,执行所有 C 程序] 它们的调用外部共享库和大小情况: !![gccldd,所有 C 程序调用共享库和大小] 一个动态版本和两个静态版本分别占 8504、861224、750136 字节. C++ 程序的所有可执行文件执行测试: !![rung++,执行所有 C++ 程序] 它们的调用外部共享库和大小情况: !![g++ldd,所有 C++ 程序调用共享库] 注意, `cpphelloworld_static_1` 相较于动态链接出的文件只缺少了对 `libstdc++` 的调用, 仍对 `libc` 等库有调用, 因此不是完全静态的. !![g++size,所有 C++ 程序大小] 一个动态版本和四个静态版本分别占 9064、710656、1608208、1472080、1636952 字节. 可见, 静态链接生成的可执行文件大小显著上升. 静态编译测试样例 ======================= ## C 静态编译 不需要额外库. C 源文件和 Makefile 内容如//staticcmakefile//. !![staticcmakefile,C 静态链接源文件] 正常构建、运行、清理的过程如//staticcmake//. !![staticcmake,C 正常编译和静态链接、运行、清理] ## C++ 静态编译 需要的额外库: `libstdc++-static-4.8.5-28.el7.x86_64` (不需要 `glibc-static`), 在 https://rpmfind.net/linux/rpm2html/search.php?query=libstdc%2B%2B-static 下载 (适用于 CentOS x86_64 的 `libstdc++-static` 4.8.5-28), 使用 `yum install ./libstdc++-static-4.8.5-28.el7.x86_64.rpm -y` 来安装. C++ 源文件和 Makefile 内容如//staticcppmakefile//. !![staticcppmakefile,C++ 静态链接源文件] 正常构建、运行、清理的过程如//staticcppmake//. !![staticcppmake,C++ 正常编译和静态链接、运行、清理] ## 上层目录 Makefile "Linux 知识补充 -- Makefile 文件的作用及编写方法" 中撰写的上层目录 `Makefile` 可直接拿来用. !![rootmakefile2,上层目录 Makefile] 构建、运行、清理过程如//rootmake2//. !![rootmake2,上层目录里构建、运行、清理] 额外加分作业 ============================= 具体思路: - 编译的机器需要有 `mariadb-devel` 包, 可直接在 CentOS ISO 用 `yum` 获得 - 从 http://rpm.pbone.net/index.php3/stat/3/srodzaj/2/search/mariadb-5.5.56-2.el7.src.rpm 下载 mariadb 的源码 `mariadb-5.5.56-2.el7.src.rpm`, 解压得到 `.tar.gz` 文件, 再次解压它, 得到一个 CMake 工程 - 切换到工程文件夹, 将 CMake 工程进行构建: `cmake . -DMYSQL_UNIX_ADDR=/var/lib/mysql/mysql.sock`. 参数 `-DMYSQL_UNIX_ADDR=/var/lib/mysql/mysql.sock` 必须添加, 否则生成出来的库文件指定的 socket 默认路径 `/tmp/mysql.sock` 会与 CentOS 环境中的 MySQL socket 文件位置 (`/var/lib/mysql/mysql.sock`) 不同. - 构建后直接在当前目录得到一个 Makefile 工程, 再用 `make` 构建它. - 构建后得到 `./libmysql/libmysqlclient.a`, 是 MySQL 客户端库的静态库文件. 放在 `mysql_demo.cpp` 同目录中. - 再从 `libstdc++-static`、`zlib-static` 包中提取出 `libz.a`、`libstdc++.a` 等静态库文件, 放在 `mysql_demo.cpp` 同目录中. - 使用 `g++ -static -nostartfiles -nostdlib ```mysql_config --cflags``` -o mysql_demo "/usr/lib64/"crt1.o "/usr/lib64/"crti.o "/usr/lib/gcc/x86_64-redhat-linux/4.8.2/"crtbeginT.o mysql_demo.cpp ./libmysqlclient.a ./libstdc++.a "/usr/lib/x86_64-redhat-linux6E/lib64/"libpthread.a "/usr/lib/x86_64-redhat-linux6E/lib64/"libdl.a "/usr/lib/x86_64-redhat-linux6E/lib64/"librt.a "/usr/lib/x86_64-redhat-linux6E/lib64/"libm.a ./libz.a "/usr/lib/x86_64-redhat-linux6E/lib64/"libc.a "/usr/lib/gcc/x86_64-redhat-linux/4.8.2/"libgcc_eh.a "/usr/lib/x86_64-redhat-linux6E/lib64/"libc.a "/usr/lib/gcc/x86_64-redhat-linux/4.8.2/"crtend.o "/usr/lib64/"crtn.o` 完成编译. 可用 `makefile` 做一些变量替换, 详见下面的 `makefile` 文件源码. - 该命令的思路为: 用 `-nostdlib` 取消 gcc 默认加上的系统库, 而转为自行模拟 gcc 添加系统库和 C 运行时目标文件 (`crt*.o`) 的行为, 将所有涉及到 `libc` 等底层库的库文件替换为 `.a` 静态库. ~~~~~~~~~~~~~~~~~~~~~~~~Makefile all : mysql_demo MYLIBDIR = "/usr/lib/x86_64-redhat-linux6E/lib64/" MYLIBDIR2 = "/usr/lib/gcc/x86_64-redhat-linux/4.8.2/" MYLIBDIR3 = "/usr/lib64/" mysql_demo : mysql_demo.cpp $(CXX) -static -nostartfiles -nostdlib `mysql_config --cflags` -o $@ $(MYLIBDIR3)crt1.o $(MYLIBDIR3)crti.o $(MYLIBDIR2)crtbeginT.o $^ ./libmysqlclient.a ./libstdc++.a $(MYLIBDIR)libpthread.a $(MYLIBDIR)libdl.a $(MYLIBDIR)librt.a $(MYLIBDIR)libm.a ./libz.a $(MYLIBDIR)libc.a $(MYLIBDIR2)libgcc_eh.a $(MYLIBDIR)libc.a $(MYLIBDIR2)crtend.o $(MYLIBDIR3)crtn.o .PHONY : clean clean : $(RM) mysql_demo mysql_demo.o ~~~~~~~~~~~~~~~~~~~~~~~~ 将整个工程目录复制到另一台虚拟机上 (该虚拟机应有一个正确配置的 MySQL 服务器, 密码为 `root123`), 执行 `make` 并运行生成的可执行文件, 发现一切正常. ![Figure [bonus1]: 构建运行正常](pic3/bonus1.png width=100%)