如何写Makefile编译汇编和C文件

编程语言及工具

99人已加入

描述

  经常在一个项目中包含多个.c源文件,而且一个.c源文件包含了一堆的头文件,这种情况下如何编写makefile,使得能成功编译整个项目?本博文对这些问题提出自己浅析的理解。涉及到对gcc命令使用与编译流程理解及多文件时makefile的编写。

  有些场景下编译的程序是不能依赖OS和标准的C库的,并且需要C和汇编混合编译,如bootloader程序,就需要制定参数-nostdlib,这样的Makefile如下所示:

  all:

  arm-linux-gcc -O2 -Wall -nostdlib -march=armv4 -Wl,-T,ipl.lds uart.c ipl.c nfc.c nand.c sha1.c arm.s -o ipl.exe

  arm-linux-objcopy -Obinary ipl.exe ipl.bin

  clean:

  rm -rf ipl.exe ipl.bin

  很奇怪的是,在《跟我一起学Makefile-陈皓》的文档中,第五部分第八章,有讲到自动生成依赖性的问题。从字面意思,我理解为在.c源文件中每添加一个自己的头文件都需要在makefile的对应目标中添加对该头文件的依赖。

  添加对此头文件的依赖是为了确保有此文件?但是在gcc编译时,如果寻找不到该头文件,是会停止编译且报错的。

  然后我就直接理解为了:添加头文件依赖是为了gcc编译时让此头文件也作为输入文件

  例如:

  [objc] view plain copy《abc.c》:

  include “abc.h”

  include 《stdio.h》

  void main()

  {

  printf(“helloworld”);

  }

  编译成.o文件时,应该为:

  [objc] view plain copygcc -c abc.c abc.h //错误示范

  结果却是相当于执行了:

  [objc] view plain copygcc -c abc.c

  gcc -c abc.h

  是因为我的gcc版本较新?总之,折腾了很久后,我就是发现,头文件没必要写到依赖中,而gcc也不需要直接把此头文件作为输入,但是若头文件不在当前目录下,需要用-I指出头文件地址。(纯粹个人阶段性的理解,还希望大牛能指正)

  在编程的时候,我们可以把一个完整程序的每个函数分离出来,写成.c文件,最后再一起编译和链接。这样有利于程序功能模块化,也方便检查代码错误。

  .h文件:里面编辑该程序需要引用的头文件。

  #ifndef /#define / #endif : 防止该头文件被重复引用。

  整体用法:

  #ifndef A_H //如果没有a.h文件,#define A_H。如果有,结束定义

  #define A_H //定义a.h文件

  定义a.h需要的头文件

  #endif //结束定义部分

  例子:

  一个.h文件的书写格式。

  #ifndef A_H

  #define A_H

  定义程序所需的一些头文件

  #include《stdio.h》

  #include《string.h》

  #include《stdlib.h》

  …

  #endif

  保存退出。

  其实在编写小程序的时候也可以不用#ifndef /#define / #endif,但是用它更安全,不易出错。

  c文件的书写格式

  在写每个函数之前要加载头文件,如果是.h文件,就需要写#include”a.h”。

  例如:引用之前的a.h文件。

  add.c

  #include”a.h”

  int add(int a,intb)

  {

  return a+b;

  }

  保存退出。

  程序编辑完成之后,需要编译链接。

  我们可以用gcc编译每个.c文件。如果有三个.c文件a.c、b.c、c.c,编译方法如下:

  gcc a.c –o a.o //将三个.c文件编译成.o文件

  gcc b.c -o b.o

  gcc c.c -o c.o

  gcc a.o b.o c.o –o all //将三个.o文件编译成一个可执行文件

  。/all //执行程序

  例如:

  test.h

  Makefile

  add.c

  Makefile

  main.c

  Makefile

  编译:

  Makefile

  执行:

  Makefile

  如果我们有很多个.c文件,这个方法就不太好了,这时,我们提出Makefile文件。

  Makefile:自动编译。先将每个.c文件的编译过程提前写在Makefile文件中,在运行程序时,系统直接用make命令使文件自动编译,提高效率。

  Makefile文件的书写格式:

  vim Makefile

  文件类型:由哪个文件得到

  得到过程

  例如:

  main:main.o //可执行文件main是由目标文件main.o得到。

  gcc main.o –o main //得到过程是将main.o编译成main文件。

  main.o:main.c

  gcc -c main.c -o main.o

  在Makefile文件中一定要将每一个.c文件按执行顺序先编译成.o文件,再按顺序将.o文件编译成可执行文件。

  每次编译过后会产生很多的.o文件,对于程序运行没什么太大意义,反而会占内存,所以我们也可以在Makefile文件中添加清除命令(clean),如:

  .PHONY:clean

  clean: 删除所有文件类型为.o的文件

  rm –rf *.o

  一些文件也可用下面符号表示:

  $@: 代表规则里面的目标文件。

  $《:代表规则当中的第一个依赖文件。

  %.c:%.o 所有.c文件全部编译成.o文件

  .PHONY:clean

  如果有.PHONY:clean,外面也有clean文件,执行make clean时,里面的.o文件会被删除而外面的clean文件还在。确保了外面clean文件的安全性。如果没有.PHONY:clean 语句,外面也没有clean文件时,在执行make clean也会删除.o文件,如果外面有clean,则会报错。

  PHONY:目标并非实际的文件名,只是显示在请求时执行命令的名字。有两种理由需要使用PHONY目标:避免和文件名冲突,改善性能。

  例如:

  vim Makefile

  Makefile

  编译和执行(make:编译, 。/all:执行):

  Makefile

  下面是经验分享:

  一、gcc编译的流程

  gcc的编译流程分为4步:(详见:http://xredman.iteye.com/blog/700901)

  预处理(Pre-Processing) -》 编译(Compling) -》 汇编(Assembling) -》 连接(Linking)

  预处理:处理#include、#define、#ifdef 等宏命令

  编译:把预处理完的文件编译为汇编程序.s

  汇编:把汇编程序.s编译为.o二进制文件

  链接:把多个二进制文件.o集合(链接)成一个可执行文件

  由此可见,

  多头文件.h时,在预处理阶段处理,指明头文件所在地址,但通常在makefile中是一个命令完成到第3步,生成.o

  多源文件.c时,在链接阶段处理,gcc命令要写出所有源文件,不然会出现引用了却未定义的函数\变量等

  二、多文件,多头文件时的gcc经验分享

  情况1、一步直接由.c生成执行文件

  [objc] view plain copygcc [-I包含文件.h的目录1 -I包含文件.h的目录2.。。] 源文件1.c [源文件2.c 源文件3.c.。。] -o 执行文件名

  情况2、先编译成.o,再由.o链接为执行文件(makefile中常见,因为在大型项目时,可以实现重编译部分文件而不需要每次都全部编译源文件文件)

  [objc] view plain copya、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经验分享

  以例子说明最好理解,文件结构如下图:

  Makefile

  (以下为源文件+头文件的展示,没兴趣的可以跳过此部分,不影响整体理解)

  《myhello.c》:

  [objc] view plain copy[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》

  [objc] view plain copy[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》

  [objc] view plain copy[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可以怎么写(只是我的写法的参考)

  [objc] view plain copy[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

  $(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执行下,结果怎么样呢:

  [objc] view plain copy[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/ -Icommon/test -Wall common/test/test.c -o common/test/test.o

  //把生成的所有.o文件链接为执行文件,如果有缺失,会提示函数/变量未定义

  gcc -o myhello myhello.o common/abc.o common/test/test.o[user@13:35 src]$

  说明:由于当前博文编写代码是在Objective-C上,因此为了观看方便而用注释//,实际在makefile中注释为#

  此时的文件结构:

  Makefile

  不知道注意到了没,我通过-o指定地址,把生成的.o与源文件.c放在一起的,例如abc.o放在了abc.c的目录,但同时的,链接时也需要给出详细地址。

  结论:

  在gcc时,会自动解决头文件.h的依赖关系,只需要指明头文件的地址

  在gcc链接时,才需要把所有的源文件.o列出了,否则出现引用了未定义的变量/函数

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分