一个工程中的源文件不计其数,其按类型、功能、模块分别放在若干个目录中,makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,因为 makefile就像一个Shell脚本一样,其中也可以执行操作系统的命令。
linux 下 编译c 文件 并运行
- 利用vim 创建一个c文件
#include <stdio.h>
#include <stdlib.h>
void main(){
int count = 5 + 5 ;
printf("结果: %d",count);
}
- 使用gcc 编译c 文件生成.o后缀的文件(gcc类似javac工具 生成class文件)
gcc -c test.c
linux 使用gcc 提示bash:gcc:command not found
出现这个问题时,首先用命令:
whereis gcc
查找看看gcc,gcc是否安装好。如果此时显示
gcc:/usr/bin/gcc
则说明已装好了,只需要配置到PATH路径下面就行了。
如果只是显示
gcc:
则说明gcc没有下载。
Centos(Rehat系列)系统下载gcc命令:yum install gcc
Ubuntu系统下载gcc命令:apt-get inatall gcc
- .o文件是中间文件,但是不能够执行, 再执行一个命令
gcc -o main test.o
main 是自己定义的让linux 执行的文件名字
4. 然后再执行 ./main 就可以了
结果
结果: 10[root@host makefileTest]# vim test.c
实际上makefile 包含gcc 里面所做的操作实际上就是类似上面的操作, Cmakelist 又包含了makefile 简化了makefile 的操作.
linux下编译多个c文件并运行
现在有多个文件
宏定义 避免重复编译
在c语言中,对同一个变量或者函数进行多次声明是不会报错的。所以如果h文件里只是进行了声明工作,即使不使用# ifndef宏定义,一个c文件多次包含同一个h文件也不会报错。 使用#ifndef可以避免下面这种错误:如果在h文件中定义了全局变量,一个c文件包含同一个h文件多次,如果不加#ifndef宏定义,会出现变量重复定义的错误;如果加了#ifndef,则不会出现这种错.
https://www.cnblogs.com/challenger-vip/p/3386819.html
include 中有个commom.h 的头文件
#ifndef COMMON_H_
#define COMMON_H_
int plus(int a, int b);
int minus(int a, int b);
int multi(int a, int b);
int divi(int a, int b);
#endif
分别定义了加减乘除的函数 然后在外面分别实现了加减乘除的c文件
plus.c
#include <stdlib.h>
#include <stdio.h>
int plus(int a, int b){
return a + b;
}
还有减,乘,除的c,剩下的就不写了
main.c文件
#include <stdlib.h>
#include <stdio.h>
#include "include/common.h"
void main(){
int ret = minus(20,10);
printf("ret:%d\n",ret);
}
这个时候 如果我想在linux 下 运行main.c 文件 应该把所有的.c文件都编译一次.
和上面编译单个c文件类似
[root@host makefile]# ls
divi.c include main.c minus.c multi.c plus.c
[root@host makefile]# gcc -c divi.c
[root@host makefile]# gcc -c main.c
[root@host makefile]# gcc -c minus.c
[root@host makefile]# gcc -c multi.c
[root@host makefile]# gcc -c plus.c
[root@host makefile]# ls
divi.c divi.o include main.c main.o minus.c minus.o multi.c multi.o plus.c plus.o
[root@host makefile]#
可以看到已经把.o的中间文件生成了. 下面生成可执行的文件,可执行文件是依赖于.o文件的,需要把这些.o文件都打包到可执行文件里面去. 所以命令是
gcc divi.o main.o minus.o multi.o plus.o -o myapp
最后执行 ./myapp
图:
最后结果 ret : 10
因为我在main里面调用了相减的函数
int ret = minus(20,10);
printf("ret:%d\n",ret);
这里就模拟了 一个c文件里面调用另外一个c文件里面的函数
很麻烦多个c文件, 所以使用makefile.
makefile 中文手册.
使用Makefile代替手动编译c文件
创建Makefile文件 , 如果不指定 使用make的时候默认会去找Makefile文件
刚才手动编译的过程 原始的c文件 > 创建.o文件 > 生成可执行文件myapp
在makefile里面 是倒过来的
使用tab键后面跟上规则的命令行. 必须是tab键跟上命令才会执行.要不然不认识.
rm *.o 吧刚才生成的.o文件全部删除 还有myapp也删除
为什么myapp是可执行文件,因为有main函数.
冒号: 代表依赖
main.o:main.c, main.o依赖于main.c
Makefile 文件
myapp:main.o plus.o minus.o multi.o divi.o
gcc main.o plus.o minus.o multi.o divi.o -o myapp
main.o:main.c
gcc -c main.c
plus.o:plus.c
gcc -c plus.c
minus.o:minus.c
gcc -c minus.c
multi.o:multi.c
gcc -c multi.c
divi.o:divi.c
gcc -c divi.c
保存以后返回 执行make 命令
[root@host makefile]# vim Makefile
[root@host makefile]# make
gcc -c main.c
gcc -c plus.c
gcc -c minus.c
gcc -c multi.c
gcc -c divi.c
gcc main.o plus.o minus.o multi.o divi.o -o myapp
[root@host makefile]# ./myapp
ret:10
[root@host makefile]#
这样的结果和刚才单个执行的结果是一样的.
-
但是如果再次使用make命令就编译不了了. 和最后修改时间有关系. 如果最后源文件修改的时间比如main.c没有修改,那么生成的可执行文件myapp也不会重新生成. 只有当源文件的时间大于生成的可执行文件的时间才会重新编译.(android studio 也是用的这种机制)
-
make 也可以执行shell命令
使用Makefile单独执行某个命令
目标“clean”不是一个文件,它仅仅代表执行一个动作的标识。正常情况下,不
需要执行这个规则所定义的动作,因此目标“clean”没有出现在其它任何规则的依赖
列表中。因此在执行make时,它所指定的动作不会被执行。除非在执行make时明确地
指定它。而且目标“clean”没有任何依赖文件,它只有一个目的,就是通过这个目标
名来执行它所定义的命令。Makefile中把那些没有任何依赖只有执行动作的目标称为
“伪目标”(
中把那些没有任何依赖只有执行动作的目标称为
“伪目标”(phony targets) )。参考4.6 Makefile伪目标 一节。需要执行“clean”目标
所定义的命令,可在shell下输入:make clean。
4.6 Makefile伪目标
本节我们讨论 Makefile 的一个重要的特殊目标:伪目标。伪目标是这样一个目标:
它不代表一个真正的文件名,在执行 make 时可以指定这个目标来执行其所在规则定义
的命令,有时也可以将一个伪目标称为标签。使用伪目标有两点原因:
-
避免在我们
的 Makefile 中定义的只执行命令的目标(此目标的目的为了执行执行一些列命令,而
不需要创建这个目标)和工作目录下的实际文件出现名字冲突。 -
提高执行 make 时
的效率,特别是对于一个大型的工程来说,编译的效率也许你同样关心。以下就这两个
问题我们进行分析讨论: -
如果我们需要书写这样一个规则:规则所定义的命令不是去创建目标文件,而
是通过 make 命令行明确指定它来执一些特定的命令。像常见的 clean 目标:
clean:
rm *.o temp
规则中“rm”不是创建文件“clean”的命令,而是删除当前目录下的所有.o 文件和 temp
文件。当工作目录下不存在“clean”这个文件时,我们输入“make clean”,“rm *.o
temp”总会被执行。这是我们的初衷。
但是如果在当前工作目录下存在文件“clean”,情况就不一样了,同样我们输入
“make clean”, 由于这个规则没有任何依赖文件,所以目标被认为是最新的而不去执
行规则所定义的命令,因此命令“rm”将不会被执行。这并不是我们的初衷。为了解
决这个问题,我们需要将目标“clean”声明为伪目标。将一个目标声明为伪目标的方
法是将它作为特殊目标.PHONY”的依赖。如下:
.PHONY : clean
这样目标“clean”就被声明为一个伪目标,无论在当前目录下是否存在“clean”这个
文件。我们输入“make clean”之后。“rm”命令都会被执行。而且,当一个目标被声
明为伪目标后,make 在执行此规则时不会去试图去查找隐含规则来创建它。这样也提
高了 make 的执行效率,同时也不用担心由于目标和文件名重名而使我们的期望失败。
在书写伪目标规则时,首先需要声明目标是一个伪目标,之后才是伪目标的规则定义。
目标“clean”的完整书写格式应该如下:
.PHONY: clean
clean:
rm *.o temp
按照上面的例子在makefile里添加clear, 作用是把之前生成的.o文件删除
myapp:main.o plus.o minus.o multi.o divi.o
gcc main.o plus.o minus.o multi.o divi.o -o myapp
main.o:main.c
gcc -c main.c
plus.o:plus.c
gcc -c plus.c
minus.o:minus.c
gcc -c minus.c
multi.o:multi.c
gcc -c multi.c
divi.o:divi.c
gcc -c divi.c
.PHONY: clean
clean:
rm -f *.o
保存之后 执行 make clean
效果如下图
可以看到最后把.o文件全删了. 把上一次生成的.o文件清空.
成功.
但是上面代码依旧很多.
使用通配符优化
- $ 后面跟上的就是变量
- Makefile有三个非常有用的变量。分别是
^,$<代表的意义分别是:
^–所有的依赖文件,$<–第一个依赖文件。 - 规则命令行中的自动化变量“$^”代表所有通过目
录搜索得到的依赖文件的完整路径名(目录 + 一般文件名)列表
Makefile 修改为
OBJECTS=main.o plus.o minus.o multi.o divi.o
myapp:$(OBJECTS)
gcc $(OBJECTS) -o myapp
#通配符.o依赖于.c
%.o:%.c
gcc -c $^ -o $@
通配符.o依赖于.c
解释: OBJECTS 是变量 包含了那些.o的名字
用$(OBJECTS) 替换变量
和上面的是一样的效果
结果:
[root@host makefile]# vim Makefile
[root@host makefile]# make
gcc -c main.c -o main.o
gcc -c plus.c -o plus.o
gcc -c minus.c -o minus.o
gcc -c multi.c -o multi.o
gcc -c divi.c -o divi.o
gcc main.o plus.o minus.o multi.o divi.o -o myapp
[root@host makefile]# ls
divi.c divi.o include main.c main.o Makefile minus.c minus.o multi.c multi.o myapp plus.c plus.o
makefile 函数的使用
wildcard 函数查找
找到所有的.c文件 放到SOURCE变量里面
SOURCE=$(wildcard *.c)
找到所有.o文件, 可以用上面的复制把c换成o,也可以直接把上面的代码替换.
patsubst 函数替换
SOURCES=$(wildcard *.c)
OBJECTS=$(patsubst %.c %.o,$(SOURCES)
后面和上面 的类似. 最后用@echo 打印下
SOURCES=$(wildcard *.c)
OBJECTS=$(patsubst %.c,%.o,$(SOURCES))
myapp:$(OBJECTS)
gcc $^ -o $@
%o:%c
gcc -c $^ -o $@
@echo $(OBJECTS)
结果:
定义自己的函数
在上面的基础上写.
myfun=$2 $1
myfun_ret=$(call myfun,20,10)
test:
@echo $(myfun_ret)
@echo "ceshi"
myfun:$2
(call myfun,20,10) 函数执行myfun, call关键字,后面带上参数
最后test里面跟上打印
结果:
[root@host makefile]# make test
10 20
ceshi
[root@host makefile]#
递归展开式
在后面定义的前面也能用
myfun=$2 $1
myfun_ret=$(call myfun,20,10)
#递归展开式
str2=$(str1)
str1=hello
test:
@echo $(myfun_ret)
@echo $(str2)
打印结果:
[root@host makefile]# make test
10 20
hello
继续修改:
#递归展开式
str2=$(str1)
str1=hello
str1=after_test
test:
@echo $(myfun_ret)
@echo $(str2)
打印:
[root@host makefile]# make test
10 20
after_test
递归展开式 : 下面改了上面也要改
直接展开
:= 符号
#递归展开式
str2=$(str1)
str1=hello
str1=after_test
#直接展开 放到上面才认识
str4:=android
str3=$(str4)
test:
@echo $(myfun_ret)
@echo $(str2)
@echo $(str3)
打印:
[root@host makefile]# make test
10 20
after_test
android
[root@host makefile]#
追加:
str4=android
str3=$(str4) addStr
test:
@echo $(myfun_ret)
@echo $(str2)
@echo $(str3)
打印结果:
[root@host makefile]# make test
10 20
after_test
android addStr
判断语句
ifeq 关键字, 分开编译某些模块的时候可以用
Cmake
makefile 只能在linux下编译
linux 下使用cmake编译项目
Cmake linux下编译项目
准备工作 : 三个文件夹.
- include里面只有一个头文件 add.h
int add(int a,int b);
- src里面有两个文件 add.c , main.c
add.c
#include "../include/add.h"
int add(int a,int b){
return a+b;
}
main.c
这里只引入了add.h的头文件 没有引入add.c
#include "../include/add.h"
#include <stdio.h>
//这里只引入了add.h的头文件 没有引入add.c
int main(int argc,char** argv)
{
int reslut=add(3,4);
printf("结果 %d ",reslut);
return 0;
}
- build 文件夹.
- 在项目根目录创建CmakeList.txt
编辑文件
cmake_minimum_required(VERSION 2.6)
PROJECT(myapp)
#导入头文件
INCLUDE_DIRECTORIES(
include
)
#指定源文件的目录
AUX_SOURCE_DIRECTORY(src DIR_SRCS)
#把可执行的文件放到那个目录下 用set 设置变量
SET(TEST_MATH ${DIR_SRCS})
ADD_EXECUTABLE(${PROJECT_NAME} ${TEST_MATH})
顺便说下 不分大小写.
- 来到bulid文件夹下 执行
cmake ..
命令
成功打印
[root@host build]# cmake ..
-- Configuring done
-- Generating done
-- Build files have been written to: /home/makefileTest/cmake/build
[root@host build]#
cmake … 作用 是生成了MakeFile
下面要用makefile 的语法生成可执行文件myapp
3. 在build文件夹下调用make
[root@host build]#
[root@host build]# cmake ..
-- Configuring done
-- Generating done
-- Build files have been written to: /home/makefileTest/cmake/build
[root@host build]# make
Scanning dependencies of target myapp
[ 50%] Building C object CMakeFiles/myapp.dir/src/main.c.o
[100%] Building C object CMakeFiles/myapp.dir/src/add.c.o
Linking C executable myapp
[100%] Built target myapp
[root@host build]# ls
CMakeCache.txt CMakeFiles cmake_install.cmake Makefile myapp
[root@host build]#
可以看到成功打印 myapp 调用一下看看
[root@host build]# ./myapp
结果 7 [root@host build]#
遇到问题
- CMake Error: The source directory “/home/makefileTest/cmake” does not appear to contain CMakeLists.txt.
Specify --help for usage, or press the help button on the CMake GUI.
结果是CMakeLists.txt 文件名写错了. 改过来就行
- CMake Error: your CXX compiler: “CMAKE_CXX_COMPILER-NOTFOUND” was not found
解决办法: 终端下输入
安装g++
yum install -y gcc
yum install -y gcc-c++
android 官网 NDK 集成
https://developer.android.com/studio/projects/add-native-code