当前位置: 首页 > news >正文

嵌入式Linux——gcc和Makefile

1.gcc编译过程

1.预处理

  • 所有的 #include 被替换为对应的头文件内容。

  • 所有的宏(#define)被展开。

  • 条件编译(如 #ifdef)被处理。

  • 注释被删除。

2.编译

将预处理后的源代码转换为汇编代码,生成一个汇编代码文件(.s 文件)
 

3.汇编

将汇编代码转换为机器代码(二进制文件),生成一个目标文件(.o 文件)

4. 链接

将多个目标文件和库文件合并,生成最终的可执行文件:

  • 所有的目标文件被合并。

  • 符号引用(如 printf 函数)被解析并链接到标准库或其他库。

  • 生成的文件可以直接运行。

     

2.Makefile的使用

2.1 规则

一个简单的 Makefile 文件包含一系列的“规则”

命令:依赖

        (Tab键)命令

命令被执行的 2 个条件:依赖文件比目标文件新,或是 目标文件还没生成


make命令执行Makefile:

  1. 第一次make:全部规则的命令都会执行
  2. 修改其中一个文件后再make:根据第一条规则的依赖往下一个一个找,若下面的依赖比目标更新(.c文件比.o文件新),则执行该规则的命令
main: main.o input.o calcu.o     #根据这些依赖一个一个往下找、对比
    gcc -o main main.o input.o calcu.o 

main.o: main.c 
    gcc -c main.c 

input.o: input.c 
    gcc -c input.c 

calcu.o: calcu.c 
    gcc -c calcu.c 

clean: 
    rm *.o     
    rm main

2.2 语法

2.2.1 通配符

依赖文件太多怎么办?

使用通配符

test: a.o b.o 
    gcc -o test $^
 
%.o : %.c
    gcc -c -o $@ $<

%.o:表示所有的.o文件
%.c:表示所有的.c文件
$@:表示目标
$<:表示第1个依赖文件
$^:表示所有依赖文件

这样就只需要一条规则就OK了

第一条指令在搜寻a.o的时候就会把a带入到“%”中,以此类推,依次执行

2.2.2  假想目标: .PHONY

test: a.o b.o 
    gcc -o test $^
 
%.o : %.c
    gcc -c -o $@ $<

clean:
    rm *.o test

想清除文件

*1)执行 make :生成第一个可执行文件。
*2)执行 make clean : 清除所有文件,即执行: rm *.o test。  

执行:make [目标] 也可以不跟目标名,若无目标默认第一个目标。我们直接执行make的时候,会在 makefile里面找到第一个目标然后执行下面的指令生成第一个目标。当我们执行 make clean 的时候, 就会在 Makefile 里面找到 clean 这个目标,然后执行里面的命令。

这个写法有些问题,原因是我们的 目录里面没有 clean 这个文件,这个规则执行的条件成立,他就会执行下面的命令来删除文件。

如果:该目录下面有名为clean文件怎么办呢?

这时候make clean不会起反应,因为一个规则能够执行的条件
*1)目标文件不存在
*2)依赖文件比目标新

现在我们的目录里面有名为“clean”的文件,目标文件是有的,并且没有依赖文件,没有办法判断依赖文件的时间。

解决办法:我们需要把目标定义为假象目标,用关键字PHONY

.PHONY: clean #把clean定义为假象目标。他就不会判断名为“clean”的文件是否存在,

在Makfile结尾添加.PHONY: clean语句,重新执行:make clean,就会执行删除操作。 

2.2.3 变量

1), 简单变量(即使变量):
A := xxx # A的值即刻确定,在定义时即确定
对于即使变量使用 “:=” 表示,它的值在定义的时候已经被确定了

2)延时变量
B = xxx # B的值使用到时才确定 对于延时变量使用“=”表示。
它只有在使用到的时候才确定,在定义/等于时并没有 确定下来。

:=   :   即时变量

  = : 延时变量

?= : 延时变量, 如果是第1次定义才起效, 如果在前面该变量已定义则忽略这句

+= : 附加, 它是即时变量还是延时变量取决于前面的定义

?= : 如果这个变量在前面已经被定义了,这句话就会不会起效果,

示例

A := $(C)
B = $(C)
C = abc
#D = 100ask
D ?= weidongshan

all:
    @echo A = $(A) //@的作用是make时候不显示该过程
    @echo B = $(B)
    @echo D = $(D)

C += 123

执行结果:
A =
B = abc 123
D = weidongshan

解释:

1) A := $(C):
A为即时变量,在定义时即确定,由于刚开始C的值为空,所以A的值也为空。

2) B = $(C):
B为延时变量,只有使用到时它的值才确定,当执行make时,会解析Makefile里面的所用变量,所以先 解析C= abc,然后解析C += 123,此时,C = abc 123,当执行:\@echo B = $(B) B的值为 abc 123。

3) D ?= weidongshan:
D变量在前面没有定义,所以D的值为weidongshan,如果在前面添加D = 100ask,最后D的值为 100ask。


2.3 处理头文件(依赖文件)

一般的Makefile中,不会包含头文件的更新,那应该怎么处理

可以像下面这段代码一样:

c.o: c.c c.h

把 c.h 加入到目标 c.o 的依赖中,这样每次检查c.o是就会判断c.h是否更新

但是当头文件一多起来,不可能每个头文件一个一个写进去

这时候就应该换一种方法,如下:

gcc -M c.c # 打印出依赖
gcc -M -MF c.d c.c # 把依赖写入文件c.d
gcc -c -o c.o c.c -MD -MF c.d # 编译c.o, 把依赖写入文件c.d
objs = a.o b.o c.o

# 生成依赖文件的列表。通过 patsubst 函数将 objs 中的每个目标文件(如 a.o)转换为对应的依赖文件(如 .a.o.d)
dep_files := $(patsubst %,.%.d, $(objs))


# 使用 wildcard 函数过滤出实际存在的依赖文件。如果某个 .d 文件不存在,wildcard 会将其从 dep_files 中移除
dep_files := $(wildcard $(dep_files))


# 定义默认目标 test,它依赖于 objs 中列出的所有目标文件(a.o, b.o, c.o)
# 当运行 make 时,会首先构建这些目标文件,然后使用 gcc 将它们链接成可执行文件 test
test: $(objs)
	gcc -o test $^  # $^ 表示所有依赖文件(即 a.o b.o c.o)


# 如果 dep_files 不为空(即存在依赖文件),则包含这些依赖文件
# 依赖文件通常由 gcc 的 -MD -MF 选项生成,用于自动处理头文件依赖
ifneq ($(dep_files),)
include $(dep_files)
endif


# 定义如何从 .c 文件生成 .o 文件的规则
# $@ 表示目标文件(如 a.o),$< 表示第一个依赖文件(如 a.c)
# -c 选项表示只编译不链接,-o 指定输出文件名
# -MD 选项生成依赖文件,-MF 指定依赖文件的名称(如 .a.o.d)
%.o : %.c
	gcc -c -o $@ $< -MD -MF .$@.d


# 定义 clean 目标,用于删除所有 .o 文件和可执行文件 test
clean:
	rm *.o test


# 定义 distclean 目标,用于删除所有依赖文件(如 .a.o.d, .b.o.d, .c.o.d)
distclean:
	rm $(dep_files)


.PHONY: clean

 看注释应该能看得懂

虽然 %.o : %.c 规则中只显式指定了 .c 文件作为依赖,但通过 gcc 的 -MD -MF 选项,Makefile 能够自动检测 .h 文件的更新并重新编译。

CFLAGS:编译参数

示例

CFLAGS = -Werror -Iinclude

通过CFLAGS配置了两个作用:

  • -Werror将所有警告视为错误,确保代码质量。

  • -Iinclude将 include 目录添加到头文件搜索路径中。

 CFLAGS 可以帮助更好地控制编译过程,确保代码的正确性和可维护性

2.4 解析Makefile

*通用Makefile设计思想:
1.在顶层和子目录Makefile文件中确定要编译的文件、目录,比如:

顶层Makefile:文件、目录
子目录Makefile:文件

obj-y += main.o
obj-y += a/

Makefile 文件总是被 Makefile.build 包含的。 

2.在 Makefile.build 中设置编译规则,有 3 条编译规则

i. 怎么编译子目录? 进入子目录编译:

$(subdir-y):
	make -C $@ -f $(TOPDIR)/Makefile.build

ii. 怎么编译当前目录中的文件?

%.o : %.c
	$(CC) $(CFLAGS) $(EXTRA_CFLAGS) $(CFLAGS_$@) -Wp,-MD,$(dep_file) -c -o $@ $<

iii. 当前目录下的.o和子目录下的built-in.o要打包起来到顶层目录

built-in.o : $(cur_objs) $(subdir_objs)
	$(LD) -r -o $@ $^

 3. 顶层Makefile中把顶层目录的built-in.o链接成APP

$(TARGET) : built-in.o
	$(CC) $(LDFLAGS) -o $(TARGET) built-in.o

    一个通用的Makefile:

    1. 顶层目录的Makefile
    2. 顶层目录的Makefile.build
    3. 各级子目录的Makefile

    源码分析(看注释):

    Makefile:

    CROSS_COMPILE = # 交叉编译工具头,如:arm-linux-gnueabihf-
    AS      = $(CROSS_COMPILE)as # 把汇编文件生成目标文件
    LD      = $(CROSS_COMPILE)ld # 链接器,为前面生成的目标代码分配地址空间,将多个目标文件链接成一个库或者一个可执行文件
    CC      = $(CROSS_COMPILE)gcc # 编译器,对 C 源文件进行编译处理,生成汇编文件
    CPP    = $(CC) -E
    AR      = $(CROSS_COMPILE)ar # 打包器,用于库操作,可以通过该工具从一个库中删除或则增加目标代码模块
    NM     = $(CROSS_COMPILE)nm # 查看静态库文件中的符号表
    
    STRIP       = $(CROSS_COMPILE)strip # 以最终生成的可执行文件或者库文件作为输入,然后消除掉其中的源码
    OBJCOPY  = $(CROSS_COMPILE)objcopy # 复制一个目标文件的内容到另一个文件中,可用于不同源文件之间的格式转换
    OBJDUMP = $(CROSS_COMPILE)objdump # 查看静态库或则动态库的签名方法
    
    # 共享到sub-Makefile
    export AS LD CC CPP AR NM
    export STRIP OBJCOPY OBJDUMP
    
    # -Wall : 允许发出 GCC 提供的所有有用的报警信息
    # -O2 : “-On”优化等级
    # -g : 在可执行程序中包含标准调试信息
    # -I : 指定头文件路径(可多个)
    CFLAGS := -Wall -O2 -g 
    CFLAGS += -I $(shell pwd)/include
    
    # LDFLAGS是告诉链接器从哪里寻找库文件,这在本Makefile是链接最后应用程序时的链接选项。
    LDFLAGS := 
    
    # 共享到sub-Makefile
    export CFLAGS LDFLAGS
    
    # 顶层路径
    TOPDIR := $(shell pwd)
    export TOPDIR
    
    # 最终目标
    TARGET := test
    
    # 本次整个编译需要源 文件 和 目录
    # 这里的“obj-y”是自己定义的一个格式,和“STRIP”这些一样,*但是 一般内核会搜集 ”obj-”的变量*
    obj-y += main.o # 需要把当前目录下的 main.c 编进程序里
    obj-y += sub.o # 需要把当前目录下的 sub.c 编进程序里
    obj-y += subdir/ # 需要进入 subdir 这个子目录去寻找文件来编进程序里,具体是哪些文件,由 subdir 目录下的 Makefile 决定。
    #obj-y += $(patsubst %.c,%.o,$(shell ls *.c))
    
    # 第一个目标
    all : start_recursive_build $(TARGET)
        @echo $(TARGET) has been built !
        
    # 处理第一个依赖,**转到 Makefile.build 执行**
    start_recursive_build:
        make -C ./ -f $(TOPDIR)/Makefile.build
        
    # 处理最终目标,把前期处理得出的 built-in.o 用上
    $(TARGET) : built-in.o
        $(CC) -o $(TARGET) built-in.o $(LDFLAGS)
        
    # 清理
    clean:
        rm -f $(shell find -name "*.o")
        rm -f $(TARGET)
        
    # 彻底清理
    distclean:
        rm -f $(shell find -name "*.o")
        rm -f $(shell find -name "*.d")
        rm -f $(TARGET)
    Makefile.build

    注意,include 命令,相对路径为 执行本 Makefile.build 的路径

    # 伪目标
    PHONY := __build
    __build:
    
    # 清空需要的变量
    obj-y :=
    subdir-y :=
    EXTRA_CFLAGS :=
    
    # 包含同级目录Makefile
    # 这里要注意,相对路径为 执行本 Makefile.build 的路径
    include Makefile
    
    # 获取当前 Makefile 需要编译的子目录的目录名
    # obj-y := a.o b.o c/ d/
    # $(filter %/, $(obj-y))   : c/ d/
    # __subdir-y  : c d
    # subdir-y    : c d
    __subdir-y	:= $(patsubst %/,%,$(filter %/, $(obj-y)))
    subdir-y	+= $(__subdir-y)
    
    # 把子目录的目标定为以下注释
    # built-in.o d/built-in.o
    subdir_objs := $(foreach f,$(subdir-y),$(f)/built-in.o)
    
    # 获取当前目录需要编进程序的文件名作为,并写为目标
    # a.o b.o
    cur_objs := $(filter-out %/, $(obj-y))
    # 使修改头文件 .h 后,重新make后可以重新编译(重要)
    dep_files := $(foreach f,$(cur_objs),.$(f).d)
    # 列出存在的文件
    dep_files := $(wildcard $(dep_files))
    
    ifneq ($(dep_files),)
      include $(dep_files)
    endif
    
    
    PHONY += $(subdir-y)
    
    # 第一个目标
    __build : $(subdir-y) built-in.o
    
    # 优先编译 子目录的内容
    $(subdir-y):
    	make -C $@ -f $(TOPDIR)/Makefile.build
    
    # 链接成 目标
    built-in.o : $(cur_objs) $(subdir_objs)
    	$(LD) -r -o $@ $^
    
    dep_file = .$@.d
    
    # 生成 cur_objs 目标
    %.o : %.c
    	$(CC) $(CFLAGS) $(EXTRA_CFLAGS) $(CFLAGS_$@) -Wp,-MD,$(dep_file) -c -o $@ $<
    	
    .PHONY : $(PHONY)

    形象化: 

    流程:

    1. 各级子目录的Makefile

    参考

    # 可以不添加下面两个变量
    EXTRA_CFLAGS  := 
    CFLAGS_test.o := 
    
    obj-y += test.o
    obj-y += subdir/
    • obj-y += file.o 表示把当前目录下的file.c编进程序里,
    • obj-y += subdir/ 表示要进入subdir这个子目录下去寻找文件来编进程序里,是哪些文件由subdir目录下的Makefile决定。
    • EXTRA_CFLAGS, 它给当前目录下的所有文件(不含其下的子目录)设置额外的编译选项, 可以不设置
    • CFLAGS_xxx.o, 它给当前目录下的xxx.c设置它自己的编译选项, 可以不设置
    注意
    1. "subdir/"中的斜杠"/"不可省略
    2. 顶层Makefile中的CFLAGS在编译任意一个.c文件时都会使用
    3. CFLAGS EXTRA_CFLAGS CFLAGS_xxx.o 三者组成xxx.c的编译选项

     
    2.顶层目录的Makefile
    主要作用:

    1. 整个工程的参数初期定义:架构、工具链、编译工具、编译参数、需要导出的变量等等
    2. 定义最终目标
    3. 跳转到 Makefile.build

    3.顶层目录的Makefile.build **

    注意:该文件虽然放在顶层,但是也是提供给各级子目录使用的。
    主要功能:

    • 把某个目录及它的所有子目录中、需要编进程序去的文件都编译出来,把各个subdir/built-in.o和当前目录的目标 .o 合并打包为当前目录的built-in.o 。
    使用提示
    • 执行"make"来编译,执行 make clean 来清除,执行 make distclean 来彻底清除。

    相关文章:

  1. C++ list类
  2. 强化学习(赵世钰版)-学习笔记(8.值函数方法)
  3. 定义模型生成数据表
  4. Linux top 命令详解:从入门到高级用法
  5. WebRTC、WebSocket、EasyRTC嵌入式音视频SDK:技术差异与应用场景详细对比
  6. C++刷题(三):string
  7. c++--vector
  8. leecode417.太平洋大西洋水流问题
  9. PyQt5 - Groove 启用高 DPI 支持,使得应用程序能够自动适应不同的屏幕分辨率
  10. numpy学习笔记6:np.sin(a) 的详细解释
  11. 《声音的未来:语音识别文献解读》专栏介绍及其文章解读目录
  12. 微学习:提高企业培训效率的创新方式
  13. 挖矿------获取以太坊测试币
  14. 基于大模型的慢性鼻窦炎全周期预测与治疗方案研究报告
  15. 云钥科技工业相机定制服务,助力企业实现智能智造
  16. PCL 高斯函数拟合(正太分布)
  17. 《TCP/IP网络编程》学习笔记 | Chapter 19:Windows 平台下线程的使用
  18. 【Java项目】基于JSP的智能停车场管理系统
  19. 蓝桥杯刷题 Day2 AC自动机(二次加强版)
  20. linux 命令 vim
  21. 获派驻6年后,中国驻厄瓜多尔大使陈国友即将离任
  22. 国际奥委会举办研讨会,聚焦如何杜绝操纵比赛
  23. 端午假期购票日历发布,今日可购买5月29日火车票
  24. 2025财政观察|长三角“三公”经费普降,钱要用在刀刃上
  25. AI含量非常高,2025上海教育博览会将于本周五开幕
  26. SIFF动画单元公布首批片单:《燃比娃》《凡尔赛玫瑰》等