gcc使用简介

2026 补充:先把构建链路拆开

这节连接 Linux 工具实战专题;旧记录与地址保留。遇到链接和运行时依赖问题,接着读 ELF、符号与动态链接,不要只反复加 -L 或修改环境变量。

对自己写的 demo.c,GCC 驱动程序可以按阶段停止:

gcc -E demo.c -o demo.i
gcc -S -O0 -g demo.c -o demo.s
gcc -c -O0 -g -Wall -Wextra demo.c -o demo.o
gcc demo.o -o demo

依次得到预处理文本、汇编文本、可重定位目标文件、可执行文件。C++ 源码通常用 g++ 驱动最终链接,以带上所需 C++ 运行库;-g 不表示关闭优化,调试符号和 -O0/-Og/-O2 是两个选择。GCC:总体选项

注意旧文“声明缺失也可以继续编译”的说法不能作为通用规则:语言标准、编译器版本以及错误选项会改变诊断级别。先补正确声明和头文件,而不是关闭诊断。

Makefile 描述的是依赖关系

下面是小型多文件项目模板,main.c、helper.c 及头文件需要由工程提供。命令行开头是实际 Tab:

CC := gcc
CPPFLAGS := -Iinclude
CFLAGS := -O2 -g -Wall -Wextra -MMD -MP
OBJECTS := main.o helper.o

app: $(OBJECTS)
$(CC) $(LDFLAGS) $^ $(LDLIBS) -o $@

%.o: %.c
$(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@

-include $(OBJECTS:.o=.d)
.PHONY: all
all: app

$@ 是目标,$< 是第一个依赖,$^ 是去重后的依赖列表。-MMD 让编译器同时生成非系统头文件的依赖,-MP 为头文件补占位目标;包含 .d 后,修改头文件才会使受影响对象重编译。依赖文件放在默认目标之后,避免意外改变默认构建对象。GNU make:自动生成依赖、GCC:预处理选项

make -n 可查看计划执行的命令,但不是无条件无副作用的安全沙箱:Makefile 解析期的 shell 展开或递归 make 有特殊规则。make -j2 只表示允许并行;漏写依赖可能让并行构建随机失败,不能用永远单线程掩盖问题。

失败发生在哪一层

现象 先查什么 下一步工具
找不到头文件 include 路径、实际编译命令、生成头文件依赖 编译器诊断与构建日志
undefined reference 函数签名、对象是否参与链接、静态库顺序 nm -C、完整链接命令
运行时找不到 .so ELF 动态依赖、RUNPATH、部署布局 readelf -d、可信文件上的 ldd
运行时 undefined symbol 实际加载库版本、动态导出符号、ABI nm -D -C、readelf --dyn-syms
源码行和指令难以对应 优化、内联、调试信息、二进制是否匹配 objdump -d -S、readelf

源码、编译命令、库版本、最终 ELF 必须一起留证。只保存一个“编译成功”的截图,无法解释换电脑后为什么运行失败。下面为原有学习记录;GDB 的系统教程仍使用原专篇。

前 言

编译 是指把一个是纯文本的源代码的“程序”. 比如 C 或 C++这种编程语言, 转变成机器码, 即用于控制计算机的中央处理单元(CPU)的 1 和 0 的序列。这种机器码被存放在称为可执行文件的文件中, 有时候也被称为二进制文件。

我们假设源代码被存放在“test.c”的文件中。用 gcc 编译“test.c”文件, 可以用下面的命令:

test.c  或者 gcc -Wall test.c -o test

span id=“inline-red”> 说明  :“-o”选项可以指定存储机器码的输出文件, 该选项通常是命令行上的最后一个参数。如果省略它, 输出将被写到默认文件“a.out”中;命令行选项“-c”用于把源码文件编译成对象文件。 “-Wall” 选项打开所有最常用到的编译警告。也是可以在 C 编程中发现最常见的错误。 #include “FILE.h”和“#include <FILE.h>”这两种 include 声明形式的含义是有差异的, 前者是先在当前目录搜索“FILE.h”, 然后再查看包含系统头文件的目录。#include <FILE.h>这种 include 声明是搜索系统目录的头文件, 默认情况下不会在当前目录下查找头文件。

当程序被存储在一个个单独的源文件中时, 只有那些修改过源码的文件才需要重新编译。用这种方法, 源文件被分开一个个编译, 然后再链接在一起。 此过程分为两个步骤。在第一阶段, 文件被编译但不生成可执行文件。生成的结果被称为对象文件(obj 文件), 用 GCC 时有“.o”后缀名。在第二阶段, 各个对象文件由一个被称为链接器的单独的程序合成在一起。链接器把所有的对象文件组合在一起生成单个的可执行文件。对象文件包含的是机器码, 其中任何对在其他文件中的函数(或变量)的内存地址的引用都留着没有被解析。这样就允许在互相之间不直接引用的情况下编译各个源代码文件。链接器在生成可执行文件时会填写这些还缺少的地址。

make file 的使用

Makefile  简单的来说是自动化编译的脚本, 在一个大的工程文件中, 各个文件分别放在不同的文件夹中, 一个个的编译起来是十分费事的, 使用这个这个自动化脚本的好处就是只需要一个make命令, 整个工程完全自动编译, 极大的提高了软件开发的效率。而make是一个解释makefile中指令的命令工具, 这个在b站中可以详细的了解到, 不过作者在视频最后有故意贬低vim的意思, 他说的功能, 完全是可以自定义的呀, 所以。。。。没必要, vim还是非常非常优秀的编辑器, 也是个人最喜欢的编辑器(没有之一)。下面就摘抄如下博客的内容, 因为自己的理解太浅, 写起来错误会很多。

源文件首先会生成中间目标文件, 再由中间目标文件生成执行文件。在编译时, 编译器只检测程序语法, 和函数、变量是否被声明。如果函数未被声明, 编译器会给出一个警告, 但可以生成Object File。而在链接程序时, 链接器会在所有的Object File中找寻函数的实现, 如果找不到, 那到就会报链接错误码(Linker Error)。

对于大型工程, 就要用到cmake了(针对c/c++)

gdb调试 (调试过程极度不爽)

该内容搬运与此

运行

对C/C++程序的调试, 需要在编译前就加上-g选项:

需要说明的如下:

1, run:简记为 r , 其作用是运行程序, 当遇到断点后, 程序会在断点处停止运行, 等待用户输入下一步的命令。

2, continue (简写c ):继续执行, 到下一个断点处(或运行结束)

3, next:(简写 n), 单步跟踪程序, 当遇到函数调用时, 也不进入此函数体;此命令同 step 的主要区别是, step 遇到用户自定义的函数, 将步进到函数中去运行, 而 next 则直接调用函数, 不会进入到函数体内。

4, step (简写s):单步调试如果有函数调用, 则进入函数;与命令n不同, n是不进入调用的函数的

5, until:当你厌倦了在一个循环体内单步跟踪时, 这个命令可以运行程序直到退出循环体。

6, until+行号: 运行至某行, 不仅仅用来跳出循环

7, finish: 运行程序, 直到当前函数完成返回, 并打印函数返回时的堆栈地址和返回值及参数值等信息。

8, call 函数(参数):调用程序中可见的函数, 并传递“参数”, 如:call gdb_test(55)

9, quit:简记为 q , 退出gdb

设置断点

1, break n (简写b n):在第n行处设置断点

2, delete 断点号n:删除第n个断点

3, disable 断点号n:暂停第n个断点

4, enable 断点号n:开启第n个断点

5, clear 行号n:清除第n行的断点

6, info b (info breakpoints) :显示当前程序的断点设置情况

7, delete breakpoints:清除所有断点:

查看源代码

1, list :简记为 l , 其作用就是列出程序的源代码, 默认每次显示10行。

2, list 行号:将显示当前文件以“行号”为中心的前后10行代码, 如:list 12

3, list 函数名:将显示“函数名”所在函数的源代码, 如:list main

4, list :不带参数, 将接着上一次 list 命令的, 输出下边的内容。

打印表达式

1, print 表达式:简记为 p , 其中“表达式”可以是任何当前正在被测试程序的有效表达式, 比如当前正在调试C语言的程序, 那么“表达式”可以是任何C语言的有效表达式, 包括数字, 变量甚至是函数调用。

2, print a:将显示整数 a 的值

3, print ++a:将把 a 中的值加1, 并显示出来

4, print name:将显示字符串 name 的值

5, print gdb_test(22):将以整数22作为参数调用 gdb_test() 函数

6, print gdb_test(a):将以变量 a 作为参数调用 gdb_test() 函数

7, display 表达式:在单步运行时将非常有用, 使用display命令设置一个表达式后, 它将在每次单步进行指令后, 紧接着输出被设置的表达式及值。如: display a

8, watch 表达式:设置一个监视点, 一旦被监视的“表达式”的值改变, gdb将强行终止正在被调试的程序。如: watch a

9, whatis :查询变量或函数

10, info function: 查询函数

11, 扩展info locals: 显示当前堆栈页的所有变量

查询运行信息

1, where/bt :当前运行的堆栈列表;

2, bt backtrace 显示当前调用堆栈

3, up/down 改变堆栈显示的深度

4, set args 参数:指定运行时的参数

5, show args:查看设置好的参数

6, info program: 来查看程序的是否在运行, 进程号, 被暂停的原因。

分割窗口

1, layout:用于分割窗口, 可以一边查看代码, 一边测试:

2, layout src:显示源代码窗口

3, layout asm:显示反汇编窗口

4, layout regs:显示源代码/反汇编和CPU寄存器窗口

5, layout split:显示源代码和反汇编窗口

6, Ctrl + L:刷新窗口

GNU make 的编写

把之前写的内容给删掉了, 换成chatGPT给我的答案会更容易理解一些

Makefile是一种用于自动化构建和编译软件的工具。以下是编写一个简单Makefile的步骤:

1, 决定需要编译的源文件和生成的目标文件。比如, 你可能有一个源文件main.c和一个库文件lib.c, 需要编译成目标文件main.o和lib.o。

2, 确定编译器和编译选项。比如, 你可能要使用GCC编译器, 并且需要加上-Wall和-O2选项。

3, 定义目标和依赖关系。例如, 你的目标可能是生成可执行文件app, 它依赖于main.o和lib.o。你可以在Makefile中使用规则来描述这些依赖关系:

vbnetCopy code
app: main.o lib.o
gcc -o app main.o lib.o

4, 对于每个源文件, 定义其对应的编译规则。例如, 你可能需要为main.c和lib.c编写如下规则:

lessCopy code
main.o: main.c
gcc -c main.c

lib.o: lib.c
gcc -c lib.c

其中, -c选项表示编译成目标文件而不是可执行文件。

5, 最后, 你可以定义一些伪目标(.PHONY), 以便在命令行中执行。例如, 你可以定义一个名为clean的伪目标, 用于删除生成的目标文件:

makefileCopy code
.PHONY: clean
clean:
rm -f *.o app

完整的Makefile代码如下:

makefileCopy code
CC = gcc
CFLAGS = -Wall -O2

app: main.o lib.o
$(CC) -o app main.o lib.o

main.o: main.c
$(CC) $(CFLAGS) -c main.c

lib.o: lib.c
$(CC) $(CFLAGS) -c lib.c

.PHONY: clean
clean:
rm -f *.o app

要执行Makefile, 请在命令行中切换到Makefile所在目录, 并运行make命令。它将自动构建和编译你的软件, 并根据需要更新目标文件。

知识共享许可协议