gcc编译以及Makefile与GDB调试

一:编译选项:      


gcc常用编译的选项:

-c 表示编译源文件,只编译并生成目标文件。

-E 只运行 C 预编译器。

-o 表示输出目标文件

-g 表示在目标文件中产生调试信息, 用于 gdb 调试

-D<宏定义> 编译时将宏定义传入进去

-Wall 打开所有类型的警告。

-w 不生成任何警告信息。

-ansi 只支持 ANSI 标准的 C 语法。这一选项将禁止 GNU C 的某些特色,

1、gcc 编译过程: 预编译--编译--汇编--链接


                                                                                   

2、编译过程包括下面几个阶段:

(1)预处理: 预处理器将对源文件中的宏进行展开。

(2)编译: gcc 将 c 文件编译成 汇编文件。

(3)汇编: as 将汇编文件编译成机器码。

(4)链接: 将目标文件和外部符号进行连接, 得到一个可执行二进制文

件。

 

3、gcc 编译常用选项

 

4、一般编译过程如下:

预处理阶段: 对包含的头文件( #include) 和宏定义( #define、 #ifdef等)               

            进行处理

            gcc  –E  hello.c  –o  hello.i

//-o 表示输出为指定文件类型 -E 将源文件(*.c) 转换为(*.i)

编译阶段 检查代码规范性、 语法错误等, 在检查无误后把代码翻译成汇编语言

            gcc –S hello.i –o hello.s

            //-S 将已预处理的 C 原始程序(*.i) 转换为(*.s)

链接阶段 将.s 的文件以及库文件整合起来链接为可执行程序

            gcc  hello.s –o hello.exe(或hello)

            //最后将汇编语言原始程序(*.s)和一些库函数整合成( *.exe)

5、条件编译

#include <stdio.h>

main()

{

     #ifdef cjy

     //表示如果定义了 cjy, 即命令行参数传了cjy, 就执行下面的输出

     printf("cjy is defined!\n");

     #else

     printf("cjy is not defined!\n");

     #endif

     printf("main exit\n");

}

注:gcc –E project2.c –o project2.i –D cjy      //条件编译, 用-D 传递,

       或: gcc   project2.c  –o  project2  –D  cjy        

       如果没有传 cjy 则执行#else

       gcc  –S   project2.i  –o  project2.s

       gcc   project2.s       –o  project2

6、gcc 编译库选项

      


二、函数库(静态库以及动态库简述)


1、简述

静态库是目标文件.a 的归档文件( 格式为 libname.a)

如果在编译某个程序时链接静态库, 则链接器将会搜索静态库并直接拷贝到该程序

的可执行二进制文件到当前文件中;

动态库( 格式为 libname.so[.主版本号.次版本号.发行号])

在程序编译时并不会被链接到目标代码中, 而是在程序运行时才被载入。

2、创建静态库及动态库

静态库:

gcc -c add.c    //编译 add.c 源文件生成 add.o 目标文件

ar  crsv  libadd.a  add.o  //对目标文件*.o 进行归档, 生成 lib*.a,

此处 lib 必须要写的

然后就是在编译其他文件时,链接静态库。

gcc  -o mian  main.c  -L./  –ladd –I./

//不要忘记-L 后面的那个. (即在库文件的搜索路径中添加当前路径 -ladd 表示链接库文件 libadd.a/.so     -I./表示包含在当前目录中的头文件)

如果我们直接将编译完成的.o文件直接加入到了库的末尾,却并没有更新库的有效符号表。连接程序进行连接时,在静态库的符号索引表中无法定 位刚才加入的.o文件中定义的函数或者变量。这就需要在完成库成员追加以后让加入的所有.o文件中定义的函数(变量)有效,完成这个工作需要使用另外一个 工具“ranlib”来对静态库的符号索引表进行更新。

GNU工具中ar是用来制作库文件.a的,但同时还提供了一个ranlib,从手册上看ranlib相当于ar -s,为什么这样呢?

这是由于最早在Unix系统上ar程序是单纯用来打包多个.o到.a(类似于tar做的事情),而不处理.o里的符号表。Linker程序则需 要.a文件提供一个完整的符号表,所以当时就写了单独的ranlib程序用来产生linker所需要的符号信息。也就是说,产生一个对linker合 格的的.a文件需要做ar和ranlib两步 。

动态库:

gcc -fPIC -Wall -c add.c

gcc -shared -o libadd.so add.o

最后编译链接动态库: gcc -o main main.c -L. –ladd

在运行 main 前, 需要注册动态库的路径。常采用的方法有:

cp  libadd.so  /lib   //通常采用的方法, cp  lib*.so /lib  将其copy到系统的/lib文件夹下

创建动态链接库之后, 以后就可以使用该动态链接库了

例如在 test.c 里面调用了原来库中的函数, 则执行 gcc  test.c –o test    –ladd 就可以了。较方便

3、静态库与动态库的比较

动态库只在执行时才被链接使用, 不是直接编译为可执行文件, 并且一个动态库可以被多个程序使用故可称为共享库。

静态库将会整合到程序中,在程序执行时不用加载静态库。因此,静态库会使你的程序臃肿并且难以升级,但比较容易部署。而动态库使你的程序轻便易于升级但难以部署。

4、gcc --- 优化选项

gcc 对代码进行优化通过选项“-On”来控制优化级别(n是整数)。

优化选项“-O1”:主要进行线程跳转和延迟退栈两种优化。

选项“-O2” 除了完成所有“ -O1” 级别的优化之外,还要进行一些额外的调整工作,如处理其指令调度等。

“-O3” 则还包括循环展开或其他一些与处理器特性相关的优化工作。

优化目标:

  • -O,-O1:效果是一样的,目的都是在不影响编译速度的前提下,尽量采用一些优化算法降低代码大小和可执行代码的运行速度。它主要对代码的分支,常量以及表达式等进行优化。
  • -O2 :该优化选项会牺牲部分编译速度,除了执行 -O1 所执行的所有优化之外,还会采用几乎所有的目标配置支持的优化算法,用以提高目标代码的运行速度。会尝试更多的寄存器级的优化以及指令级的优化,它会在编译期间占用更多的内存和编译时间。
  • -O3 :该选项除了执行-O2所有的优化选项之外,一般都是采取很多向量化算法,提高代码的并行执行程度,利用现代CPU中的流水线,Cache等。例如使用伪寄存器网络,普通函数的内联,以及针对循环的更多优化

  • -Os :O3的目标是宁愿增加目标代码的大小,也要拼命的提高运行速度,但是这个选项是在-O2的基础之上,尽量的降低目标代码的大小,这对于存储容量很小的设备来说非常重要。一般就是压缩内存中的对齐空白(alignment padding)。主要是对代码大小的优化,

  • -Og : 该标识会精心挑选部分与-g选项不冲突的优化选项,当然就能提供合理的优化水平,同时产生较好的可调试信息和对语言标准的遵循程度。

注意:虽然优化选项可以加速代码的运行速度,但对于调试而言将是一个很大的挑战,因为代码在经过优化之后,原先在源程序中声明和使用的变量很可能不再使用,控制流也可能会突然跳转到意外的地方, 循环语句也有可能因为循环展开而变得到处都有,所有这些对调试来讲都是不好的。所以在调试的时候最好不要使用任何的优化选项,只有当程序在最终发行的时候才考虑对其进行优化。

三、make工程管理器


1、简介

工程管理器指管理较多的文件,它是自动管理器能根据文件时间自动发现更新过的文件而减少编译的工作量,同时通过读入Makefile文件来执行大量的编译工作。(自动化)

2、Makefile基本语法及格式

target: dependency_files //目标项:依赖项

< TAB >command           //必须以 tab 开头, command为编译命令

编写好Makefile之后,就可以直接使用  make 编译了。若写了clean,可make clean 清理,重新编译。

如下:

                               

特殊处理与伪目标:

.PHONY 是 Makefile 文件的关键字, 表示它后面列表中的目标均为伪目标。伪目标通常用在清理文件、 强制重新编译等情况下。

eg:      

.PHONY:clean

clean:

     rm main  main.o

变量、 函数与规则

1、引言

    随着软件项目的变大、变复杂,源文件也越来越多,如果采用前面的方式makefile 文件,将会使makefile也变得复杂而难于维护。故通过make支持的变量定义、规则和内置函数,可以写出通用性较强的makefile文件,使得同一个makefile文件能够适应不能的项目是重要的。

2、变量: 用来代替一个文本字符串

    变量名:=变量值     简单变量展开(类似于 C++的赋值)   //通常采用这种形式

使用变量的一般方法: $(变量名)=??? 赋值        ???=$(变量名) 引用

例如:--下

                     

变量分为: 用户自定义变量, 预定义变量( CFLAGS), 自动变量, 环境变量

自动变量: 指在使用的时候, 自动用特定的值替换。

常见的有: $@ 当前规则的目标文件(重点) 

          $^ 当前规则的所有依赖文件, 以空格分隔(重点)

          eg:用自动变量      CFLAGS:= -Wall -O2 –fpic

预定义变量: 内部事先定义好的变量, 但是它的值是固定的, 并且有

些的值是为空的。

AR: 库文件打包程序默认为 ar             CC: c 编译器默认为 cc

CPP: c 预编译器, 默认为$(CC) –E        CFLAGS: c 编译器选项, 无默认

CXXFLAGS: c++编译器选项

                

规则分为: 普通规则, 隐含规则, 模式规则

隐含规则:  //*.o 文件自动依赖*.c 或*.cc 文件, 所以可以

省略main.o:main.cpp 等

模式规则: 通过匹配模式找字符串, %匹配 1 或多个任意字符串    %.o: %.cpp 任何目标文件的依赖文件是与目标文件同名的并且扩展名为.cpp 的文件

函数:

1. wildcard 搜索当前目录下的文件名, 展开成一列所有符合由其参数描述的文件名,文件间以空格间隔。 SOURCES = $(wildcard *.cpp)把当前目录下所有'.cpp'文件存入变量 SOURCES 里。

2、 字符串替换函数:(patsubst 要查找的子串,替换后的目标子串, 源字符串)。 将源字符串(以空格分隔)中的所有要查找的子串替换成目标子串。 如

OBJS = $(patsubst %.cpp,%.o,$(SOURCES))   可以和上一步对应起来看。即把 SOURCES 中'.cpp' 替换为'.o' 。

3、(addprefix 前缀, 源字符串)函数把第二个参数列表的每一项前缀加上第一个参       

   数值

3、如下为一个较为通用的Makefile:

SOURCES:=$(wildcard *.c)

OBJS:=$(patsubst %.c,%.o,$(SOURCES))

ELF:=main

CC:=gcc

CFLAGS:=-g -Wall

$(ELF):$(OBJS)

        gcc $^ -o $@

.PHONY:clean

clean:

        rm $(OBJS) $(ELF)

四、Gdb程序调试


1、gdb 常用命令

  • 首先程序编译时加 -g 选项才可打开调试选项 
  • eg:gcc –o filename –Wall filename.c –g //进入调试
  • gdb filename //进入调试
    • l    //显示代码 (list)
    • b  4    //在第四行设置断点 相当于 Windows 的 F9 (break)           //若为 b  main  则表示断点打在main处
    • r    //运行   相当于 Windows 的 F5 (run)
    • n //下一步不进入函数 相当于 Windows 的 F10  (next)
    • s //表示单步进入函数, 相当于 Windows 的 F11 (step)
    • p  I  //打印变量 I 相当于 Windows 的 Watch 窗口(print)
    • c     //运行到最后(continue)
    • bt   // 用于查看当前堆栈
    • f   4    // 进入第 4 层堆栈 
  • q     //退出 相当于 Windows 的   Shift+F5 (quit)

          

五、其他补充


1. 在 linux 中利用 system(“clear”);实现类似于 windows 里面的清屏函数 system(“cls”);

2. LINUX 中可以通过下面的方式可以实现 system("pause");功能:

printf(“Press any key to continue…”);

getchar();

getchar(); //要用两个 getchar()函数

3. linux 中如何刷新输入缓冲区, 利用 getchar()函数即可。 输出缓冲区可以利用 fflush(stdout);

4.命令 x 是用来检查内存情况, 英文是 examine 含义, 使用方法   x /20xb 变量首地址, 其中 20x 代表 16 进制的长度, b 代表字节的含义

5.针对段错误, 可以通过 ulimit -c unlimited 设置 core file size 为不限制大小, 设置完毕后, 可以通过 ulimit -a 进行查看是否设置 ok, 这时候再次运行程序,会产生 core 文件,通过 gdb 可执行程序 core 文件,

进行调试。 直接通过 bt 可以看到程序段错误时的现场, 通过 f  1 可以直接切换到程序现场(第一层堆栈)。

gdb ./test2 core

6.调试正在运行的程序, 通过 attach 进程 ID, 调试正在运行的程序

其他:更多操作可以参考: 1. gdb 调试利器 — Linux Tools Quick Tutorial

GDB 优秀博文: Linux基础 30分钟GDB调试快速突破 - 喜欢兰花山丘 - 博客园

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

smilejiasmile

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值