【gcc与g++】:联系、区别和命令

  gcc与g++ 分别是GNU的c与c++的编译器 ,gcc和g++的用法一样,由于c和c++的库文件命名方式不同,所以主要区别在于gcc编译c文件,g++默认编译c++文件。但是g++可以用来编译c文件,因为g++碰到c文件会调用gcc去编译

gcc和g++常用命令

  gcc和g++指令的一般格式为:[gcc|g++] [选项] 要编译的文件 [选项] [目标文件]

  • -x language filename 

  设定文件所使用的语言, 使后缀名无效, 对以后的多个有效。也就是根据约定 C 语言的后缀名称是 .c 的,而 C++ 的后缀名是 .C 或者 .cpp。

  如下:将hello.pi看成c语言文件。

gcc -x c hello.pig 
  • -x none filename

  关掉上一个选项,也就是让gcc根据文件名后缀,自动识别文件类型

  如下:在编译hello2.c时使用后缀识别。

gcc -x c hello.pig -x none hello2.c 
  • -c

  只激活预处理,编译,和汇编,生成.o文件或.obj文件。

gcc -c hello.c
  • -S

  只激活预处理和编译,就是指把文件编译成为汇编代码

gcc  -S hello.c
  • -E

  只激活预处理,这个不生成文件, 你需要把它重定向到一个输出文件里面

#输出到hello.i
gcc -E hello.c -o hello.i 
#重定向到hello.txt
gcc -E hello.c > hello.txt
#more查看
gcc -E hello.c | more
  • -o file

  制定目标名称, 默认的时候, gcc 编译出来的文件是 a.out。

  如下:输出hello文件

gcc hello.c -o hello
  • -C

  在预处理时,不删除注释信息

  一般与-E一起使用。

gcc -C -E hello.c -o hello.i
  • -g 

  产生符号调试工具(GNU的gdb)所必要的符号资讯,要想对源代码进行调试,我们就必须加入这个选项。

  • -ggdb 

  此选项将尽可能的生成 gdb 的可以使用的调试信息。

  • -D 宏名

  编译时添加宏名这个宏定义。

  • -O0 、-O1 、-O2 、-O3 

  编译器的优化选项的 4 个级别,-O0 表示没有优化, -O1 为默认值,-O3 优化级别最高。对程序进行优化编译l链接,整个源代码会在编译、链接过程中进行优化处理,产生的可执行文件的执行效率可以提高,但是,编译、链接的速度就相应地要慢一些。

  • -static 

  此选项将禁止使用动态库,所以,编译出来的东西,一般都很大,也不需要什么动态连接库,就可以运行。

  • -llibrary

  制定编译的时候使用的库。

  • -v

  打印出编译器内部编译各过程的命令行信息和编译器的版本。

  • -l dir

  在头文件的搜索路径列表中添加dir目录

  • -L dir

  在库文件的搜索路径列表中添加dir目录

  • -M

  生成文件关联的信息。包含目标文件所依赖的所有源代码。

  •  -e funtionName

  指定入口函数为functionName函数,默认情况下的入口函数时main。

  例子startup.c

  

 

 

   运行命令:gcc startup.c -o startup -e print,结果向下

  

 

 

 

编译过程

  如下图分为4个阶段:

  1. 预处理阶段:预编译器(cpp)生成预编译文件(.i文件): gcc –E hello.c –o hello.i
  2. 编译阶段:编译器(cc1)生成汇编代码(.s文件):gcc –S hello.i –o hello.s
  3. 汇编阶段:汇编器(as)生成目标文件(.o文件):gcc –c hello.s –o hello.o
  4. 链接阶段:链接器(ld)生成可执行文件:gcc hello.o –o hello
gcc  hello.c  -o hello

 

gcc hello.c

 

预编译

  预编译过程主要处理那些源代码文件中的以 "#" 开始的预编译指令。gcc使用的预编译器为cpp。

  1. 将所有的"#define"删除,并且展开所有的宏定义
  2. 处理所有条件编译指令,如"#if","#ifdef","#elif ","#else","#endif";
  3. 处理"#include"预编译指令,将被包含的文件插入到该预编译指令的位置。注意:该过程递归进行,也就是说被包含的文件可能还包含其他文件。
  4. 删除所有的注释"//"和  "/* */",使用-C选项可以保留注释;
  5. 添加行号和文件标识,如 #2 "hello.c" 2,以便于编译时编译器产生调试用的行号信息及用于编译时产生编译错误或警告时能够显示行号;
  6. 保留所有的 #pragma 编译器指令,因为编译器须要使用它们。
cpp hello.c  > hello.i
#或者
gcc -E hello.c -o hello.i

编译

  编译过程通过词法和语法分析,确认所有指令符合语法规则(否则报编译错),之后翻译成对应的中间码,在Linux中被称为RTL(Register Transfer Language),通常是平台无关的,这个过程也被称为编译前端编译后端对RTL树进行裁减,优化,得到在目标机上可执行的汇编代码。gcc采用as作为其汇编器,所以汇编码是AT&T格式的,而不是Intel格式,所以在用gcc编译嵌入式汇编时,也要采用AT&T格式。

gcc -S hello.i -o hello.s

汇编

  汇编器将上面生成的汇编代码转为机器可以执行的指令。每一条汇编语句对应一条机器指令。所以汇编过程比编译过程简单,没有复杂的语法、语义,也不需要优化指令。只是根据汇编指令和机器指令的对照表一一翻译就可以了,最终输出的是目标文件.o,即机器码

as hello.s -o hello.o
#或者
gcc -c hello.c -o hello.s

链接

  在成功编译之后,就进入了链接阶段。在这里涉及到一个重要的概念:函数库

  在Hello World 程序中并没有定义 "printf" 的函数实现,且在预编译中包含进的 "stdio.h" 中也只有该函数的声明,而没有定义函数的实现,那么,是在哪里实现"printf" 函数的呢?系统把这些函数实现都被做到名为libc.so.6的库文件中去了,在没有特别指定时,gcc会到系统默认的搜索路径 "/usr/lib" 下进行查找,也就是链接到libc.so.6库函数中去,这样就能实现函数"printf" 了,而这也就是链接的作用。

 函数库一般分为静态库动态库两种。静态库是指编译链接时,把库文件的代码全部加入到可执行文件中,因此生成的文件比较大,但在运行时也就不再需要库文件了。其后缀名一般为 ".a" 。动态库在编译链接时并没有把库文件的代码加入到可执行文件中,而是在程序执行时由运行时链接文件加载库,这样可以节省系统的开销。动态库一般后缀名为 ".so",如前面所述的libc.so.6就是动态库。gcc在编译时默认使用动态库

  链接器ld将各个目标文件组装在一起,解决符号依赖,库依赖关系,并生成可执行文件。链接的时候还会用到静态链接库和动态连接库。静态库和动态库都是 .o目标文件的集合。动态库在链接时只创建一些符号表,而在运行的时候才将有关库的代码装入内存,映射到运行时相应进程的虚地址空间。如果出错,如找不到对应的.so文件,会在执行的时候报动态连接错(可用LD_LIBRARY_PATH指定路径)。

参考

  《深入理解计算机系统》

  https://zhaoyang.blog.csdn.net/article/details/84770797

  https://www.runoob.com/w3cnote/gcc-parameter-detail.html

 



 

posted @ 2020-05-19 19:47  Chen沉尘  阅读(1191)  评论(0编辑  收藏  举报