Linux下使用gcc编译C程序的过程及常见选项

目录

 

Linux下的gcc和g++简介

预处理(Preproceessing)

编程操作示例

编译(Compilation)

汇编(Assembly)

链接(Linking)

ELF可执行文件格式

静态库和动态库

静态库

动态库

gcc常见选项


  • Linux下的gcc和g++简介

gcc 与 g++ 分别是 gnu 的 c &和c++ 语言的编译器 gcc/g++ 在执行编译工作的时候,总共需要4步:

1、预处理,生成 .i 的文件[预处理器cpp]

2、将预处理后的文件转换成汇编语言, 生成文件 .s [编译器egcs]

3、由汇编变为目标代码(机器代码)生成 .o 的文件[汇编器as]

4、连接目标代码, 生成可执行程序 [链接器ld]

  • 预处理(Preproceessing)

预处理是读取c源程序,对其中的伪指令(以#开头的指令,也就是宏)和特殊符号进行“替代”处理;经过此处理,生成一个 没有宏定义、没有条件编译指令、没有特殊符号的输出文件。这个文件的含义同没有经过预处理的源文件是相同的,仍然是C文 件,但内容有所不同。 

伪指令主要包括以下三个方面:  
  1. 宏定义指令,如#define Name TokenString,#undef以及编译器内建的一些宏,如__DATE__, __FILE__,  __LINE__,  __TIME__, __FUNCTION__ 等。   
  2. 条件编译指令,如#ifdef,#ifndef,#else,#elif,#endif等。   
  3. 头文件包含指令,如#include "FileName"或者#include 等。
预处理的过程主要处理包括以下过程:
 
  1. 将所有的#define删除,并且展开所有的宏定义 处理所有的条件预编译指令,比如#if #ifdef #elif #else #endif等 处理#include 预编译指令,
  2. 将被包含的文件插入到该预编译指令的位置。 删除所有注释 “//”和”/* */”. 添加行号和文件标识,以便编译时产生调试用的行号及编译错误警告行号。 保留所有的#pragma编译器指令,因为编译器需要使用它们
通常使用以下命令来进行预处理,参数-E表示只进行预处理:
 
gcc hello.c                 //gcc正常编译直接生成可执行文件    a.out
gcc hello.c -o hello        //gcc正常编译生成指定的可执行文件 hello
gcc hello.c -E hello.i      //gcc预处理 生成预处理文件hello.i (去掉注释,宏、头文件的替换,处理条件编译(#if0/1  #endif ),即处理所有以#开头的语句)
printf("This program complied on %s:%s\n", __DATE__,__TIME__);                //显示程序运行的日期(__DATE__),时间(__TIME__)
printf("The func  is using into %s:%d%s()\n", __FILE__,__LINE__,__func__);   //显示函数被调用的c文件(__FILE__),具体在哪=哪一行(__LINE__),哪个函数调用(__func__)
    
  • 编程操作示例

    示例代码: 

    #include 
    #include 
    int test (void);
    int main (int argc, char *argv[])
    {          
    #if 0      
        printf("#if 0"); 
    #endif     
    #if 1      
        printf("#if 1\n");
        test();
        printf("This program complied on %s:%s\n", __DATE__,__TIME__);
        printf("Hello Word!\n");
    #endif 
        return 0;
    } 
      
    int test (void)
    { 
        printf("The func  is using into %s:%d%s()\n", __FILE__,__LINE__,__FUNCTION__);
        return 0;
    }                 

     运行结果:

     Linux下使用gcc编译C程序的过程及常见选项_第1张图片

     预处理结果:

    Linux下使用gcc编译C程序的过程及常见选项_第2张图片

 

  • 编译(Compilation)

   编译程序所要作得工作就是通过词法分析和语法分析,在确认所有的指令都符合语法规则之后,将其翻译成等价的中间代码表 示或汇编代码。 优化处理是编译系统中一项比较艰深的技术。它涉及到的问题不仅同编译技术本身有关,而且同机器的硬件环境 也有很大的关系。优化一部分是对中间代码的优化,这种优化不依赖于具体的计算机。另一种优化则主要针对目标代码的生成而 进行的。对于前一种优化,主要的工作是删除公共表达式、循环优化(代码外提、强度削弱、变换循环控制条件、已知量的合并 等)、复写传播,以及无用赋值的删除,等等。后一种类型的优化同机器的硬件结构密切相关,主要的是考虑是如何充分利用 机器的各个硬件寄存器存放的有关变量的值,以减少对于内存的访问次数。另外,如何根据机器硬件执行指令的特点(如流水 线、RISC、CISC等)而对指令进行一些调整使目标代码比较短,执行的效率比较高,也是一个重要的研究课题。

   使用下面命令进行编译生成汇编文件:
gcc hello.c -E -o hello.i  //预处理

gcc -S hello.i > hello.s  //编译

cat hello.s 

Linux下使用gcc编译C程序的过程及常见选项_第3张图片

 

  • 汇编(Assembly)

   汇编过程实际上指把汇编语言代码翻译成目标机器指令的过程。对于被翻译系统处理的每一个C语言源程序,都将终经过这 一处理而得到相应的目标文件。目标文件中所存放的也就是与源程序等效的目标的机器语言代码。    目标文件由段组成。通常一 个目标文件中至少有两个段:    

  1. 代码段(文本段): 该段中所包含的主要是程序的指令。该段一般是可读和可执行的,但一般却不可写;
  2. 数据段:                主要存放程序中要用到的各种常量、全局变量、静态的数据。一般数据段都是可读,可写,可执行的。
gcc hello.s -o hello.o

 

  • 链接(Linking)

    汇编程序生成的目标文件并不能立即就被执行,其中可能还有许多没有解决的问题。例如,某个源文件中的函数可能引用了另 一个源文件中定义的某个符号(如变量或者函数调用等);在程序中可能调用了某个库文件中的函数,等等。所有的这些问题, 都需要经链接程序的处理方能得以解决。 链接程序的主要工作就是将有关的目标文件彼此相连接,也即将在一个文件中引用的符 号同该符号在另外一个文件中的定义连接起来,使得所有的这些目标文件成为一个能够被操作系统装入执行的统一整体,也就是 可执行程序。 根据开发人员指定的库函数的链接方式的不同,链接处理可分为两种:

1. 静态链接

2. 动态链接

   对于可执行文件中的函数调用,可分别采用动态链接或静态链接的方法。使用动态链接能够使终的可执行文件比较短小,并 且当共享对象被多个进程使用时能节约一些内存,因为在内存中只需要保存一份此共享对象的代码。但并不是使用动态链接就一 定比使用静态链接要优越。在某些情况下动态链接可能带来一些性能上损害。
1. 静态链接
 
gcc hello.c  -o hello -static   //使用静态库编译
du -h hello                     //查看文件大小

file hello                      //查看文件属性

Linux下使用gcc编译C程序的过程及常见选项_第4张图片

 2. 动态链接

gcc hello.c  -o hello           //默认使用动态库编译
du -h hello                     //查看文件大小

file hello                      //查看文件属性

Linux下使用gcc编译C程序的过程及常见选项_第5张图片

  • ELF可执行文件格式

   Linux/Unix的可执行文件以及动态库都是以ELF(Executable Linkage Format)存在的。在Linux下,可以使用readelf命令查看 ELF文件,关于加载过程所需要的信息都在ELF文件头里面,可以用使用readefl filename -e来查看EFL文件所有的头。我们可以 先来查看下hello.c编译出来的hello可执行文件的ELF头信息:

readelf hello -e

 

 
Linux下使用gcc编译C程序的过程及常见选项_第6张图片

 

 readelf hello -d

 Linux下使用gcc编译C程序的过程及常见选项_第7张图片

 

ldd hello

  • 静态库和动态库

    在windows和Linux下都存在着大量的库,库是什么呢?本质上来说,库时一种可执行代码的二进制形式,可以被操作系统载 入内存执行。我们通常将一些通用函数写成函数库,所以库是别人写好的,现有的,成熟的,可以复用的代码,你可以使用但要 必须得遵守许可协议。在我们现实开发过程中,不可能每一份代码都从头编写,当我们拥有库时,我们就可以直接将我们所需要 的文件链接到我们的程序中。可以为我们节省大量的时间,提高开发效率。Linux下库分为两种,静态库和动态库。这两种库相 同点是两种库都是由.o文件生成的,下边讨论一下它们的不同点:

  • 静态库

静态库文件名的命名方式是“libxxx.a”,库名前加”lib”,windows和linux下都是后缀用”.a”,“xxx”为静态库名, windows下的静态库名也叫libxxx.a; 链接时间: 静态库的代码是在编译过程中被载入程序中。 链接方式:静态库的链接是将整个函数库的所有数据都整合进了目标代码。这样做优点是在编译后的执行程序不在需要外 部的函数库支持,因为所使用的函数都已经被编进去了。缺点是,如果所使用的静态库发生更新改变,你的程序必须重新编译。

  • 动态库

动态库的命名方式与静态库类似,前缀相同为“lib”,linux下后缀名为“.so(shared object)”即libxxx.so;而windows 下后缀名为“.dll(dynamic link library)”即libxxx.dll; 链接时间:动态库在编译的时候并没有被编译进目标代码,而是当你的程序执行到相关函数时才调用该函数库里的相应函 数。这样做缺点是因为函数库并没有整合进程序,所以程序的运行环境必须提供相应的库。优点是动态库的改变并不影响 你的程序,所以动态函数库升级比较方便。它们两个还有很明显的不同点:

  1. 当同一个程序分别使用静态库,动态库两种方式生成两个可执行文件时,静态链接所生成的文 件所占用的内存要远远大于动态链接所生成的文件。这是因为静态链接是在编译时将所有的函数都编译进了程序,而动态链接是 在程序运行时由操作系统帮忙把动态库调入到内存空间中使用。

  2. 另外如果动态库和静态库同时存在时,链接器优先使用动态库。

  • 如果希望把源码 file1.c、flle2.c、...fileN.c 做成库文件,则我们可以分别通过下面命令把他们制作成静态库或动态库:
  • 静态库生成:     gcc -c file1.c     gcc -c file2.c            ...    

gcc -c fileN.c     ar  -rcs libname.a file1.o file2.o ... fileN.o 

  • 动态库生成:      

gcc -shared -fPIC -o libname.so file1.c file2.c ... fileN.c

  • 使用静态库或动态库:      gcc main.c -o myapp -L lib_path  -lname
  • gcc常见选项

选项 说明
-E 只进行预处理,不编译
-S 只编译,不汇编
-c 只编译、汇编,不链接
-g 编译生成可执行文件包含gdb调试信息,可被gdb调试
-o 指定编译生成可执行文件名
-I 指定include包含文件的搜索目录
-L 指定链接所需库(动态库或静态库)所在路径
-l 指定所需链接库的库名
-ansi ANSI标准
-std=c99 C99标准
-Werror 不区分警告和错误,遇到任何警告都停止编译
-Wall 开启大部分警告提示
--static 静态编译
-static   静态链接
-O0 关闭所有优化选项
-O1 第一级别优化,使用此选项可使可执行文件更小、运行更快,并不会增加太多编译时间,可以简写 为-O
-O2 第二级别优化,采用了几乎所有的优化技术,使用此选项会延长编译时间
-O3 第三级别优化,在-O2的基础上增加了产生inline函数、使用寄存器等优化技术
-Os 此选项类似于-O2,作用是优化所占用的空间,但不会进行性能优化,常用于生成终版本

 

 

 

 

你可能感兴趣的:(Ubuntu)