【Linux系统编程】Linux开发环境下C/C++编译和调试知识总结

GCC

如何对制作完成的.c文件进行编译

GCC: 编译套件

  • GCC原名为GNU C语言编译器(GNU C Compiler)

  • GCC(GNU Compiler collection, GNU编译器套件)是由 GNU开发的编程语言译器。GNU编译器套件包括C、C++、Objective-c、Java、Ada和 Go语言前端,也包括了这些语言的库(如libstdc++, libgcj等)

  • GCC不仅支持C的许多"方言",也可以区别不同的c语言标准;可以使用命令行选项来控制编译器在翻译源代码时应该遵循哪个C标准。如:当使用命令行参数 '-std=c99` 启动GCC时,编译器支持C99标准。

  • 安装命令sudo apt install gcc g++(版本> 4.8.5 :支持C++11特性)

  • 查看版本 gcc/g++ -v/–version

  • 编译程序: gcc test.c -o app(aoo为指定输出文件名 默认a.out)

编程语言的发展:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Y9zSPoO7-1658457466120)(C:\Users\85420\AppData\Roaming\Typora\typora-user-images\image-20220719141709668.png)]

GCC工作流程
  1. 源代码 .h .c .cp
  2. (预处理)预处理器: 对头文件进行展开;将代码注释删除;进行宏替换 输出文件: .i
  3. (编译器)汇编代码:输出文件: .s
  4. (汇编器)目标代码:输出文件: .o (计算机能够识别的目标指令)
  5. (链接器)目标代码 + 启动代码 + 库代码 + 其他目标代码 -> 可执行程序 输出文件: .exe .out

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mSZ1j1ce-1658457466121)(C:\Users\85420\AppData\Roaming\Typora\typora-user-images\image-20220719143548533.png)]

GCC常用参数选项:

# (-E: 对test.c 进行预处理, -o: 生成目标文件 app)
gcc test.c -E -o app
# (直接对源文件进行 -S 那么会一次性执行 预处理 和 编译操作)
gcc test.c -S  
# 在程序编译的时候,指定一个宏(DEBUG)-DDEBUG
# 方便调试信息的输出 Log输出
gcc test.c -o test -D DEBUG 

# -Wall 生成所有警告信息
gcc test.c -o test -Wall

# -On n的取值范围:0~3。编译器的优化选项的4个级别,-○0表示没有优化,-o1为缺省值,-o3优化级别最高
# 效果 可以达到反汇编的效果,通过优化将一些代码逻辑信息隐藏 ; 还有性能优化等
# 如int a = 10; int b = a; 优化后 int a = 10; int b = 10;
gcc test.c -o test -O1
#include <stdio.h>

int main()
{
#ifdef DEBUG
     printf("aaaaaaa\n");
#endif
     printf("Hello World\n");
     return 0;
}

注意:发布Release版本 需要把所有Log信息输出都去除,所以

  • 在调试期间,可以加上宏进行Log信息输出 。./main DEBUG
  • 在Release时,就不加上宏防止Log信息输出 ./main
gcc vs. g++

gcc 和 g++ 都是GNU(组织)的一个编译器。

  • 误区一:gcc只能编译c代码,g++只能编译c++代码。其实两者都可以。

    • 请注意:后缀为 .c 的, gcc把它当作是C程序,而g++当作是 c++程序
    • 后缀为 .cpp 的,两者都会认为是C++程序,C++的语法规则更加严谨一些
    • 编译阶段, g++会调用gcc,对于C++代码,两者是等价的.但是因为gcc命令不能自动和C++程序使用的库链接,所以通常用g++来完成链接,为了统一起见,干脆编译/链接都用g++ 了。
  • 误区二:gcc 不会定义__cplusplus 宏,而g++会

    • 实际上,这个宏只是标志着编译器将会把代码按C还是C++语法来解释
    • 如上所述,如果后缀为.c,并且采用gcc 编译器,则该宏就是未定义的,否则就是已定义
  • 误区三:编译只能用gcc,链接只能用g++

    • 严格来说,这句话不算错误,但是它混淆了概念,应该这样说:编译可以用gcc/g++,而链接可以用g++或者gcc -lstdc++。
    • gcc命令不能自动和C++程序使用的库链接,所以通常使用g++来完成链接。但在编译阶段,g++会自动调用gcc,二者等价

  • 库文件是计算机上的一类文件,可以简单的把库文件看成―种代码(二进制代码)仓库,它提供给使用者一些可以直接拿来用的变量、函数或类。
  • 库是特殊的一种程序.编写库的程序和编写一般的程序区别不大,只是库不能单独运行。
  • 库文件有两种:静态库和动态库(共享库)
    • 静态库:在程序的链接阶段被复制到了程序中;
    • 动态库:在链接阶段没有被复制到程序中,而是程序在运行时由系统动态加载到内存中供程序调用。
  • 库的好处: 1. 代码保密 2. 方便部署和分发
工作原理:
  • 静态库:GCC进行链接时,会把静态库的代码打包到可执行程序中

  • 动态库:GCC进行链接时,动态库的代码不 会被打包到可执行程序中

  • 程序启动之后,(当运行到使用动态库里面 API 的时候) (整个)动态库会被动态加载到内存中,通过ldd (list dynamic dependencies)命令检查动态库依赖关系

    boyangcao@MyLinux:~/Linux/Lesson06/library$ ldd main
    	linux-vdso.so.1 (0x00007ffc995f7000)
    	libcalc.so => not found  
    	libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f99cb99c000)
    	/lib64/ld-linux-x86-64.so.2 (0x00007f99cbf8f000)
    

    libcalc.so => not found 没有找到目标动态库地址

  • 如何定位共享库文件呢?
    当系统加载可执行代码时候,能够知道其所依赖的库的名字,但是还需要知道绝对路径。此时就需要系统的动态载入器来获取该绝对路径。对于elf格式的可执行程序,是由ld-linux.so来完成的,它先后搜索elf文件的 DT_RPATH段 -> 环境变量LD_LIBRARY_PATH -> /etc/ld.so.cache文件列表 -> /lib/,/usr/lib目录找到库文件后将其载入内存。

静态库的制作

命名规则:

  • Linux : libxxx.a

    lib:前缀(固定)

    xxx:库的名字,自己起

    .a:后缀(固定)

  • windows : libxxx.lib

静态库的制作:

  • gcc获得.o 文件

    gcc -c add.c div.c mult.c sub.c 
    
  • 将.o 文件打包,使用ar 工具(archive)

    ar rcs libxxx.a xxx.o xxx.o
    
    • r :将文件(.o)插入备存文件(libxxx.a)中
    • c :建立备存文件(libxxx.a)
    • s :索引
# -I directory 指定include包含文件的搜索目录
gcc main.c -o app -I ./include/
# -l 在程序编译的时候,指定使用的库
# -L 指定编译的时候,搜索的库的路径。
gcc main.c -o app -I ./include/ -l calc -L ./lib/
动态库的制作

(共享库)

命名规则:

  • Linux:libxxx.so
    • lib:前缀(固定)
    • xxx: 库的名字,自己起
    • .so:后缀(固定)

​ 在Linux下是一个可执行文件

  • windows : libxxx.dll

动态库的制作:

  • gcc得到.o文件,得到和位置无关的代码

    gcc -c -fpic a.c b.c
    # gcc -c -fPIC a.c b.c 
    # -fpic/-fPIC 生成与位置无关的代码:用于编译阶段,产生的代码没有绝对地址,全部用相对地址,这正好满足了共享库的要求,共享库被加载时地址不是固定的。如果不加-fpic ,那么生成的代码就会与位置有关,当进程使用该.so文件时都需要重定位,且会产生成该文件的副本,每个副本都不同,不同点取决于该文件代码段与数据段所映射内存的位置。
    
  • gcc得到动态库

    gcc -shared a.o b.o -o libcalc.so
    

动态库的使用:

  • 将制作好的动态库放入目标工程目录下(./lib/)

  • 编译main.c得到可执行文件main

    (附加:参数 -I 指定include查询目录 参数-L 指定搜索的库的路径 参数-I 指定使用的库)

gcc main.c -o main -I ./include/ -L ./lib/ -l calc
  • 配置动态库地址

    • 第一种方法:环境变量LD_LIBRARY_PATH
    # 当前主机/虚拟机下的环境配置信息(以键值对形式出现) (PATH:环境变量 在其中寻找调用的函数)
    env
    
    # 配置环境变量LD_LIBRARY_PATH 使系统的动态载入器获取该动态库的绝对路径
    # export :添加环境变量 $LD_LIBRARY_PATH: : 获取原先LD_LIBRARY_PATH的内容 (:后跟新添加的内容)
    export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/boyangcao/Linux/Lesson06/library/lib
    
    # 现在输出LD_LIBRARY_PATH环境变量的内容就可以看到目标动态库路径了
    boyangcao@MyLinux:~/Linux/Lesson06/library$ echo $LD_LIBRARY_PATH
    :/home/boyangcao/Linux/Lesson06/library/lib
    boyangcao@MyLinux:~/Linux/Lesson06/library$ ldd main
    	linux-vdso.so.1 (0x00007ffd893a1000)
    	libcalc.so => /home/boyangcao/Linux/Lesson06/library/lib/libcalc.so (0x00007fbd04e4d000)
    	libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fbd04a5c000)
    	/lib64/ld-linux-x86-64.so.2 (0x00007fbd05251000)
    

    不过这个临时配置环境变量,关闭终端后重新打开就会消失。

    永久配置环境变量方法:

    1. 用户级别

      vim .bashrc
      # 1. 将export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/boyangcao/Linux/Lesson06/library/lib
      # 插入.bashrc即可
      
      # 2. 运行(更新).bashrc
      . .bashrc
      source .bashrc
      
    2. 系统级别

      sudo vim /etc/profile
      # 1. 将export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/boyangcao/Linux/Lesson06/library/lib
      # 插入.bashrc即可
      
      # 2. 运行(更新)/etc/profile
      source /etc/profile
      
    • 第二种方法:/etc/ld.so.cache文件列表
    # 都是二进制数据 无法直接修改
    vim /etc/ld.so.cache
    # 通过这里间接进行配置
    sudo vim /etc/ld.so.conf
    # 将 /home/boyangcao/Linux/Lesson06/library/lib 保存里面即可
    # 更新
    sudo ldconfig
    
    • 第三种方法:/lib/,/usr/lib目录 (不建议这么做)

    本身就包含了系统的很多库文件,如果写入自己的库文件可能会把系统库文件替换,可能会出现问题。

静态库 vs. 动态库

程序编译成可执行程序的过程:

静态库、动态库区别来自链接阶段如何处理,链接成可执行程序。分别称为静态链接方式和动态链接方式。

  • 静态库的优缺点:
    • 优点:
      • 静态库被打包到应用程序中加载速度快
      • 发布程序无需提供静态库,移植方便
    • 缺点:
      • 消耗系统资源,浪费内存
      • 更新、部署、发布麻烦(每次更新需要重新编译)

  • 动态库的优缺点:
    • 优点:
      • 可以实现进程间资源共享(共享库) (只需要加载进入内存一次)
      • 更新、部署、发布简单
      • 可以控制何时加载动态库
    • 缺点
      • 加载速度比静态库慢
      • 发布程序时需要提供依赖的动态库
image-20220719201601947

MakeFile

  • 一个工程中的源文件不计其数,其按类型、功能、模块分别放在若干个目录中,Makefile 文件定义了一系列的规则来指定哪些文件需要先编译.哪些文件需要后编译.哪些文件需要重新编译,甚至于进行更复杂的功能操作,因为Makefile文件就像一个shell脚本一样,也可以执行操作系统的命令。
  • Makefile 好处:"自动化编译”,一旦写好,只需要一个 make命令,整个工程完全自动编译,极大的提高了软件开发的效率。make 是一个命令工具,是一个解释Makefile 文件中指令的命令工具,大多数的 IDE都有这个命令,比如Delphi 的 make, Visual C++的nmake,Linux 下GNU 的make。

Makefile文件命名和规则:

  • 文件命名:makefile或者Makefile

    • Makefile 规则

      • 一个 Makefile 文件中可以有一个或者多个规则

        • 目标…:依赖…
          命令(shell命令)

          app:sub.c add.c mult.c div.c main.c
                  gcc sub.c add.c mult.c div.c main.c -o app
          
        • 目标:最终要生成的文件(伪目标除外)

        • 依赖:生成目标所需要的文件或是目标

        • 命令:通过执行命令对依赖操作生成目标(命令前必须Tab缩进)

  • Makefile 中的其它规则一般都是为第一条规则服务的。

    (注意:这种方式效率更高(分开写),这样的话当某个.c文件修改后,只需要对该.c文件进行重新编译,其他.c文件不会重新编译,然后最后链接一下 更新app即可)

    app:sub.o add.o mult.o div.o main.o
            gcc sub.o add.o mult.o div.o main.o -o app
    sub.o:sub.c
            gcc -c sub.c -o sub.o
    add.o:add.c
            gcc -c add.c -o add.o
    mult.o:mult.c
            gcc -c mult.c -o mult.o
    div.o:div.c
            gcc -c div.c -o div.o
    main.o:main.c
            gcc -c main.c -o main.o
    

工作原理:

  • 命令在执行之前,需要先检查规则中的依赖是否存在

    • 如果存在,执行命令;
    • 如果不存在.向下检查其它的规则,检查有没有一个规则是用来生成这个依赖的,如果找到了,则执行该规则中的命令
  • 检测更新,在执行规则中的命令时,会比较目标和依赖文件的时间

    • 如果依赖的时间比目标的时间晚,需要重新生成目标

    • 如果依赖的时间比目标的时间早,目标不需要更新,对应规则中的命令不需要被执行

      boyangcao@MyLinux:~/Linux/Lesson07$ make
      make: “app”已是最新。
      boyangcao@MyLinux:~/Linux/Lesson07$ vim main.c 
      boyangcao@MyLinux:~/Linux/Lesson07$ make
      gcc -c main.c -o main.o
      gcc sub.o add.o mult.o div.o main.o -o app
      boyangcao@MyLinux:~/Linux/Lesson07$ vim Makefile
      

变量:

  • 自定义变量:变量名=变量值 var=hello $(var)
  • 预定义变量:
    • AR:归档维护程序的名称,默认值为ar
    • CC : c编译器的名称,默认值为cc
    • CXX : C++编译器的名称,默认值为g++
    • $@:目标的完整名称
    • $<:第一个依赖文件的名称
    • $^:所有的依赖文件
  • 获取变量的值: $(变量名)
app:main.c a.c b.c
	gcc -c main.c a.c b.c
#自动变量只能在规则的命令中使用
app:main.c a.c b.c
	$(cc) -c $^ -o $@
	
# define varience
src=sub.o add.o mult.o div.o main.o
target=app
$(target):$(src)
        $(CC) $(src) -o $(target)

模式匹配:

  • %.o:%.c
    • %:通配符,匹配一个字符串
    • 两个%匹配的是同一个字符串
%.o:%.c
	gcc -c $< -o $@

函数:

  • $ ( wildcard PATTERN . . .)

    • 功能:获取指定目录下指定类型的文件列表
    • 参数:PATTERN:某个或多个目录下的对应的某种类型的文件,如果有多个目录,一般使用空格间隔
    • 返回:得到的若干个文件的文件列表,文件名之间使用空格间隔

    示例:

    $(wildcard *.c ./sub/*.c)

    返回值格式:a.c b.c c.c d.c e.c f.c

  • $ (patsubst ,, )

    • 功能:查找 中的单词(单词以"空格"、“Tab"或“回车""换行"分隔)是否符合
      模式,如果匹配的话,则以替换。
    • 可以包括通配符’%‘,表示任意长度的字串。如果中也包含%,那么中的这个 ‘%’ 将是中的那个号所代表的字串。(可以用’\‘来转义,以’%'来表示真实含义的%字符)
    • 返回:函数返回被替换过后的字符串

    示例:

    $ (patsubst %.c, %.o, x.c bar.c)

    返回值格式:x.o bar.o

# define varience
src=$(wildcard ./*.c)
objs=$(patsubst %.c, %.o, $(src))
target=app
$(target):$(objs)
        $(CC) $(objs) -o $(target)
%.o:%.c
        $(CC) -c $< -o $@
clean:
	rm $(objs) -f

注意:

  1. clean不会通过 make命令就自动执行,一般默认只执行第一条规则,其它规则一般都是为第一条规则服务的。所以如果需要执行其他规则需要手动进行执行。
boyangcao@MyLinux:~/Linux/Lesson07$ make clean
rm  ./add.o  ./mult.o  ./main.o  ./div.o  ./sub.o -f
  1. 如果如下执行,会出现这样的情况:
boyangcao@MyLinux:~/Linux/Lesson07$ touch clean
boyangcao@MyLinux:~/Linux/Lesson07$ make clean
make: “clean”已是最新。

这是因为clean: 后面没有任何依赖,那么目标 总是被认为是更新的,所以不会再执行该规则中的命令。

解决办法:

# 设置clean为伪目标
.PHONY:clean
clean:
        rm $(objs) -f

GDB调试

GDB是由GNU软件系统社区提供的调试工具,同GCC配套组成了一套完整的开发环境,GDB是 Linux和许多类Unix系统中的标准开发环境。

GDB 完成四个方面的功能:

  1. 启动程序,可以按照自定义的要求随心所欲的运行程序
  2. 可让被调试的程序在所指定的调置的断点处停住(断点可以是条件表达式)
  3. 当程序被停住时,可以检查此时程序中所发生的事
  4. 可以改变程序,将一个BUG产生的影响修正从而测试其他 BUG

准备工作:

为调试而编译时,我们会()关掉编译器的优化选项(-o),并打开调试选项(-g)。另外, `-wall`在尽量不影响程序行为的情况下选项打开所有warning,也可以发现许多问题,避免一些不必要的 BUG。

gcc -g -Wall program.c -o program

‘-g’ :在可执行文件中加入源代码的信息,比如可执行文件中第几条机器指令对应源代码的第几行,但并不是把整个源文件嵌入到可执行文件中,所以在调试时必须保证gdb能找到源文件。

GDB命令-启动、退出、查看代码

注意:

  • 执行finish的时候,后续执行当前函数的内容中不能有断点。
  • 执行until,后续执行当前循环的内容中不能有断点。
  • 2
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Beyon.sir

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

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

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

打赏作者

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

抵扣说明:

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

余额充值