Linux C/C++ 编译器 – gcc/g++
正文开始@Assassin
1. gcc/g++ 基本用法:
gcc/g++ 分别是linux环境下 C/C++ 的编译器,其基本使用方法:
[Assassin@Ninghai ~]$ gcc [选项] [编译文件] -o [可执行别名]
[Assassin@Ninghai ~]$ g++ [选项] [编译文件] -o [可执行别名]
执行编译好的可执行程序 test,该文件可以类比 win环境下的 test.exe 文件,linux 环境下运行可执行程序需要带上该文件的路径 (绝对路径 or 相对路径都可),在这里就是 ./ 也就是相对路径 当前文件夹下:
2. C/C++编译基本步骤:
gcc/g++分别是GNU (GNU Compiler Collection) 的C/C++编译器,gcc及g++在执行翻译过程 (因为步骤中有编译步骤,这里为了不冲突说成翻译) 的时候一般有以下四个步骤:
- ① 预处理(头文件展开,去注释,宏替换,条件编译):生成
.i 文件 - ② 编译(将预处理后的C/C++代码编译成汇编语言):生成
.s 文件 - ③ 汇编(将汇编代码转为二进制目标代码):生成
.o 文件 - ④ 链接(将汇编过程产生的二进制代码进行链接生成二进制可执行文件):生成可执行二进制文件,默认是
a.out ,可以用 -o 选项进行重命名
3. gcc/g++语法:
语法: gcc/g++?[选项]?[文件] -o [别名]
常用选项:
选项 | 具体含义 |
---|
-E | 只进行预处理,此操作不生成文件,需要把它重定向到一个输出文件里面(否则将把预处理后的结果打印到屏幕上) | -S | 编译到汇编语言,不进行汇编和链接,即只进行预处理和编译 (默认会生成 .s 文件) | -c | 编译到目标二进制代码,即只进行预处理,编译和汇编(默认会生成 .o 文件) | -o | 将处理结果输出到指定文件,该选项后需紧跟输出文件名,可简单理解为重命名 | -static | 此选项将采用静态链接的方式编译文件(gcc/g++默认编译是动态链接) | -g | 添加调试信息生成debug版本,debug版本可使用gdb进行调试(若不携带该选项则默认生成release版本,release不可调试) | -shared | 此选项将尽量使用动态库,生成文件较小 | -w | 不生成任何警告信息 | -Wall | 生成所有警告信息 | -O0/-O1/-O2/-O3 | 编译器优化选项的四个级别,-O0表示没有优化,-O1为缺省值,-O3优化级别最高 |
注:以下过程为演示g++的使用,gcc同理
3.1 预处理:
预处理的功能主要包括宏定义替换,头文件包含,条件编译,去注释等;预处理指令主要指以 # 开头的语句
[Assassin@Ninghai c_code]$ g++ -E test.cc
g++ -E 选项不会默认生成 .i 文件,也就是会把预处理信息打印在stdout上:
[Assassin@Ninghai c_code]$ g++ -E test.cc -o test.i
使用 -o 选项重命名生成 -i 文件:less查看
- 预处理功能主要包括头文件展开、去注释、宏替换、条件编译等
- 预处理指令是以 # 开头的代码行
- -E 选项的作用是让 gcc/g++ 在预处理结束后停止编译过程
- -o 选项可指定生成的目标文件名,简单来讲相当于重命名," xxx.i " 文件为已经进行过预处理的原始程序
3.2 编译:
编译时,gcc/g++ 会检查代码的规范性、是否有语法错误,用来确定代码实际所需要做的工作,检查无误后 gcc 会生成汇编代码
[Assassin@Ninghai c_code]$ g++ -S test.i -o test.s
注意:这里 -S 选项不带 -o 选项也可生成 test.s 文件,带上只是为了统一格式
- ① 在阶段此中,gcc/g++ 首先检查代码的规范性、是否有语法错误等,以确定代码的实际要做的工作,在检查无误后,将代码翻译成汇编语言
- ② 用户可以使用 -S 选项来进行翻译,该选项只进行预处理与编译,而不进行汇编,此过程过后生成汇编代码
- ③ -o 选项可指定生成的目标文件名,简单来讲相当于重命名," xxx.s " 文件为已经进行过编译的原始程序
3.3 汇编:
汇编阶段将编译所形成的汇编代码再翻译生成可重定向目标二进制文件 .o 文件,对应到 win 环境下也就是 .obj 文件;虽说是二进制文件,但也不可执行
[Assassin@Ninghai c_code]$ g++ -c test.s -o test.o
注意:这里 -c 选项不带 -o 选项也可生成 test.o 文件,带上只是为了统一格式
- ① 汇编阶段是把编译阶段生成的 " xxx.s " 文件翻译成可重定向目标二进制文件 " xxx.o "
- ② 使用 -c 选项可将汇编代码翻译成 " xxx.o " 目标文件
- ③ -o 选项可指定生成的目标文件名,简单来讲相当于重命名," xxx.o " 文件为已经进行过汇编的原始程序
3.4 链接:
预处理,编译,汇编三个阶段统称为翻译(编译)过程,完成了上述编译的过程,就到了翻译环境的最后一个阶段:链接;将多个目标二进制文件链接之后便可生成可执行二进制文件
[Assassin@Ninghai c_code]$ g++ test.o -o test
- ① 预处理,编译,汇编都成功完成之后,就来到链接操作
- ② 链接的主要任务就是将生成的各个 " xxx.o " 文件与C/C++的动静态链接库进行链接,生成可执行文件
- ③ gcc/g++ 不带 -E,-S,-c 选项时,就默认生成预处理,编译,汇编,链接全过程后的文件,相当于一步执行
- ④ 若不用 -o 选项指定生成文件的文件名,则默认生成的可执行文件名为a.out
链接是将所有.c 源文件所生成的目标文件与语言的链接库文件绑定在一起进行链接;如图所示: 可以用 file 指令查看一下 test 文件的基本信息: 当然了,gcc/g++ 不带 -E,S,c 选项可一步到位生成可执行程序: 一般来说 -o 的使用有如下两种方式,共同点是 -o 后面跟的一定是生成的可执行文件而不是源文件,个人推荐第一种:
[Assassin@Ninghai c_code]$ g++ test.cc -o test
[Assassin@Ninghai c_code]$ g++ -o test test.cc
4. 动静态链接:
4.1 函数库的概念:
语言本身会提供链接库,库可以看成一套头文件和一套库文件;比如通常使用的 cout,cin 等库函数,都是在库文件中实现的,可以通过引用对应的头文件,来使用这些C语言已经为我们封装好的代码
使用 ldd 命令可以查看可执行程序所依赖的C/C++库: Linux 环境下C/C++的库文件一般是 libc.a (libstdc++.a)和 libc.so (libstdc++.so),分别是静态库和动态库;Windows 环境下的静态库和动态库的后缀分别为.lib和.dll;
将用户所写的程序文件和第三方库提供的方法关联起来的过程称为链接;显然,链接又分为静态链接和动态链接,由上图可得知 test 文件的链接库是 .so,即 gcc/g++ 编译默认是动态链接;
4.2 动静态库:
- 静态库是指编译链接时,把库文件的代码全部拷贝到可执行文件当中,因此生成的文件比较大,但在运行时也就不再需要库文件了,静态库在linux环境下一般以.a为后缀,win环境下一般是以.lib为后缀
- 动态库与之相反,在编译链接时并没有把库文件的代码拷贝到可执行文件当中,而是在程序运行时由链接文件加载库,这样可以节省系统的开销,但是其可移植性一般来说比较差,因为缺少动态库的支持;动态库在linux环境下一般以.so为后缀,在win环境下一般是以 .dll 为后缀
静态链接和动态链接两者最大的区别就在于链接的时机不一样,静态链接是在形成可执行程序前,而动态链接的进行则是在程序执行时才开始链接
动态链接:
- 优点: 节省空间(磁盘的空间,内存的空间),bin体积小,更新维护较为方便
- 缺点: 依赖动态库,程序可移植性较差;因为把链接推迟到了程序运行时,所以每次执行程序都需要进行链接,性能会有一定损失,运行速度相对较慢
静态链接:
- 优点: 不依赖第三方库,程序的可移植性较高,执行速度较快
- 缺点: 需要将库中的代码拷贝过来,体积较大,较为臃肿;如果有多个程序文件,那么每个程序中都会加入库文件的内容,包含相同的公共代码,造成浪费;此外,更新也较为麻烦,每当库函数的代码修改了,此时就需要重新进行打包成lib文件,再去编译链接形成可执行程序
前面我们已经知道,gcc/g++默认采用的是动态链接的方式,这里也可以用 file 指令进行再一次验证: 虽然 gcc/g++ 默认采用的是动态链接,但如果需要使用静态链接,带上-static选项即可: 可以看到静态链接生成的文件确实比动态链接大得多 ^_^
之前我个人在使用 -static 静态编译时出现了诸如以下等类似的报错
/usr/bin/ld: cannot find -lc
查找了Stack Overflow后发现是我的云服务器缺少或者找不到 libc.a 静态库,解决方法是:
[Assassin@Ninghai c_code]$ sudo yum install -y glibc-static
安装之后应该是可以进行静态编译的~~
依然是使用 file 跟 ldd 查看 test_s 文件: over
|