gcc/g++入门
GCC/G++ 编译过程
示例代码
head.h
#ifndef __HEAD_H__
#define __HEAD_H__
#define NUM1 10
#define NUM2 20
#endif
sum.c
#include <stdio.h>
#include "head.h"
//#define DEBUG
int main(void)
{
int a = NUM1;
int aa;
int b = NUM2;
int sum = a + b;
// 小盆友: 这是一个加法运算
#ifdef DEBUG
printf("The sum value is: %d + %d = %d\n", a, b, sum);
#endif
return 0;
}
GCC/G++ 分步骤编译过程演示
# ls
head.h sum.c
// 预处理器(头文件复制到当前目录下,宏替换等操作)
# gcc -E sum.c -o sum.i
# ls
head.h sum.c sum.i
// 编译器(C/C++代码变成汇编代码)
# gcc -S sum.i -o sum.s
# ls
head.h sum.c sum.i sum.s
// 汇编器(把汇编代码变成二进制)
# gcc -c sum.s -o sum.o
# ls
head.h sum.c sum.i sum.o sum.s
// 链接器(链接其他需要的二进制代码成最后的可执行二进制代码)
# gcc sum.o -o sum
# ls
head.h sum sum.c sum.i sum.o sum.s
GCC/G++ 直接编译成可执行程序演示
## -o 是命名的意思
# gcc sum.c -o sumc
GCC/G++ 命令参数的常用选项
选项 | 解释 |
---|---|
-ansi | 只支持 ANSI 标准的 C 语法。这一选项将禁止 GNU C 的某些特色, 例如 asm 或 typeof 关键词。 |
-c | 只编译并生成目标文件。 |
-DMACRO | 以字符串"1"定义 MACRO 宏。 |
-DMACRO=DEFN | 以字符串"DEFN"定义 MACRO 宏。 |
-E | 只运行 C 预编译器。 |
-g | 生成调试信息。GNU 调试器可利用该信息。 |
-IDIRECTORY | 指定额外的头文件搜索路径DIRECTORY。 |
-LDIRECTORY | 指定额外的函数库搜索路径DIRECTORY。 |
-lLIBRARY | 连接时搜索指定的函数库LIBRARY。 |
-m486 | 针对 486 进行代码优化。 |
-o FILE | 生成指定的输出文件。用在生成可执行文件时。 |
-O0 | 不进行优化处理。 |
-O 或 -O1 | 优化生成代码。 |
-O3 | 比 -O2 更进一步优化,包括 inline 函数。 |
-shared | 生成共享目标文件。通常用在建立共享库时。 |
-static | 禁止使用共享连接。 |
-UMACRO | 取消对 MACRO 宏的定义。 |
-w | 不生成任何警告信息。 |
-Wall | 生成所有警告信息。 |
-Wl,-rpath=<your_lib_dir> | 指定编译好的程序在运行时动态库的目录(可以 man gcc 搜索 -Wl查看),当编译好程序后用 ldd 就可以看到你指定的路径了。 |
GCC/G++ 一些参数详解
如果头文件和目标文件不在同一目录下
# ls
head.h sum.c
# mkdir include
# ls
head.h include sum.c
# mv head.h include/.
# ls
include sum.c
这时候编译就会出现报错:
# gcc sum.o -o sum
sum.c:2:10: fatal error: head.h: No such file or directory
2 | #include "head.h"
| ^~~~~~~~
compilation terminated.
原因是include “”
只会查找当前执行文件所在的目录的文件,所以无法找到
#include "head.h"
指定头文件目录
// -I 指定头文件目录
# gcc -o sum sum.c -I./include
指定宏
# gcc -o sum sum.c -I./include
# ./sum
// -D 指定宏
# gcc -o sum sum.c -I./include -D DEBUG
# ./sum
The sum value is: 10 + 20 = 30
指定优化等级
// -On 指定优化等级,n代表数字,越大表示优化越好
# gcc -o sum sum.c -I./include -D DEBUG -O3
警告提示
// -Wall 会给代码中一些不好的地方提出警告
# gcc -o sum sum.c -I./include -D DEBUG -O3 -Wall
加入gdb调试编译信息
// -g 表示加入gbd调试编译信息,然后就可以gdb进行调试
# gcc -o sum sum.c -I./include -D DEBUG -O3 -g
// gdb调试
# gbd ./sum
GCC/G++ 静态库制作和使用
命名规则
- lib开头
- 静态库名
- .a结尾
如:libsrot.a
优缺点及使用场景
优点:
- 寻址方便,速度快
- 库被打包到可执行程序中,直接发布可执行程序即可使用,不需要发布.a文件出去了。
缺点:
- 静态库的代码在编译过程中已经被载入可执行程序,因此体积较大。
- 如果静态函数库改变了,那么你的程序必须重新编译。
使用场景:
- 在核心程序上使用,保证速度,可忽视空间
- 主流应用于80、90年代,现在很少用
静态库制作
- 生成对应的.o文件
- 将生成的.o文件打包成.a文件(使用工具ar)
示例
// include目录存放头文件 src目录存放源文件 lib目录存放静态库文件
# ls
include lib main.c src
// 目标:把src目录下的所有源文件制作成一个静态库文件存放到lib目录下
# cd src
# ls
add.c div.c mul.c sub.c
// 把当前目录下所有.c文件生成为.o文件
# gcc *.c -c -I../include
# ls
add.c add.o div.c div.o mul.c mul.o sub.c sub.o
// 使用ar工具打包所有.o文件到静态库文件(这里我指定的是libMyCalc.a)
# ar rcs libMyCalc.a *.o
# ls
add.c add.o div.c div.o libMyCalc.a mul.c mul.o sub.c sub.o
// 查看库中的符号(函数、全局变量等)
# nm libMyCalc.a
add.o:
0000000000000000 T add
div.o:
0000000000000000 T div
mul.o:
0000000000000000 T mul
sub.o:
0000000000000000 T sub
# mv libMyCalc.a ../lib/
# ls
add.c add.o div.c div.o mul.c mul.o sub.c sub.o
# cd ../lib/
# ls
libMyCalc.a
发布静态库
- 发布静态库(.a)
- 头文件(.h)
示例
// include目录存放头文件 src目录存放源文件 lib目录存放静态库文件
# ls
include lib main.c src
// 编译方法一:直接指定静态库和头文件目录
# gcc -o sum main.c lib/libMyCalc.a -I./include/
// 编译方法二:-L指定库目录 -l指定库名(静态库的库名是去掉lib和.a)-I指定头文件目录
# gcc main.c -L./lib -lMyCalc -I./include -o sum
# ls
include lib main.c src sum
# ./sum
sum = 26
// 查看可执行文件中的符号(函数、全局变量等)
# nm sum
// 查看文件格式
# file app
app: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), BuildID[sha1]=ff4f7b88f3a25310b65e8cbc18ad297f9fb37943, for GNU/Linux 2.6.32, not stripped
// 查看文件格式
# file main.c
main.c: C source, ASCII text
GCC/G++ 动态库制作和使用
命名规则
- lib开头
- 动态库名
- .so结尾
如:libsrot.so
优缺点及使用场景
优点:
- 节省内存(共享)
- 易于更新(动态链接):停止运行程序–>使用新库覆盖旧库(保证新旧库名称一致,接口一致) “接口”–>重新启动程序。
缺点:
延时绑定,速度略慢
使用场合:
对速度要求不是很强烈的地方都应使用动态库
注意事项:
动态库是否加载到内存,取决于程序是否运行。
制作动态库
- 生成与位置无关的代码
- 将.o打包成共享库(.so)
示例
// include目录存放头文件 src目录存放源文件 lib目录存放动态库文件
# ls
include lib main.c src
// 目标:把src目录下的所有源文件制作成一个动态库文件存放到lib目录下
# cd src
# ls
add.c div.c mul.c sub.c
// 把当前目录下所有.c文件生成为.o文件
// 参数 -fPIC 表示生成与位置无关代码
# gcc -fPIC *.c -c -I../include
# ls
add.c add.o div.c div.o mul.c mul.o sub.c sub.o
// 参数:-shared 制作动态库
# gcc -shared -o libMyCalc.so *.o -I../include
# ls
add.c add.o div.c div.o libMyCalc.so mul.c mul.o sub.c sub.o
// 查看库中的符号(函数、全局变量等)
# nm libMyCalc.so
0000000000001105 T add
0000000000004028 b completed.7326
w __cxa_finalize@@GLIBC_2.2.5
0000000000001050 t deregister_tm_clones
000000000000111f T div
00000000000010c0 t __do_global_dtors_aux
0000000000003e10 t __do_global_dtors_aux_fini_array_entry
0000000000003e18 d __dso_handle
0000000000003e20 d _DYNAMIC
000000000000116c T _fini
0000000000001100 t frame_dummy
0000000000003e08 t __frame_dummy_init_array_entry
00000000000020f8 r __FRAME_END__
0000000000004000 d _GLOBAL_OFFSET_TABLE_
w __gmon_start__
0000000000002000 r __GNU_EH_FRAME_HDR
0000000000001000 T _init
w _ITM_deregisterTMCloneTable
w _ITM_registerTMCloneTable
0000000000001138 T mul
0000000000001080 t register_tm_clones
0000000000001151 T sub
0000000000004028 d __TMC_END__
# mv libMyCalc.so ../lib/
# ls
add.c add.o div.c div.o mul.c mul.o sub.c sub.o
# cd ../lib/
# ls
libMyCalc.so
发布动态库
- 发布动态库(.so)
- 头文件(.h)
示例
// include目录存放头文件 src目录存放源文件 lib目录存放静态库文件
# ls
include lib main.c src
// 编译方法一:直接指定动态库和头文件目录
# gcc main.c -o sum lib/libMyCalc.so -I./include
// 编译方法二:-L指定库目录 -l指定库名(动态库的库名是去掉lib和.a)-I指定头文件目录
# gcc main.c -o sum -L./lib -lMyCalc -I./include
# ls
include lib main.c src sum
# ./sum
./sum: error while loading shared libraries: libMyCalc.so: cannot open shared object file: No such file or directory
// 查看可执行文件中所有依赖的共享库(ldd)
// 可以看到动态库没有找到
// 可执行程序运行时,需要加载动态库,加载动态库由动态链接器完成,ldd打印的最后一行就是动态链接器(即如下/lib64/ld-linux-x86-64.so.2 (0x00007fa297e9d000)),注意动态链接器本身也是一个动态库
// 动态链接器链接动态库通过环境变量查找动态库路径
# ldd sum
linux-vdso.so.1 => (0x00007ffeae49e000)
libMyCalc.so => not found
libc.so.6 => /lib64/libc.so.6 (0x00007fa297acf000)
/lib64/ld-linux-x86-64.so.2 (0x00007fa297e9d000)
// 打印环境变量
# echo $PATH
/opt/rh/devtoolset-9/root/usr/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/usr/local/nginx/sbin:/usr/local/libxml2/bin:/root/bin
// 设置动态库路径,使加载器知道到哪里加载我们的动态库
// 方法一:把动态库放到/lib中(放到系统库目录中)。不推荐
# cp lib/MyCalc.so /lib
// 方法二:把动态库路径加入到LD_LIBRARY_PATH字段。不过该方法只是临时的,关闭终端后就会失效
# export LD_LIBRARY_PATH=./lib
// 方法三:永久设置。原理是:家目录的.bashrc文件每次终端启动都会加载一次。不常用。
# cd //进入家目录
# vi .basnrc //在最后一行添加 export LD_LIBRARY_PATH=/home/ronghui/test/build_lib_a_test/lib/(需要添加的动态库的绝对路径)
// 方法四:永久设置。
# cd /etc/
# sudo vi ld.so.conf //在最后一行添加/home/ronghui/test/build_lib_a_test/lib/(需要添加的动态库的绝对路径)
# ldconfig -v //重新加载配置
// 方法五:gcc/g++添加运行时加载动态库路径
// 在gcc/g++最后加上-Wl,-rpath=<your_lib_dir> 指定编译好的程序在运行时动态库的目录(可以 man gcc 搜索 -Wl查看)