多⽂件,多头⽂件时gcc与makefile的编写经验
经常在⼀个项⽬中包含多个.c源⽂件,⽽且⼀个.c源⽂件包含了⼀堆的头⽂件,这种情况下如何编写makefile,使得能成功编译整个项⽬?本博⽂对这些问题提出⾃⼰浅析的理解。涉及到对gcc命令使⽤与编译流程理解及多⽂件时makefile的编写。
=================================================================================
很奇怪的是,在《跟我⼀起学Makefile-陈皓》的⽂档中,第五部分第⼋章,有讲到⾃动⽣成依赖性的问题。从字⾯意思,我理解为在.c源⽂件中每添加⼀个⾃⼰的头⽂件都需要在makefile的对应⽬标中添加对该头⽂件的依赖。
添加对此头⽂件的依赖是为了确保有此⽂件?但是在gcc编译时,如果寻不到该头⽂件,是会停⽌编译且报错的。
然后我就直接理解为了:添加头⽂件依赖是为了gcc编译时让此头⽂件也作为输⼊⽂件
例如:
<abc.c>:
include "abc.h"
include <stdio.h>
void main()
{
printf("helloworld");
}
编译成.o⽂件时,应该为:
gcc -c abc.c abc.h //错误⽰范
结果却是相当于执⾏了:
gcc -c abc.c
gcc -c abc.h
是因为我的gcc版本较新?总之,折腾了很久后,我就是发现,头⽂件没必要写到依赖中,⽽gcc也不需要直接把此头⽂件作为输⼊,但是若头⽂件不在当前⽬录下,需要⽤-I指出头⽂件地址。(纯粹个⼈阶段性的理解,还希望⼤⽜能指正)
=================================================================================
吐槽完毕,下⾯是经验分享:
⼀、gcc编译的流程
预处理(Pre-Processing) -> 编译(Compling) -> 汇编(Assembling) -> 连接(Linking)
1. 预处理:处理#include、#define、#ifdef 等宏命令
2. 编译:把预处理完的⽂件编译为汇编程序.s
3. 汇编:把汇编程序.s编译为.o⼆进制⽂件
4. 链接:把多个⼆进制⽂件.o集合(链接)成⼀个可执⾏⽂件
由此可见,
1. 多头⽂件.h时,在预处理阶段处理,指明头⽂件所在地址,但通常在makefile中是⼀个命令完成到第3步,⽣成.o
2. 多源⽂件.c时,在链接阶段处理,gcc命令要写出所有源⽂件,不然会出现引⽤了却未定义的函数\变量等
⼆、多⽂件,多头⽂件时的gcc经验分享
情况1、⼀步直接由.c⽣成执⾏⽂件
gcc [-I包含⽂件.h的⽬录1 -I包含⽂件.h的⽬录2...] 源⽂件1.c [源⽂件2.c 源⽂件] -o 执⾏⽂件名
情况2、先编译成.o,再由.o链接为执⾏⽂件(makefile中常见,因为在⼤型项⽬时,可以实现重编译部分⽂件⽽不需要每次都全部编译源⽂件⽂件)
a、gcc [-I源⽂件1包含的⽂件.h的⽬录] 源⽂件1.c [-o 源⽂件1.o]
//可以通过-o指定⽣成的⼆进制⽂件地址和位置
gcc [-I源⽂件2包含的⽂件.h的⽬录] 源⽂件2.c [-o 源⽂件2.o]
.
...
b、gcc    源⽂件1.o    源⽂件2.o  ......  -o  ⽣成的执⾏⽂件(默认为a.out)
三、多⽂件,多头⽂件时的makefile经验分享
以例⼦说明最好理解,⽂件结构如下图:
[user@13:06 src]$tree
.
├── common
│├── abc.c
│├── abc.h
│└── test
│├── test.c
│└── test.h
├── Makefile
└── myhello.c
2 directories, 6 files
[user@13:06 src]$
(以下为源⽂件+头⽂件的展⽰,没兴趣的可以跳过此部分,不影响整体理解)
<myhello.c>:
[user@13:08 src]$cat myhello.c
#include <stdio.h>
#include "test.h"
#include "abc.h"
void printhelloworld(void);
int main()
{
abc();
printtest();
printf("\n");
printhelloworld();
return 0;
}
void printhelloworld(void){
printf("hello world\n");
}
[user@13:08 src]$
<abc.c && abc.h>
[user@13:10 src]$cat ./common/abc.c
#include "abc.h"
void abc(void)
{
printf("\nit is in funciton abc");
}
[user@13:10 src]$cat ./common/abc.h
#include <stdio.h>
void abc(void);
[user@13:11 src]$
<test.c && test.h>
[user@13:11 src]$cat ./common/test/test.c
#include <stdio.h>
void printtest(void)
{
printf("\nit is in test.c");
}
[user@13:11 src]$cat ./common/test/test.h
void printtest(void);
[user@13:11 src]$
(代码展⽰到此结束)
简单来说,在myhello.c的main中,需要调⽤./common/abc.c的abc函数和./common/test.c的printtest函数,因⽽包含了他们的头⽂件abc.h  test.h
重点来了,makefile可以怎么写(只是我的写法的参考)
[user@13:11 src]$cat Makefile
//⽬标(要⽣成的⽂件名)
TARGET    := myhello
//编译器的选择(在Linux中其实可以忽略,因为cc指向的本来就是gcc)
CC    := gcc
//编译的参数
CFLAG    := -Wall
//编译包含的头⽂件所在⽬录
INCLUDES  := -I. -Icommon/ -Icommon/test
//所有⽤到的源⽂件,注意:⾮当前⽬录的要+上详细地址
SRCS    = myhello.c ./common/abc.c ./common/test/test.c
//把源⽂件SRCS字符串的后缀.c改为.o
OBJS    = $(SRCS:.c=.o)
//匹配所有的伪⽬标依赖,即执⾏⽬标myhello.o & ./common/abc.c & ./common/test/test.c
.PHONY:all //all为伪⽬标all:$(OBJS)
//当所有依赖⽬标都存在后,链接,即链接myhello.o & ./common/abc.c & ./commontest/test.c
makefile phony$(CC) $(LDFLAG) -o $(TARGET) $^
//重定义隐藏规则,匹配上述⽬标:myhello.o & ./common/abc.c & ./common/test/test.c
%.o:%.c
//⽣成.o⽂件,注意,由于SRCS有个别包含详细地址的,⽣成的.o⽂件也是详细地址
$(CC) -c $(INCLUDES) $(CFLAG) $(CPPFLAG) $< -o $@
//清空除源⽂件外的所有⽣成⽂件
clean:    rm -rf $(basename $(TARGET)) $(SRCS:.c=.o)[user@13:14 src]$
make执⾏下,结果怎么样呢:
[user@13:35 src]$make
//编译了myhello.c,⾃动处理了头⽂件abc.h test.h的包含关系
gcc -c -I. -Icommon/ -Icommon/test -Wall  myhello.c -o myhello.o gcc -c -I. -Icommon/ -Icommon/test -Wall  common/abc.c -o common/abc.o gcc -c -I. -Icommon/ //把⽣成的所有.o⽂件链接为执⾏⽂件,如果有缺失,会提⽰函数/变量未定义
gcc  -o myhello  myhello.o common/abc.o common/test/test.o[user@13:35 src]$
说明:由于当前博⽂编写代码是在Objective-C上,因此为了观看⽅便⽽⽤注释//,实际在makefile中注释为#
此时的⽂件结构:
[user@13:43 src]$tree
.
├── common
│├── abc.c
│├── abc.h
│├── abc.o
│└── test
│├── test.c
│├── test.h
│└── test.o
├── Makefile
├── myhello
├── myhello.c
└── myhello.o
2 directories, 10 files
[user@13:43 src]$
不知道注意到了没,我通过-o指定地址,把⽣成的.o与源⽂件.c放在⼀起的,例如abc.o放在了abc.c的⽬录,但同时的,链
接时也需要给出详细地址。
结论:
在gcc时,会⾃动解决头⽂件.h的依赖关系,只需要指明头⽂件的地址
在gcc链接时,才需要把所有的源⽂件.o列出了,否则出现引⽤了未定义的变量/函数

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。