【Linux】Linux环境下GCC编译
文章目录
前言
最初的GNU C编译器(GCC)是由Richard Stallman开发的,他是GNU项目的创始人。Richard Stallman在1984年创立了GNU项目,旨在创建一个完整的类unix操作系统作为自由软件,以促进计算机用户和程序员之间的自由和合作。
GCC,以前是“GNU C编译器”,随着时间的推移,已经支持许多语言,如C (GCC), c++ (g++), Objective-C, objective - c++, Java (gcj), Fortran (gfortran), Ada (gnat), Go (gccgo), OpenMP, Cilk Plus,和OpenAcc。它现在被称为“GNU编译器集合”。GCC的母站点是http://gcc.gnu.org/。当前版本是GCC 7.3,发布于2018-01-25。
GCC是所谓的“GNU工具链”的关键组件,用于开发应用程序和编写操作系统。GNU工具链包括:
- GCC (GNU Compiler Collection):一个支持多种语言的编译器套件,例如C/ c++和Objective-C/ c++。
- GNU Make:一个用于编译和构建应用程序的自动化工具。
- GNU Binutils:一套二进制实用工具,包括链接器和汇编器。
- GNU调试器(GDB)。
- GNU Autotools:一个包含Autoconf, Autoheader, Automake和Libtool的构建系统。
- GNU Bison:解析器生成器(类似于lex和yacc)。
GCC是可移植的,可以在许多操作平台上运行。GCC(和GNU Toolchain)目前在所有unix上都可用。它们也被移植到Windows(由Cygwin, MinGW和MinGW- w64)。GCC也是一个交叉编译器,用于在不同的平台上生成可执行文件。
GNU C和C++编译器分别称为gcc和g++。
一、GCC编译过程
[vvvcxjvvv@localhost GCC_Test]$ cat hello.cpp
#include <iostream>
using namespace std;
int main(){
cout << "Hello World!" << endl;
cout << "Hello Linux!" << endl;
return 0;
}
[vvvcxjvvv@localhost GCC_Test]$ ls
hello.cpp
1.1 一步编译
使用g++ 源文件
可以直接对源文件进行预处理、编译、汇编、链接,生成可执行文件
[vvvcxjvvv@localhost GCC_Test]$ ls
hello.cpp
[vvvcxjvvv@localhost GCC_Test]$ g++ -o2 hello.cpp -o hello
[vvvcxjvvv@localhost GCC_Test]$ ls
hello hello.cpp
[vvvcxjvvv@localhost GCC_Test]$ ./hello
Hello World!
Hello Linux!
[vvvcxjvvv@localhost GCC_Test]$
1.2 编译过程拆解
1.2.1 预处理Pre-processing
预处理阶段主要做以下六个任务:
(1)删除#define,并做文本替换;
(2)递归展开头文件;
(3)处理以#开头的预编译指令;
(4)删除注释部分;
(5)添加行号和文件标识;
(6)保留#pragma指令,供给编译器,
预编译阶段的语言还属于高级语言,计算机不能理解.
通过包含头文件(# include)和展开宏(# define)的GNU C/CPP预处理器生成包含扩展的源代码的中间文件(.i)
[vvvcxjvvv@localhost GCC_Test]$ ls
hello.cpp
[vvvcxjvvv@localhost GCC_Test]$ g++ -E hello.cpp -o hello.i
[vvvcxjvvv@localhost GCC_Test]$ ls
hello.cpp hello.i
1.2.2 编译-Compiling
编译阶段主要进行以下五个任务:
(1)进行词法分析;
(2)语法分析;
(3)语义分析;
(4)代码优化;
(5)生成汇编指令
编译阶段的语言属于低级语言.
编译器将预处理的源代码编译为特定处理器的汇编代码(.s)
[vvvcxjvvv@localhost GCC_Test]$ g++ -S hello.i -o hello.s
[vvvcxjvvv@localhost GCC_Test]$ ls
hello.cpp hello.i hello.s
1.2.3 汇编-Assembling
汇编阶段主要是翻译指令(即将低级语言翻译成机器语言.
将汇编代码转换为目标文件中的机器代码(.o),这一步产生的文件叫做目标文件,是二进制格式。
[vvvcxjvvv@localhost GCC_Test]$ g++ -c hello.s -o hello.o
[vvvcxjvvv@localhost GCC_Test]$ ls
hello.cpp hello.i hello.o hello.s
1.2.4 链接-Linking
链接阶段主要做汇编阶段未做的事情:
(1)强弱符号的处理;
(2)外部符号的处理;
(3)指令段中虚假地址和虚假偏移的处理;
(4)符号的重定位.
链接器将目标代码与库代码链接,以生成可执行文件
[vvvcxjvvv@localhost GCC_Test]$ g++ hello.o -o hello
[vvvcxjvvv@localhost GCC_Test]$ ls
hello hello.cpp hello.i hello.o hello.s
[vvvcxjvvv@localhost GCC_Test]$ ./hello
Hello World!
Hello Linux!
[vvvcxjvvv@localhost GCC_Test]$
二、重要编译参数
2.1 gcc/g++使用手册
man g++/ man gcc
GCC(1) GNU GCC(1)
NAME
gcc - GNU project C and C++ compiler
SYNOPSIS
gcc [-c|-S|-E] [-std=standard]
[-g] [-pg] [-Olevel]
[-Wwarn...] [-Wpedantic]
[-Idir...] [-Ldir...]
[-Dmacro[=defn]...] [-Umacro]
[-foption...] [-mmachine-option...]
[-o outfile] [@file] infile...
Only the most useful options are listed here; see below for the
remainder. g++ accepts mostly the same options as gcc.
2.2 常用命令选项
无选项
:
对c/cpp文件进行预处理、编译、汇编、链接,形成可执行文件,默认生成的可执行文件名为a.out,若要指定输出文件名可使用-o
[vvvcxjvvv@localhost temp]$ ls
hello.cpp
[vvvcxjvvv@localhost temp]$ g++ hello.cpp
[vvvcxjvvv@localhost temp]$ ls
a.out hello.cpp
-E
:
预处理指定源文件而不进行编译
[vvvcxjvvv@localhost GCC_Test]$ g++ -E hello.cpp -o hello.i
-S
:
告诉g++在为代码产生汇编文件后停止编译
[vvvcxjvvv@localhost GCC_Test]$ g++ -S hello.i -o hello.s
-c
:
编译、汇编指定源文件但不进行链接
[vvvcxjvvv@localhost GCC_Test]$ g++ -c hello.s -o hello.o
-std=c++11
:
设置编译标准
[vvvcxjvvv@localhost src]$ g++ -std=c++11 hello.cpp
-g
:
告诉 GCC 产生能被 GNU 调试器GDB使用的调试信息,以调试程序。
[vvvcxjvvv@localhost src]$ g++ -g hello.cpp
-o[n]
:
使用不同的优化级别编译程序,级别为0-3,级别越高优化效果越好但编译时间越长
- -O0 表示不做优化
- -O1 为默认优化
- -O2 除了完成-O1的优化之外,还进行一些额外的调整工作,如指令调整等。
- -O3 则包括循环展开和其他一些与处理特性相关的优化工作。
[vvvcxjvvv@localhost temp]$ g++ -o0 hello.cpp -o hello_o0
[vvvcxjvvv@localhost temp]$ g++ -o1 hello.cpp -o hello_o1
[vvvcxjvvv@localhost temp]$ g++ -o2 hello.cpp -o hello_o2
[vvvcxjvvv@localhost temp]$ ls
hello.cpp hello_o0 hello_o1 hello_o2
[vvvcxjvvv@localhost temp]$ time ./hello_o0
Hello World!
Hello Linux!
real 0m0.004s
user 0m0.000s
sys 0m0.004s
[vvvcxjvvv@localhost temp]$ time ./hello_o1
Hello World!
Hello Linux!
real 0m0.002s
user 0m0.001s
sys 0m0.000s
[vvvcxjvvv@localhost temp]$ time ./hello_o2
Hello World!
Hello Linux!
real 0m0.003s
user 0m0.000s
sys 0m0.003s
[vvvcxjvvv@localhost temp]$
-Wall/-w
:
- -wall:打印警报信息
- -w:关闭警告信息
[vvvcxjvvv@localhost src]$ g++ -Wall test.cpp -o test_withwall
test.cpp: 在函数‘int main()’中:
test.cpp:5:6: 警告:未使用的变量‘a’ [-Wunused-variable]
int a = 0;
^
test.cpp:6:6: 警告:未使用的变量‘b’ [-Wunused-variable]
int b = 0;
^
[vvvcxjvvv@localhost src]$ g++ test.cpp -o test_withoutwall
[vvvcxjvvv@localhost src]$ g++ -w test.cpp -o test_withw
[vvvcxjvvv@localhost src]$
-I
:
指定头文件搜索目录
[vvvcxjvvv@localhost src]$ g++ -g -o2 say.cpp -o say
say.cpp:1:18: 致命错误:head.h:没有那个文件或目录
#include <head.h>
^
编译中断。
[vvvcxjvvv@localhost src]$ g++ -g -o2 -I../include/ say.cpp -o say
[vvvcxjvvv@localhost src]$ ./say
Hello World
-l
:
指定库文件,-l可以直接链接在/lib、/usr/lib和/usr/local/lib里的库
[vvvcxjvvv@localhost src]$ g++ -lglog hello.cpp #链接glog库
-L
:
指定库文件目录——如果库文件没放在/lib、/usr/lib、/usr/local/lib三个目录里,需要使用-L参数(大写)指定库文件所在目录
[vvvcxjvvv@localhost GCC_Demo]$ g++ main.cpp -lsayHello -Lsrc -Iinclude -o staticmain
-D
:
在使用gcc/g++编译的时候定义宏
#include <iostream>
using namespace std;
int main(){
#ifdef _HELLO_
cout << ("_HELLO_ has already been defined~") << endl;
#endif
cout << "Hello World" << endl;
return 0;
}
[vvvcxjvvv@localhost src]$ g++ -D_HELLO_ define_test.cpp -o define_withD
[vvvcxjvvv@localhost src]$ g++ define_test.cpp -o define_withoutD
[vvvcxjvvv@localhost src]$ ./define_withD
_HELLO_ has already been defined~
Hello World
[vvvcxjvvv@localhost src]$ ./define_withoutD
Hello World
[vvvcxjvvv@localhost src]$
-o
:
用于指定输出文件名
[vvvcxjvvv@localhost temp]$ ls
hello.cpp
[vvvcxjvvv@localhost temp]$ g++ -g hello.cpp
[vvvcxjvvv@localhost temp]$ ls
a.out hello.cpp
[vvvcxjvvv@localhost temp]$ g++ -g hello.cpp -o hello
[vvvcxjvvv@localhost temp]$ ls
a.out hello hello.cpp
[vvvcxjvvv@localhost temp]$
三、多源文件编译方式对比
初始目录及源文件:
[vvvcxjvvv@localhost GCC_Demo]$ tree ./
./
├── include
│ └── sayHello.h
├── main.cpp
└── src
└── sayHello.cpp
2 directories, 3 files
sayHello.h
#include <iostream>
using namespace std;
class myfriend{
private:
string name;
public:
myfriend(string a):name(a){};
void say(){cout << "Hello " << name << endl;};
};
void sayHello(string name);
sayHello.cpp
#include <iostream>
#include "sayHello.h"
using namespace std;
void sayHello(string name){
myfriend a(name);
cout << "Haven't seen you for a long time" << endl;
a.say();
}
main.cpp
#include <iostream>
#include "sayHello.h"
using namespace std;
int main(){
sayHello("Chandler");
return 0;
}
3.1 多源文件直接编译
[vvvcxjvvv@localhost GCC_Demo]$ tree ./
./
├── include
│ └── sayHello.h
├── main.cpp
└── src
└── sayHello.cpp
2 directories, 3 files
[vvvcxjvvv@localhost GCC_Demo]$ g++ main.cpp src/sayHello.cpp -Iinclude/ -o main
[vvvcxjvvv@localhost GCC_Demo]$ ./main
Haven't seen you for a long time
Hello Chandler
3.2 库文件链接
有时候想将程序功能给别人用,又不想公开源代码,就可以考虑将源文件编译成库文件,同时给别人提供相关的一组头文件即可(生成的动态/静态库文件+头文件)。头文件(.h)只是对函数,变量,类的声明,而源文件(.cpp)才是模块真正实现的部分。头文件(.h)服务于源文件。
3.2.1 静态库
在linux中静态库后缀名是.a,而windows是.lib
,并且静态链接库的名称遵循特定规则
对于linux静态库名字格式为libxxx.a,对于windows静态库名字格式为libxxx.lib
(其中xxx为起的静态库名字)
优点:寻址方便,速度快;库被打包到可执行程序中,直接发布可执行程序即可使用。
缺点:静态库的代码在编译过程中已经被载入可执行程序,因此体积较大;如果静态函数库改变了,那么你的程序必须重新编译。
使用场景:在核心程序上使用,保证速度,可忽视空间。
生成静态库
#step1:生成目标文件(*.o)
g++ -c 源文件
#step2:将目标文件生成静态库
ar rcs 静态库名称 目标文件
命令参数:
r
:将文件插入静态库中
c
:创建静态库,不管库是否存在
s
:写入一个目标文件索引到库中,或者更新一个存在的目标文件索引
查看库中的符号(函数、全局变量等): nm libxxx.a
生成静态库例子:
- gcc -c a.c b.c c.c #将a.c、b.c、c.c三个文件生成对应的目标文件a.o、b.o、c.o
- ar rcs libxxx.a a.o b.o c.o # 打包.o文件,生成静态库libxxx.a
静态编译
使用静态库语法:gcc + 源文件 + -L静态库路径 + -l静态库名 + -I头文件目录 + -o 可执行文件名
例子:
gcc main.cpp -Lsrc -lhello -I./include -o staticmain
源文件:main.cpp
静态库路径:./src
静态库名:libhello.a
头文件目录:./include
生成可执行文件:staticmain
另一种使用方式:
g++ 源文件 静态库文件 -o 可执行文件名
例如:g++ main.cpp lib/libxxx.a -o main
注意:静态库文件的顺序是在源文件之后,否则链接出错
命令参数说明:
-L
:静态库所在目录;
-l
:所使用静态库名字(去除前缀lib,去除后缀.a);对于windows,-l链接的静态库名字带lib前缀,而linux不带。例如windows下,静态库名字为libmodules.lib,则参数为-llibmodules,而对于linux静态库名字为libmodules.a,参数为-lmodules;
-I
:头文件目录位置。
生成的静态库需要跟对应的头文件同时发布,头文件中存放的是函数接口(函数声明)。
gcc默认进行动态编译,加入-static参数通知编译器进行静态编译。默认情况下, GCC在链接时优先使用动态链接库,只有当动态链接库不存在时才考虑使用静态链接库,如果需要的话可以在编译时加上-static选项,强制使用静态链接库。
例如:gcc - static main.cpp libxxx.a -o main
[vvvcxjvvv@localhost GCC_Demo]$ tree ./
./
├── include
│ └── sayHello.h
├── main.cpp
└── src
└── sayHello.cpp
2 directories, 3 files
[vvvcxjvvv@localhost GCC_Demo]$ cd src
[vvvcxjvvv@localhost src]$ g++ sayHello.cpp -c -I../include/
[vvvcxjvvv@localhost src]$ ls
sayHello.cpp sayHello.o
[vvvcxjvvv@localhost src]$ ar rs libsayHello.a sayHello.o
ar: 正在创建 libsayHello.a
[vvvcxjvvv@localhost src]$ ls
libsayHello.a sayHello.cpp sayHello.o
[vvvcxjvvv@localhost src]$ cd ../
[vvvcxjvvv@localhost GCC_Demo]$ g++ main.cpp -lsayHello -Lsrc -Iinclude -o staticmain
[vvvcxjvvv@localhost GCC_Demo]$ tree ./
./
├── include
│ └── sayHello.h
├── main.cpp
├── src
│ ├── libsayHello.a
│ ├── sayHello.cpp
│ └── sayHello.o
└── staticmain
2 directories, 6 files
[vvvcxjvvv@localhost GCC_Demo]$ ./staticmain
Haven't seen you for a long time
Hello Chandler
[vvvcxjvvv@localhost GCC_Demo]$
步骤:
- 进入src目录下对sayHello.cpp文件进行汇编,生成sayHello.o文件
- 使用
ar rs libsayHello.a sayHello.o
将sayHello.o生成静态库libsayHello.a g++ main.cpp -lsayHello -Lsrc -Iinclude -o staticmain
链接静态库生成可执行文件
可改进之处:可以创建一个lib文件夹,将静态库文件libsayHello.a放入lib文件夹中,第三步改为:
g++ main.cpp -Llib -lsayHello -Iinclude -o staticmain
,这样可以将库文件和源代码进行分离。发布时只需要将lib以及include交付给客户即可。
3.2.2 动态库(共享库)
共享库的代码是在可执行程序运行时才载入内存的,在编译过程中仅简单的引用,因此代码体积较小。 对速度要求不是很强烈的地方都应使用动态库,动态库是否加载到内存,取决于程序是否运行。
优点:
- 节省内存(共享);
- 易于更新(动态链接),更新时操作:
- 停止运行程序
- 使用新库覆盖旧库(保证新旧库名称一致,接口一致) “接口”
- 重新启动程序
缺点:延时绑定,速度略慢
动态库命名特点: 前缀lib+动态库名称+后缀.so====>libxxx.so
生成动态库
方式一:
g++ -fpic -shared 源文件名... -o 动态链接库名
-fpic
(还可写成 -fPIC)选项的功能是,令 GCC 编译器生成动态链接库(多个目标文件的压缩包)时,表示各目标文件中函数、类等功能模块的地址使用相对地址,而非绝对地址。这样,无论将来链接库被加载到内存的什么位置,都可以正常使用;-shared
选项用于生成动态链接库。在linux中动态库后缀名是.so,windows是.dll
方式二:
#step1:通过源文件产生目标文件(*.o)
g++ -c -fpic 源文件名
#step2:生成动态库文件(.so)
g++ -shared 目标文件 -o 动态库名称
动态库使用:gcc + 源文件 + -L动态库路径 + -l动态库名 + -I头文件目录 + -o 可执行文件名
注意:-l指明动态库名时,需要去点前缀(lib)、后缀(.so),保留中间部分
直接运行使用动态库的可执行文件,程序不能运行——找不到相应的动态库。
解决方法一:将动态库文件拷贝到系统动态链接库文件夹再运行程序。
windows系统动态链接库文件夹:C:\Windows\System32
Linux系统动态链接库文件夹:/lib
解决方法二:
LD_LIBRARY_PATH
:用于在程序加载运行期间查找动态链接库时指定除了系统默认路径之外的其他路径。该路径在默认路径之前查找,可以用export命令来设置值。
echo $LD_LIBRARY_PATH #输出当前LD_LIBRARY_PATH的值
export LD_LIBRARY_PATH=./lib #将./lib设置为除了默认路径之外的查找共享库(动态链接库)时路径
export LD_LIBRARY_PATH=LIBDIR1:LIBDIR2:$LD_LIBRARY_PATH
在终端输入export LD_LIBRARY_PATH=库路径名
将库路径名加入环境变量,但是终端退出了就无效了(临时设置)。
解决方法三:
将export LD_LIBRARY_PATH=库路径名
写入家目录下.bashrc文件中(永久设置)。
解决方法四:
将libxxx.so所在绝对路径追加入到/etc/ld.so.conf文件,使用sudo ldconfig -v 更新。
[vvvcxjvvv@localhost GCC_Demo]$ tree ./
./
├── include
│ └── sayHello.h
├── main.cpp
└── src
└── sayHello.cpp
2 directories, 3 files
[vvvcxjvvv@localhost GCC_Demo]$ cd src
[vvvcxjvvv@localhost src]$ g++ sayHello.cpp -I../include -fPIC -shared -o libsayHello.so
[vvvcxjvvv@localhost src]$ ls
libsayHello.so sayHello.cpp
[vvvcxjvvv@localhost src]$ cd ../
[vvvcxjvvv@localhost GCC_Demo]$ g++ main.cpp -Iinclude -Lsrc -lsayHello -o dynamicmain
[vvvcxjvvv@localhost GCC_Demo]$ ls
dynamicmain include main.cpp src
[vvvcxjvvv@localhost GCC_Demo]$ tree ./
./
├── dynamicmain
├── include
│ └── sayHello.h
├── main.cpp
└── src
├── libsayHello.so
└── sayHello.cpp
2 directories, 5 files
[vvvcxjvvv@localhost GCC_Demo]$ ./dynamicmain
./dynamicmain: error while loading shared libraries: libsayHello.so: cannot open shared object file: No such file or directory
[vvvcxjvvv@localhost GCC_Demo]$ LD_LIBRARY_PATH=src ./dynamicmain
Haven't seen you for a long time
Hello Chandler
[vvvcxjvvv@localhost GCC_Demo]$
步骤:
- 进入src目录下使用sayHello.cpp生成动态库libsayHello.so
- 将动态库进行链接生成可执行文件
- 通过LD_LIBRARY_PATH指定查找动态链接库路径,执行可执行文件
注:g++ sayHello.cpp -I../include -fPIC -shared -o libsayHello.so
可分解为以下两步:
g++ sayHello.cpp -I../include -c -fPIC
g++ -shared -o libsayHello.so sayHello.o
3.2.3 静态库/动态库对比
静态库特点:
一般扩展名为(.a或.lib),这类的函数库通常扩展名为libxxx.a或xxx.lib 。
这类库在编译的时候会直接整合到目标程序中,所以利用静态函数库编译成的文件会比较大,这类函数库最大的优点就是编译成功的可执行文件可以独立运行,而不再需要向外部要求读取函数库的内容;但是从升级难易度来看明显没有优势,如果函数库更新,需要重新编译。
动态库特点:
动态函数库的扩展名一般为(.so或.dll),这类函数库通常名为libxxx.so或xxx.dll 。
与静态函数库被整个捕捉到程序中不同,动态函数库在编译的时候,在程序里只有一个“指向”的位置而已,也就是说当可执行文件需要使用到函数库的机制时,程序才会去读取函数库来使用;也就是说可执行文件无法单独运行。这样从产品功能升级角度方便升级,只要替换对应动态库即可,不必重新编译整个可执行文件。
GCC and Make Compiling, Linking and Building C/C++ Applications
A library is a collection of pre-compiled object files that can be linked into your programs via the linker. Examples are the system functions such as printf() and sqrt().
There are two types of external libraries: static library and shared library.Because of the advantage of dynamic linking, GCC, by default, links to the shared library if it is available.You can list the contents of a library via “nm filename”.
- A static library has file extension of “.a” (archive file) in Unixes or “.lib” (library) in Windows. When your program is linked against a static library, the machine code of external functions used in your program is copied into the executable. A static library can be created via the archive program “ar.exe”.
- A shared library has file extension of “.so” (shared objects) in Unixes or “.dll” (dynamic link library) in Windows. When your program is linked against a shared library, only a small table is created in the executable. Before the executable starts running, the operating system loads the machine code needed for the external functions - a process known as dynamic linking. Dynamic linking makes executable files smaller and saves disk space, because one copy of a library can be shared between multiple programs. Furthermore, most operating systems allows one copy of a shared library in memory to be used by all running programs, thus, saving memory. The shared library codes can be upgraded without the need to recompile your program.
总结
本文整理了GCC编译的相关知识,分析了gcc/g++编译代码的过程,展示了常用命令的使用。在多源文件编译下对比了直接编译、静态链接、动态链接的区别,并比较了动态库、静态库的特点