Makefile学习记录

最近又把之前的makefile重新看了一遍,在日常还是用的很广泛的,因此记录一下,方便以后查阅

1、编译过程

我觉得要学习Makefile就必须仔细了解编译过程,不清楚这个编译过程就不知道makefile在说什么东西,特别是对于习惯了从win平台用ide来写代码的人来说,另外就是需要了解一点点的shell知识。

关于编译过程,我总结如下图所示

主要是下面的四个阶段构成,可以把这四个过程使用gcc展示出来,可以更深刻的认识一下

image

使用下面的代码进行测试

#include <stdio.h>
int add(int a, int b);
int minus(int a, int b);
int main()
{
    int a = 30;
    int b = 5;
    int c;
    c = add(a, b);
    printf("a+b = %d\n", c);
    c = minus(a, b);
    printf("a-b = %d\n", c);
    return 0;
}
int add(int a, int b)
{
    return a+b;
}
int minus(int a, int b)
{
    return a-b;
}

可以线使用预处理看一下

gcc -E main.c

image
加上 -o指定输出文件 gcc -E main.c -o main.i 可以看到就是吧stdio.h加进来的样子(实际情况一般不需要看预处理的文件!)
image
查看汇编程序:gcc -S main.c -o main.s 生成汇编程序,不过实际情况也一般不需要,这里可以看到汇编还是文本,是可以直接打开的
image
下面看第三阶段,生成机器指令gcc -c main.c -o main.o 这个时候已经是二进制指令了,无法打开
image
当然我们也可以直接输出可执行文件gcc main.c -o main ,这个输出的main就直接是可执行文件了,可以尝试执行一下,结果如下所示!
image

回顾一下上面的过程

image
这里需要注意这四条语句不是相互独立的,比如直接说最后一条语句,生成可执行文件,是经过了生成.i .s .o的这几个过程的,只是他的文件没有输出出来罢了!

下面将上面的程序做一点小改动,将add 和 minus函数单独放到一个源文件中去,那我们就需要同时编译多个文件

gcc main.c add.c minus.c -o main

运行结果如下,可以看到是正常的
image

2、编译静态库并使用

编译静态库的规则如下所示

  • 编译成 .o 的文件
    gcc -c [.c] -o [自定义文件名] 
    gcc -c [.c] [.c] ...
    
  • 编静态库
    ar -r [lib自定义库名.a] [.o] [.o] ...
    
  • 链接成可执行文件
    gcc [.c] [.a] -o [自定义输出文件名]
    gcc [.c] -o [自定义输出文件名] -l[库名] -L[库所在路径]
    

下面将上面程序依赖的两个文件加入编成静态库,如下所示
image

3、编译动态库并使用

编译静态库的规则如下所示

  • 编译二进制.o文件
gcc -c -fpic [.c/.cpp][.c/.cpp]... 
  • 编库
gcc -shared [.o][.o]... -o [lib自定义库名.so]
  • 链接库到可执行文件
gcc [.c/.cpp] -o [自定义可执行文件名]  -l[库名] -L[库路径] -Wl,-rpath=[库路径]

但是这里要注意,如果按照跟我一样的下面方式进行操作
image

这样会直接提示找不到动态库,因为系统寻找动态库是需要指定一下路径的,不然只会在默认的路径下去找,因此需要首先

sudo vim .bashrc

在末尾加上这个路径
image

之后source一下

source .bashrc

这样之后再次运行就不会报错了
image

4、makefile实现

makefile的基本格式

targets : prerequisties
[tab键]command

用中文翻译一下就是:

目标: 生成目标需要的文件或者是目标(目标套娃)
[tab键]执行的命令

伪目标,伪目标的格式如下

.PHONY : clean

为什么要有伪目标这个概念,意思就是不管有没有clean这个文件,当运行make clean的时候就是运行clean的命令,不然的话,还是用上面的程序进行实验:

#include <stdio.h>
int add(int a, int b);
int minus(int a, int b);
int main()
{
    int a = 30;
    int b = 5;
    int c;
    c = add(a, b);
    printf("a+b = %d\n", c);
    c = minus(a, b);
    printf("a-b = %d\n", c);
    return 0;
}
int add(int a, int b)
{
    return a+b;
}
int minus(int a, int b)
{
    return a-b;
}

这里要注意:当只有一个文件的时候可以不编写makefile,直接make这个文件就是用gcc编译了
image

这样如果我们不使用伪目标,当有一个clean.c的文件或者clean的可执行文件,就回去执行clean这个文件而不是我们准备好的makefile命令

1、makefile变量

1、定义变量

直接用=的形式就可以,当然这个=有几种变化的形式,比如下面使用变量表示源文件和.o文件的位置

src := main.c
obj := objs/main.o
bin := main

几种 = 的介绍

:=

  • 立即赋值运算符
  • 用于在定义变量时立即求值
  • 该值在定义后不再更改
  • 即使在后面的语句中重新定义了该变量

?=

  • 默认赋值运算符
  • 如果该变量已经定义,则不进行任何操作
  • 如果该变量尚未定义,则求值并分配

+=

  • 累加

2、使用变量

使用(){}的方式来实现

$(bin) : $(obj)
    gcc $(obj) -o $(bin)
$(obj) : $(src)
    gcc -c $(src) -o $(obj)
.PHONY : clean
clean:
    rm -rf *.o main

执行如下所示:
image

3、一些预定义变量,可以用预定义变量来提高效率

  • $@: 目标(target)的完整名称
  • $<: 第一个依赖文件的名称
  • $^: 所有的依赖文件,以空格分开,不包含重复的依赖文件

修改makefile如下所示:

src := main.c
obj := objs/main.o
bin := main
# $(bin) : $(obj)
#   gcc $(obj) -o $(bin)
# $(obj) : $(src)
#   gcc -c $(src) -o $(obj)
#使用自动化变量实现
$(bin) : $(obj)
    gcc $^ -o $(bin)
$(obj) : $(src)
    gcc -c $^ -o $@
.PHONY : clean
clean:
    rm -rf *.o main

这里在介绍一下通配符*%这两个都是表示匹配到任意字符串,*常用于目录名或者文件名,比如在make clean中经常用到 %可以将匹配到的字符串作为变量使用

修改makefile如下所示:

src := main.c
obj := main.o add.o minus.o
bin := main
# $(bin) : $(obj)
#   gcc $(obj) -o $(bin)
# $(obj) : $(src)
#   gcc -c $(src) -o $(obj)
#使用自动化变量实现
# $(bin) : $(obj)
#   gcc $^ -o $(bin)
# $(obj) : $(src)
#   gcc -c $^ -o $@
#使用通配符
$(bin) : $(obj)
    gcc $^ -o $(bin)
%.o : %.c
    gcc -c $< -o $@
.PHONY : clean
clean:
    rm -rf *.o main

使用通配符之后效果如下,就不用我们一个个去写对应的.c.o的转换了
image

运行结果如下
image

2、shell语法在makefile中使用

shell在makefile中的使用如下所示

$(shell <command> <arguments>)

可以在makefile里面加上一句这个:
image

之后输出如下:
image

这里第一行输出了原本的命令,makefile会把命令输出,因此可以修改为不输出
image

3、makefile中的一些函数

makefile中的函数用法如下所示

$(subst <from>,<to>,<text>)

1、使用subst函数

对上面的makefile再次进行修改

src := $(shell find . -name "*.c")
obj := $(subst .c,.o, $(src)) 
bin := main
#使用通配符
$(bin) : $(obj)
    gcc $^ -o $(bin)
%.o : %.c
    gcc -c $< -o $@
#shell输出
HOST_ARCH := $(shell uname -m)
echo:
    @echo $(HOST_ARCH)
    @echo $(obj)

.PHONY : clean
clean:
    rm -rf *.o main

修改说明如下
image

2、使用patsubst函数

再次修改mafefile如下所示

# src := $(shell find . -name "*.c")
# obj := $(subst .c,.o, $(src)) 
#使用patsubst
src := $(shell find . -name "*.c")
obj := $(patsubst %.c,%.o, $(src)) 
bin := main
#使用通配符
$(bin) : $(obj)
    gcc $^ -o $(bin)
%.o : %.c
    gcc -c $< -o $@
#shell输出
HOST_ARCH := $(shell uname -m)
echo:
    @echo $(HOST_ARCH)
    @echo $(obj)

.PHONY : clean
clean:
    rm -rf *.o main

结果和上面的是差不多的

3、使用foreach函数

函数用法如下,和shell脚本的for循环很类似

$(foreach <var>,<list>,<text>)

为了测试这个韩式,新建一个文件夹
image

foreach测试程序如下所示

src := $(shell find . -name "*.c")
obj := $(patsubst %.c,%.o, $(src)) 
bin := main

#使用通配符
$(bin) : $(obj)
    gcc $^ -o $(bin)
%.o : %.c
    gcc -c $< -o $@
#shell输出
HOST_ARCH := $(shell uname -m)
echo:
    @echo $(HOST_ARCH)
    @echo $(obj)
foreach_test:
    @echo $(foreach item,$(shell ls ./test_dir),$(item))
.PHONY : clean
clean:
    rm -rf *.o main

输出如下
image

4、makefile中的条件语句

下面是一个简单的条件语句规则

if_test:
ifeq ($(CC), gcc)
    @echo 1111
else
    @echo 1111
endif

作用是判断两个是否相等,效果如下
image

相类似的条件语句还有(用来判断宏定义的)
ifneq / else / endif
ifdef / else / endif

5、编译选项

下面是一些常见的编译选项和说明

  • -m64: 指定编译为 64 位应用程序
  • -std=: 指定编译标准,例如:-std=c++11、-std=c++14
  • -g: 包含调试信息
  • -w: 不显示警告
  • -O: 优化等级,通常使用:-O3
  • -I: 加在头文件路径前
  • fPIC: (Position-Independent Code), 产生的没有绝对地址,全部使用相对地址,代码可以被加载到内存的任意位置,且可以正确的执行。这正是共享库所要求的,共享库被加载时,在内存的位置不是固定的

链接选项

  • -l: 加在库名前面
  • -L: 加在库路径前面
  • -Wl,<选项>: 将逗号分隔的 <选项> 传递给链接器
  • -rpath=: "运行" 的时候,去找的目录。运行的时候,要找 .so 文件,会从这个选项里指定的地方去找

6、常见编译错误

下面是两个常见的编译报错说明

*** missing separator. Stop.

  • 原因: Makefile 语法出错
  • 解决方法: 根据报错的行数,检查 tab 缩进,空格问题

*** commands commence before first target. Stop

  • 原因: if等语句里面用了 tab 缩进
  • 解决方法: 缩进的地方全部改为空格

5、内核态makefile

makefile如下所示:

obj-m += my_module.o
my_module-y := main.o add.o minus.o

all:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

可以直接make之后insmod查看结果

posted @   LX2020  阅读(39)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?
点击右上角即可分享
微信分享提示