zoukankan      html  css  js  c++  java
  • GCC工具的常用指令

    1、安装

    sudo apt install gcc
    gcc --version
    

    2、编译的四个阶段

    C源码编译可以细分为四个阶段,分别为:

    • 预处理Preprocession

    • 编译Compiling

    • 汇编Assembling

    • 链接Linking

    正常情况下,GCC会直接走完4个阶段,然后输出可执行文件。但是使用不同的指令,我们也可以分阶段编译,并且对每个阶段进行控制。

    2.1 预处理

    预处理阶段,展开宏定义、插头文件代码。

    对于预处理阶段,我们可以使用-E使预处理结束编译器即停止。

    #-o选项,将预处理后文件输出到circle.i文件中;否则,直接输出到标准输出流
    gcc -E -o circle.i circle.c 
    

    如果嫌弃预处理后文件庞杂难懂,可以使用-C选项保留源文件和头文件的注释。

    gcc -E -C -o circle.i circle.c
    

    注意,预处理后文件默认是xxx.i形式。

    预处理阶段主要做的是头文件和宏展开工作,下面几个指令主要用于头文件寻址和利用宏实现条件编译的指令:

    1 条件编译

    gcc -E -Dname=banana circle.c
    

    -Dname配合源码中的宏#ifdef banana实现条件编译。

    2 头文件寻址

    # -Idirectory1:directory2:directory3... 可以指定除标准include目录以外的头文件目录
    gcc -E -I/usr/local/myinclude:/home/myinclude/ circle.c
    
    # -iquote dir1:dir2:dir3... 可以指定双引号头文件目录
    # #include "cirfunction.h"
    gcc -iquote /usr/local/header1:/usr/local/header2 circle.c
    

    更多的指令可以查询手册,但是我想以上两个基本可以一招鲜吃遍天了。

    3 include头文件搜索顺序

    • 1 源文件当前目录内
    • 2 -iqueto选项执行的目录,这个选项只对双引号("")头文件有效
    • 3 -Idir1:dir2指定目录
    • 4 CPATH环境变量指定目录
    • 5 isystem选项所指定的目录
    • 6 C_INCLUDE_PATH环境变量指定的目录
    • 7 系统默认include目录

    以上优先级由上至下依次递减。

    2.2 编译

    编译阶段将预处理后文件翻译成汇编语言文件。汇编语言几乎等同于机器语言,但是更易于阅读。它避免程序员使用0x2ffeff 0x2238fa之类的纯二进制指令,而是使用诸如addload等易于识别、记忆的词汇代替。使用汇编器可以翻译汇编语言为机器语言。

    常用指令为:

    gcc -S -fverbose-asm -o circle.s circle.c
    

    -S选项强制在编译后停止,输出xxx.s形式的汇编文件,-fverbose-asm可以将C源码的变量名作为输出文件的注释。

    2.3 汇编

    承接上一步,汇编阶段将汇编源文件编译成机器语言文件,即对象文件xxx.o。该文件既包含源码机器语言,还包含描述链接函数的符号表(symbol table)。符号表是为链接阶段准备的。

    常用指令:

    # -c选项确保生成circle.o对象文件,且不会进行到链接阶段
    #一般掌握这个也就够了
    gcc -c circle.c
    

    额外的,如果使用-Wa选项可以对汇编器(不是gcc,gcc只是在汇编阶段调用了汇编器)传递指令,譬如:

    gcc -v -o circle -Wa,-as=circle.sym,-L circle.c
    

    以上指令,紧接在-Wa后面的选项(无空格,逗号分割)就是专门传给汇编器的指令选项。-as=circle.sym表示单独保存符号表(symbol table)到circle.sym文件中。-L表示符号表包含本地符号。

    2.4 链接(重要)

    链接阶段将对象文件引用的外部对象和一些C标准函数都加入进来,是程序能够最终运行

    前三个阶段一般程序员都不会特别关系,也很少出错,而链接阶段却是必须要了解的。

    我们一般引用的C标准函数使用xxx.h的头文件,这些文件主要是声明函数,做一些宏定义之类的。函数对象本身一般放在libc.a文件中,如果是动态共享链接则放在libc.so文件中(so——shared object,有共享对象的意思)。动态链接是最常用的链接方式。

    如果只引用标准库,使用gcc -o circle circle.c即可完成编译,但是使用非标准库则需要注意外部链接寻址的问题:

    1 链接库在gcc标准搜索目录中

    这种情况直接引用即可,gcc -o circle.o circle.c -lncurses,该指令使用-l链接ncurses库,两者需要写在一起,无空格。

    2 链接库在默认库搜索目录以外

    gcc默认标准库目录一般为/usr/lib/usr/lib64,若不在此目录,还有3种方案:

    # 1 直接引用对象文件地址
    # 一般函数对象文件,都是在库名前加一个'lib',静态链接后缀为'.a',动态为'.so'
    # 此方式,动、静链接都可,取决于使用什么文件
    gcc -o circle circle.c /usr/local/lib/libncurses.a
    
    # 2 使用'-L'指令添加一个搜索目录——/usr/local/lib
    # 如果ncurses是动态链接库libncurses.so,还需要使用-Wl,rpath选项告诉程序在执行时去哪链接动态库
    # -L是给链接器使用的,主编译;rpath指令是给程序用的,能够实时找到动态链接库位置
    gcc -o circle circle.c -L/usr/local/lib -Wl,rpath=/usr/local/lib -lncurses
    
    # 需要注意的是,如果想用-l指令做静态链接,则需要添加-static
    gcc -static -o circle circle.c -L/usr/local/lib -lncurses
    
    # 3 将链接库路径添加到环境变量'LIBRARY_PATH'中
    export LIBRARY_PATH=/usr/local/lib:$LIBRARY_PATH
    

    2.5 保留中间文件和语法检查指令

    1 保留所有中间文件

    # 保存所有的中间文件——circle.i circle.s circle.o
    gcc -save-temps -o circle circle.c
    

    2 只做语法检查

    # 只是检查源文件语法,不做编译
    gcc -fsyntax-only circle.c
    

    3、动态共享链接库

    动态链接库会在程序实际运行时,才开始调用,相比静态链接库直接将引用函数的指令直接插入可执行文件的做法,有下面几点优势:

    • 引用库不用直接插入,可执行文件更小
    • 只要接口不变,共享库的实现可以随时更新,静态链接则只能重新编译才能享受更新
    • 有效内存的使用效率更高

    1 创建共享链接库

    我们把circle.c依赖的circulararea.c做成共享链接文件

    # 先生成对象文件,再生成.so文件
    # -fpic,将源码编译成位置独立代码,否则链接库的外部变量就无法引用而报错
    gcc -c -fpic circulararea.c
    gcc -shared -o libcirculararea.so circulararea.o
    

    2 使用自定义链接库

    和其他链接库使用方式完全一样:

    gcc -o circle circle.c libcirculararea.so -lncurses
    

    也可以将libcirculararea.so文件安装在标准目录中,则可以直接使用-lcirculararea来引用。

    4、警告的控制

    警告(Warning)不同于错误(error),编译时出现警告并不影响gcc继续工作,但是不建议完全忽视他们。

    再编译时,可以使用-W开头的选项细致的控制警告条目——哪些问题弹出警告,同时忽视哪些问题的警告。

    小栗子,

    # switch-default表示使用switch表达式时候,忘了些default就发出警告
    gcc -Wswitch-default circle.c
    

    使用-Wall,虽然不会检查所有的(all)警告情况,但是已经可以穷尽大部分警告了:

    gcc -Wall circle.c
    

    使用no-xxx形式的选项,可以取消对应的警告,譬如

    # 取消switch-default警告
    gcc -Wall -Wno-switch-default circle.c
    

    5、调试选项与剖析器

    1 调试选项

    -g选项使生成的对象文件或可执行文件包含符号表和源码行号信息,利用这些结合gdb便于逐步调试程序。

    gcc -g -o circle circle.c -lncurses
    

    这样生成文件会非常臃肿,只适用于调试。

    2 剖析器

    对于GNU剖析器gprof,编译时使用-pg选项(这里的g代表gnu的意思,和调试用的-g无关),会生成pmon.out文件。之后使用gprof剖析器分析,可以得到调用图,显示函数之间的调用细节等。

    # -pg生成剖析文件,-g提供源码行号信息
    gcc -pg -g circle.c -lncurses
    

    6、优化选项

    gcc提供优化选项,使用-O选项选择不同的优化等级,或者使用-f进行专项的优化。

    6.1 优化等级选择

    -O0-O1一直到-O3选项,优化等级一次递增。

    • -O0,不采取优化
    • -O1,会优化循环、合并一致常量等,使文件更小、执行更快的浅优化,因此编译时间不会过分拖沓
    • -O2,几乎应用全部支持的优化技术,譬如公共子表达式删除、指令重排、数据流分析,消耗时间相当长的深度优化
    • -O3,包含-O2全部优化,且使用inline函数优化寄存器变量分配问题
    • -Os,类似于O2等级优化,但是不会为了让生成程序更小,不会应用使用导致代码字节增大的技术

    典型指令,

    gcc -Wall -O3 -o circle circle.c curculararea.c -lncurses
    

    6.2 -f控制优化细节

    -f即flag。它可以单独使用或者再-O的基础上执行细节控制——添加或者取消某个特定的优化技术。

    典型指令,

    # no-inline-functions表示取消inline函数优化,unroll-loops表示重写循环语句,使用迭代代替循环
    gcc -Wall -O3 -fno-inline-functions -funroll-loops -o circle circle.c circulararea.c -lncurses
    

    6.3 优化的取舍

    优化可以带来很多优势——程序更小、执行更快、内存效率更高。但是优化编译会造成很长编译时间,指令重排、代码合并等会导致之后的代码变得难以理解,有些甚至会改变代码的运行逻辑,造成无法调试。想要获得最优性能,还是针对代码细节进行不同程度的优化尝试,反复试验,确定最佳性能。

    7、常见编译相关的环境变量

    C_PATH,C_INCLUDE_PATH:逗号分割的目录列表,设置头文件的搜索位置

    LIBRARY_PATH:链接库的搜索位置,这是给链接器用的。第三方链接库如果没指定在编译时链接阶段会报错

    LD_LIBRARY_PATH:动态链接库的搜索位置,由可执行文件读取该路径,这是给程序本身用的。第三方库不指定,则在运行时报错,找不到库

    【END】
  • 相关阅读:
    《Java架构师的第一性原理》24Java基础之并发第5篇Java并发编程的艺术
    《Java架构师的第一性原理》71场景题之搜索引擎ElasticSearch
    70道HR常问面试题,找工作避坑必看
    《Java架构师的第一性原理》10计算机基础之计算机组成原理
    《Java架构师的第一性原理》00计算机的第一性原理
    photoshop--历史记录画笔工具-- 所画之处恢复原图像
    photoshop--去色--彩色图像变成灰度图像
    photoshop--历史记录
    qt5-循环遍历语句foreach
    qt5串口通信
  • 原文地址:https://www.cnblogs.com/Franken-Fran/p/gcc_use.html
Copyright © 2011-2022 走看看