编译C程序

廖雪峰
资深软件开发工程师,业余马拉松选手。

C程序的编译通常分两步:

  1. 将每个.c文件编译为.o文件;
  2. 将所有.o文件链接为最终的可执行文件。

我们假设如下的一个C项目,包含hello.chello.hmain.c

hello.c内容如下:

#include <stdio.h>

int hello()
{
    printf("hello, world!\n");
    return 0;
}

hello.h内容如下:

int hello();

main.c内容如下:

#include <stdio.h>
#include "hello.h"

int main()
{
    printf("start...\n");
    hello();
    printf("exit.\n");
    return 0;
}

注意到main.c引用了头文件hello.h。我们很容易梳理出需要生成的文件,逻辑如下:

┌───────┐ ┌───────┐ ┌───────┐
│hello.c│ │main.c │ │hello.h│
└───────┘ └───────┘ └───────┘
    │         │         │
    │         └────┬────┘
    │              │
    ▼              ▼
┌───────┐      ┌───────┐
│hello.o│      │main.o │
└───────┘      └───────┘
    │              │
    └───────┬──────┘
            │
            ▼
       ┌─────────┐
       │world.out│
       └─────────┘

假定最终生成的可执行文件是world.out,中间步骤还需要生成hello.omain.o两个文件。根据上述依赖关系,我们可以很容易地写出Makefile如下:

# 生成可执行文件:
world.out: hello.o main.o
	cc -o world.out hello.o main.o

# 编译 hello.c:
hello.o: hello.c
	cc -c hello.c

# 编译 main.c:
main.o: main.c hello.h
	cc -c main.c

clean:
	rm -f *.o world.out

执行make,输出如下:

$ make
cc -c hello.c
cc -c main.c
cc -o world.out hello.o main.o

在当前目录下可以看到hello.omain.o以及最终的可执行程序world.out。执行world.out

$ ./world.out 
start...
hello, world!
exit.

与我们预期相符。

修改hello.c,把输出改为"hello, bob!\n",再执行make,观察输出:

$ make
cc -c hello.c
cc -o world.out hello.o main.o

仅重新编译了hello.c,并未编译main.c。由于hello.o已更新,所以,仍然要重新生成world.out。执行world.out

$ ./world.out 
start...
hello, bob!
exit.

与我们预期相符。

修改hello.h

// int 变为 void:
void hello();

以及hello.c,再次执行make

$ make
cc -c hello.c
cc -c main.c
cc -o world.out hello.o main.o

会触发main.c的编译,因为main.c依赖hello.h

执行make clean会删除所有的.o文件,以及可执行文件world.out,再次执行make就会强制全量编译:

$ make clean && make
rm -f *.o world.out
cc -c hello.c
cc -c main.c
cc -o world.out hello.o main.o

这个简单的Makefile使我们能自动化编译C程序,十分方便。

不过,随着越来越多的.c文件被添加进来,如何高效维护Makefile的规则?我们后面继续讲解。

参考源码

可以从GitHub下载源码。

GitHub

小结

Makefile正确定义规则后,我们就能用make自动化编译C程序。



Comments

Loading comments...