GCC编译

The GNU Compiler Collection,通常简称GCC,是一套由GNU开发的编译器集。为什么会是一个集合,因为它不仅支持C语言编译,还支持C++, Ada, bjective C等语言。另外GCC支持的硬件平台包括:X86处理器架构,ARM架构,MIPS架构。

GCC内部由Binutils, Gcc-core,  Glibc等软件包组成

1)Binutils:它是一组开发工具,包括连接器,汇编器和其他用于目标文件和档案的工具。这个软件包会依不同的平台而不同,因为不同的架构指令集不同。

2)Gcc-core:GCC的核心部分,但是这部分默认只包含C的编译器及其公共部分,而对其他语言(C++, Ada等)的支持包需要另外安装。

3)Glibc:包含常用到的一些C的函数库,这个库提供基本的函数,用于分配内存,搜素目录,读写文件,字符串处理等。

对GNU编译器来说,程序的翻译要经过:预处理,汇编,编译,链接四个步骤。GCC经常把前三个合为一个步骤来操作,所以就有了编译模式(-c指定)和编译链接模式(-o指定输出)两种方式。

预处理阶段主要处理.h之类的头文件,#include,#ifdef,#define等。生成文件.i。

汇编阶段主要将.i的中间文件转换为汇编语言文件.s

  gnu也有专门的汇编工具,as

编译阶段主要将汇编语言文件.s转换为2进制机器语言.o

链接阶段主要是将不同的机器语言文件链接到一起,生成最终的可执行文件。

  gnu也有专门的链接工具,ld

ar 库文件操作命令,可以查看库文件中的详细情况,将多个对象问价生成一个库文件。

  ar  -t libname.a  显示所有对象文件的列表;

  ar  -rv  libname.a  objfile1.o .... objfile10.o  将objfile1.o ... objfile10.o 打包成一个库文件。

      在linux下,库文件,都以lib开头,否则gcc无法识别。

GCC的基本用法:假设源文件程序名是test.c

  -Wall,gcc选项,表示打开所有常用的警告

1)无选项编译链接:gcc test.c  将test.c预处理,汇编,编译,链接成可执行文件,未指定输出文件,默认输出a.out。

2)-o指定输出文件:gcc test.c  将test.c预处理,汇编,编译,链接成可执行文件,指定输出文件,test.out。

3)-E只进行预处理:gcc -E test.c -o test.i 将test.c进行预处理操作,生成test.i文件。生成文件后缀.i,只是简单的将define和include展开;

4)-S只进行汇编:gcc -S test.i -o test.s 将test.i进行汇编操作,生成test.s文件。

5)-c只进行编译:gcc -c test.s 将test.s进行编译操作,生成test.o文件。

6)对编译后的文件进行无选项操作:gcc test.o -o test 将test.o链接生成可执行文件test。

7)-fPIC ,产生位置无关的目标代码;

8)-L dir,将dir加到编译器,进行链接的搜索列表中;

  -lname,链接名为libname.a,libname.so的库文件。

9) -g,用在debug调试。

10) -Wl, 表示将后面的参数,传递给链接器。

11) --whole-achive    --no-whole-archive,是ld的专用命令行参数,gcc并不认识,只能通过加Wl,来传递给gcc。

        将在其之后的所有静态库包括的函数和变量输出给动态库,--no-whole-achive,表示关掉这个特性。

    --whole-achive作用IA的库中不能有函数重名。

12) -shared,表示生成动态库文件,进行动态编译时,尽量使用动态库。

13) -static,表示尽量使用静态库。

多个文件处理:

gcc test1.c test2.c -o test 将test1和test2分别预处理,汇编。编译后链接为test可执行文件输出。

gcc test1.o test2.o -o test 将test1编译后文件和test2编译后文件,链接为test可执行文件输出。

gcc -o输出之后的文件,可以直接在shell中运行。

gcc -I +dir:来指明.h文件的路径,然后在C的.h文件中,可以直接包含该路径中的.h文件,不需要写入路径。

     -D +define:来指明gcc编译过程中,添加的define(与ifdef...配套使用)

gcc在编译的过程中,会对代码进行多方面的优化,主要有这几类:

  1) 精简操作指令,

  2) 尽量满足CPU的流水线操作,

  3) 重新调整代码的执行顺序,

  4) 函数的展开调度,

gcc提供了o0-o3的几种优化级别供用户设置,

  -O0:不做优化,默认的编译选项,

  -O1:对程序做部分优化,gcc尝试减小生成代码的尺寸,缩短执行时间,

  -O2:gcc将执行几乎所有的空间时间的优化,具体的优化项目:https://blog.csdn.net/qq_31108501/article/details/51842166

  -O3:再次打开一些优化选项,

  -Os:主要是针对程序的尺寸的优化。

优化代码带来的两个问题:

  1) 调试问题,因为很多分支的合并,公用表达式的消除,等优化

  2) 内存操作顺序的影响,可以通过选项关闭,asm __ __violatile()。 

Linux下的gdb和gcc默认输出的汇编格式都是AT&T格式,但是他们都有方式来转换为intel格式;

  -masm=[intel|att] 选择intel或者att的汇编语法

  gcc -S -masm=intel test.c

gdb则是设置环境变量, set disassembly-flavor intel

对于预处理宏,    -Dmacro=string,等价于在头文件中加定义,#define macro string

        -Dmacro,等价于在头文件中加定义,#define macro 1或者#define macro

  -D中可以加空格,也可以不加空格。

gcc取消预定义,使用-U来做,可以中间加空格,也可以不加空格。

交叉编译:在一种机器结构下编译的软件将在另一种完全不同的机器结构下执行。一个常见的例子是在PC机上编译运行在ARM,MIPS上的软件。由于GCC可以在命令行显式调用编译器,从而适合交叉编译。  arm-linux-gcc  -o test.c。

arm-linux-gcc:基于ARM目标机的交叉编译软件,跟GCC所需的安装包不同,X86和ARM的指令集不同,所以Binutils不一样,gcc-core依赖于Binutils,所以gcc-core也不相同,glibc库不同。

arm-elf-gcc:也是基于ARM目标机的交叉编译软件。两者区别主要在glibc的不同。arm-linux-gcc使用GNU的glibc,而arm-elf-gc一般使用uClibc等专门为嵌入式系统开发的C库。Glibc针对PC开发,uClibc小型C语言库,实现Glibc的部分功能。

汇编中的几个字符:

  .text 部分表示该部分,需要放在代码段中;是arm_gcc的编译关键字;(一般会在之后设置为空间只读)

  .data 部分表示该部分,已初始化的全局变量的一块内存区域,需要放在数据段中;(属于静态内存分配)

  .bss 部分表示该部分,未初始化的全局变量的一块内存区域,(属于静态内存分配)

  .rodata  部分表示该部分,存放C中的字符串和#define定义的变量

  .heap堆   进程动态运行中被动态分配的内存段,大小不固定,malloc,free来进行分配与回收;

  .stack栈 存放程序临时的局部变量,函数被调用时,参数也会被压入该栈中。 

  .global 是一个全局变量,可以实现汇编和C之间传递信息,可以是main函数的函数地址;

  .global  _start

  _start:

    .......

常见的gnu的伪指令:

  .global _start   @给_start外部链接属性

  .section

我们常常需要将一些公用函数制作成函数库,供其他函数使用,函数库分为静态库和动态库,

  静态库,在程序编译是,被链接到目标代码中,程序运行时,不需要该库,

  动态库,在程序运行时,直接载入,程序运行时,还需要动态库存在。

生成静态库:  gcc -c hello.c

        ar rv libmyhello.a hello.o

        生成可执行文件时,gcc  main.c  libmyhello.a -o main

生成动态库,有三种方法,ld -G

            gcc -shared

            libtool

       使用ld命令最复杂,gcc -shared最简单,但是并不是在任何平台下都可以使用,所以GNU提供了一个更好的工具,libtool。

  使用gcc -shared来生成,gcc  -shared  -o libmyjob.so  myjob.o  将myjob.o生成动态库。

file命令:查看linux下文件格式;

size命令:查看linux下elf文件的各个段大小;

nm命令:查看linux下elf文件的符号表;

strip命令:用来去掉ELF文件中的调试信息;

ldd命令:用来查看一个程序主模块或者共享库的依赖;

linux下默认的链接脚本在/usr/lib/ldscripts/,可以使用ld  --verbose来查看

objdump -x example.o:详细显示目标文件的内容;

    -h example.o:显示目标文件的各个段的信息打印;

    -s example.o:将所有段的内容以16进制的方式打印;

    -d example.o:将所有包含指令的段反汇编;

    -r example.o:显示目标文件中的重定位表;

readelf -h example.o:显示elf文件头信息(格式定义在/usr/include/elf.h);32bit的elf格式头文件占52byte,64bit的elf格式头文件占64byte。

    -S  example.o:显示elf文件的head头文件(比objdump显示的符号表要全)(格式定义在/usr/include/elf.h);

    -s  example.o:显示elf文件中的符号表信息;

objcopy,将二进制文件copy到目标文件中的某个段,或者将某个目标文件的一部分copy到另一个目标文件中

  objcopy -I binary -O elf32-i386  -B i386   image.jpg  images.o 

gcc提供方法,将某些变量和函数可以存放在指定的段中去

  __attribute__((section("Foo"))) int global = 42;

       指定弱符号和强符号:

  __attribute__(("Weak"))  weak2 = 2;

       指定强引用,弱引用:

  __attribute__(("weakref"))  void  foo();  

      未初始化的全局变量一般当做弱符号来处理,放在common块中,也可以显示声明不放在common块中,此时作为强符号处理;

  int global __attribute__((nocommon)) a;

原文地址:https://www.cnblogs.com/-9-8/p/4463513.html