makefile简单初探索

makefile简单初探索

系统 ubuntu20.04
参考正点原子

只是学习中自己的小记


0.引子

我们常常会遇到有c编译的需求,在linux环境下一般使用gcc编译,如

gcc main.c -o main

如果还有引用别的文件,则

gcc main.c file1.c file2.c -o main

这在仅有一个文件或少量文件时,是非常方便使用的。


但是这里有两个问题
– 文件不可能一直这么少,随着工程的增大,文件只会越来越多

这个时候你总不能还在gcc后面一直加文件吧
– 当文件多起来后,编译也很费时,总不能每次都重新编译吧
这个时候链接就好用多了

这可不好!需要想办法解决!
对此,就轮到 make 大展身手啦!


1. 从小案例开始

1.1 准备工作

这里,我们先写个很乐色的代码

/* main.c */
#include "io.h"
#include "calcu.h"

int main(void)
{
    int num1, num2;
    int answer;
    get_two_number(&num1,&num2);
    answer = my_add(num1,num2);
    display_number(&answer);

    return 0;
}
calcu.c
/* calcu.c */
#include "calcu.h"

int my_add(int num1, int num2)
{
    return num1 + num2;
}

io.c
/* io.c */
#include "io.h"

void get_two_number(int* pnum1, int* pnum2)
{
    printf("input 2 number here:\n");
    scanf("%d%d", pnum1,pnum2);
}

void display_number(int* pnum)
{
    printf("%d\n",*pnum);
}

calcu.h
/* calcu.h */
#ifndef __CALCU_H__
#define __CALCU_H__

int my_add(int,int);

#endif

io.h
/* io.h*/
#ifndef __MYIO_H__
#define __MYIO_H__

#include <stdio.h>

void get_two_number(int*,int*);
void display_number(int*);

#endif

1.2 最基础的makefile

首先我们先介绍makefile的基础规则
makefile可以拆成如下的基础块

target: dependence
    command1
    command2
    ...

其中,target是你的目标,dependence是你的依赖项。
当你执行make target(这里更换为实际target)时,make工具会检查dependence是否存在或满足。如果满足,则会执行命令command。

version 0

接下来,我们将一步一步改造我们的makefile,这里有几点提醒要注意:
– 缩进要是Tab,但是某些平台会将Tab转换为4个空格,这将会导致makefile报错

version 1

对此,我们上述的代码用make来实现,就应该是

main: main.c io.c calcu.c
    gcc main.c io.c calcu.c -o main

clean:
    rm main
    rm *.o

version 2

但是这样解决不了问题2,这实际上还是全编译。
对此,我们要把它拆开

main: main.o io.o calcu.o
    gcc main.o io.o calcu.o -o main

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

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

io.o: io.c
    gcc -c io.c

clean:
    rm main
    rm *.o

很好,现在编译就不是全编译了。

version 3

不过这样的编写太冗长了,如main.o io.o calcu.o重复写就很烦。
这个时候,我们就可以使用变量

objects = main.o io.o calcu.o
main: (objects)
    gcc(objects) -o main

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

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

io.o: io.c
    gcc -c io.c

clean:
    rm main
    rm *.o

version 4

上面的代码虽然已经足够简洁了,但是一个一个的写 gcc 编译还是有些烦。
此时,通配符就出马了。

objects = main.o io.o calcu.o
main: (objects)
    gcc(objects) -o main

%.o: %.c
    gcc -c $<

clean:
    rm main
    rm *.o

其中%表示任意文字,%.o: %.c 表示:任何 .o 文件都可以由同名.c 文件生成。(在dependence里的%具体是什么内容,取决于执行此规则时候的target中的%是什么内容)
在command中的$<,表示第一个依赖项(如果有多个)。

version 5

上文已经足够简单,但是忘记考虑了一个东西,即依赖项没有添加头文件,因为实际工程中,头文件又不是不会修改。
makefile会递归检查所有依赖的时间戳。

objects = main.o io.o calcu.o
main: (objects)
    gcc(objects) -o main

%.o: %.c %.h
    gcc -c $<

clean:
    rm main
    rm *.o

version 6

如果你尝试在同文件夹下面添加一个clean文件,你再次尝试清理make clean时会发现出现

make: 'clean' is up to date.

这是因为clean没有任何依赖,而且clean已经存在,所以导致的问题。
对此,可以使用伪目标来避免。

objects = main.o io.o calcu.o
main: (objects)
    gcc(objects) -o main

%.o: %.c %.h
    gcc -c $<

.PHONY = clean
clean:
    rm main
    rm *.o

其中.PHONY : clean就是在说明target clean只是一个伪目标,不会真的生成东西。


2 小结

至此,我们一个小型的makefile已经写完了。

但是很显然,很多makefile的知识点都没有提及。

– 更详细的通配符介绍
条件语句的介绍
函数的介绍

剩下的这些,可以在实际工程中边学便摸索。

不要一味追求完美,尤其在面对陌生领域时。
先动手,再优化——你越早使用工具,就越快掌握它;
越快掌握它,就越高效地推进项目,甚至真正踏入这片未知之地。
所以,先动手吧

3. 参考资料

[1] 正点原子资料
[2] 《跟我一起写makefile》

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇