目录
1.单文件编译
首先我们建立一个hello.c 的程序。
#include <stdio.h>
int main()
{
printf("hello world!!!\n");
return 0;
}
此hello.c文件以ascii码的方式存储于磁盘中,若此程序在要在linux系统运行,比如在ubuntu18.04上运行,必须对其进行编译。
gcc -o hello hello.c
运行:
./hello
从上面可以看出,我们只用一条命令,可以实现对hello.c的编译,但是假如工程中不仅仅有hello.c,还有很多其它的.c和.h文件呢?
我们能否有种方式,像我们调试windows工程一样,可以一键编译的,答案是有,就是MakeFile,执行一个指令make可以完全所有工作和文件的编译。
2.多文件编译
2.1 编写
如下为此工程的目录结构,对应文件的内存如下
.
|-- Makefile
|-- hello.c
|-- print.c
`-- print.h
print.c
#include <stdio.h>
#include "print.h"
int my_print(void)
{
printf("hello world!!!! ==>>\n");
return 0;
}
print.h
#ifndef __PRINT_H__
#define __PRINT_H__
int my_print(void);
#endif
hello.c
#include <stdio.h>
#include "print.h"
int main()
{
my_print();
return 0;
}
我们要用make完成上面三个文件的编译,并生成hello的可执行二进制文件。
makefile组成三要素:目标,依赖, 命令
targets(目标):prequisities(依赖)
command(命令,command前面要有一个tab键)
根据以上规则,写出Makefile
hello:hello.o print.o
gcc hello.o print.o -o hello
hello.o:hello.c print.h
gcc -c hello.c
print.o:print.c print.h
gcc -c print.c
clean:
rm -f *.o hello
从上面的Makefile看出,首先print.c和hello.c分别编译和汇编生成print.o,hello.o的机器指令,然后gcc将print.o和hello.o链接成目标程序hello
2.2 改进
将两个中间过程的二制作文件.o文件用OBJ替代
OBJ=hello.o print.o
hello:$(OBJ)
gcc $(OBJ) -o hello
hello.o:hello.c print.h
gcc -c hello.c
print.o:print.c print.h
gcc -c print.c
clean:
rm -f *.o hello
2.3 改进
省略.h
OBJ=hello.o print.o
hello:$(OBJ)
gcc $(OBJ) -o hello
hello.o:hello.c
gcc -c hello.c
print.o:print.c
gcc -c print.c
clean:
rm -f *.o hello
2.4 改进
# $@ : 目标文件
# $^ : 所有依赖文件
# $< : 第一个依赖文件
引入部分符号,替代上述的内容,让程序自己去找.c生成对应的.o并将所有的.o链接成目录文件
CC = gcc
TARGETS = hello
OBJ=hello.o print.o
$(TARGETS):$(OBJ)
$(CC) $^ -o $@
%.o:%.c
$(CC) -c $<
clean:
rm -f *.o $@
2.5 改进
进一步根据文件夹内所有的.c文件,推段出所有.o文件。到这一步,就实现了当前目录下的所有文件一键编译。假如我们想将.c文件.h文件分开,该如何做。
CC = gcc
TARGETS = hello
SRC := $(wildcard *.c)
OBJ=$(patsubst %.c,%.o,$(SRC))
$(TARGETS):$(OBJ)
$(CC) $^ -o $@
%.o:%.c
$(CC) -c $<
clean:
rm -f *.o $@
3.多目录编译
如下目录,我们重新创建了3个目录,src用于放所有c文件,obj用于放所有的.o文件,inc用于放所有的.h文件。
.
|-- Makefile
|-- inc
| `-- print.h
|-- obj
`-- src
|-- hello.c
`-- print.c
1.加入CFLAGS用于让c文件转化成.o时,能够知道所有的.h文件在哪个位置
2.在生成.o文件的命令后面加了-o $@
此举的目的是为了让所有.o文件生成到obj目录
3.src和inc和obj的位置均用相应的宏指定
TARGETS = hello
DIR_INC = ./inc
DIR_SRC = ./src
DIR_OBJ = ./obj
CC = gcc
SRC := $(wildcard ${DIR_SRC}/*.c)
OBJ := $(patsubst ${DIR_SRC}/%.c,$(DIR_OBJ)/%.o,$(SRC))
INCLUDES = -I$(DIR_INC)
CFLAGS += $(INCLUDES)
$(TARGETS):$(OBJ)
$(CC) $^ -o $@
$(DIR_OBJ)/%.o:$(DIR_SRC)/%.c
$(CC) $(CFLAGS) -c $< -o $@
clean:
rm -f $(TARGETS)
rm -f $(DIR_OBJ)/*.o
4.动态库与静态库
另外在实际的开发过程中,一整个系统,不可能是一个公司能够做完成,A公司和B公司同时做项目,一般A公司不可能对B公司开源自己的代码,因而需要有一种方式将代码屏蔽,而只将功能抽象出来,这种方法就是库。
库有动态库和静态库。
静态库:
扩展名为.a,函数库通常扩展名为libxxx.a
这类库在编译的时候会直接整合到目标程序中,所以利用静态函数库编译成的文件会比较大,这类函数库最大的优点就是编译成功的可执行文件可以独立运行,而不再需要向外部要求读取函数库的内容;但是从升级难易度来看明显没有优势,如果函数库更新,需要重新编译。
动态库
扩展名为.so,函数库通常名为libxxx.so
与静态函数库被整个捕捉到程序中不同,动态函数库在编译的时候,在程序里只有一个“指向”的位置而已,也就是说当可执行文件需要使用到函数库的机制时,程序才会去读取函数库来使用;也就是说可执行文件无法单独运行。这样从产品功能升级角度方便升级,只要替换对应动态库即可,不必重新编译整个可执行文件。
下面我们来生成动态库与静态库,目录如下,我们将print_lib.c的内容打包成库。
.
|-- Makefile
|-- inc
| `-- print_lib.h
|-- obj
`-- src
`-- print_lib.c
print_lib.c
#include <stdio.h>
#include "print_lib.h"
int my_print_api(void)
{
printf("hello world!!!! this is lib ==>>\n");
return 0;
}
print_lib.h
#ifndef __PRINT_LIB_H__
#define __PRINT_LIB_H__
int my_print_api(void);
#endif
Makefile修改:
1.目录文件名改成libxxx.so和libxxx.a, 记住最前面一定有lib标识
2.CFLAGS位置加-fPIC
3.链接过程中一定要加-shared
4.静态库是用AR打包生成
TARGETS = libprint_lib.so
TARGETS_STATIC = libprint_lib.a
DIR_INC = ./inc
DIR_SRC = ./src
DIR_OBJ = ./obj
CC = gcc
SRC := $(wildcard ${DIR_SRC}/*.c)
OBJ := $(patsubst ${DIR_SRC}/%.c,$(DIR_OBJ)/%.o,$(SRC))
INCLUDES = -I$(DIR_INC)
CFLAGS += $(INCLUDES)
CFLAGS += -fPIC
$(TARGETS):$(OBJ)
$(CC) -shared $^ -o $@
$(AR) -r $(TARGETS_STATIC) $(DIR_OBJ)/*.o
$(DIR_OBJ)/%.o:$(DIR_SRC)/%.c
$(CC) $(CFLAGS) -c $< -o $@
clean:
rm -f $(TARGETS_STATIC)
rm -f $(TARGETS)
rm -f $(DIR_OBJ)/*.o
5.动态库与静态库使用
5.1 静态库
目录如下:
.
|-- Makefile
|-- inc
| `-- print.h
|-- lib
| `-- print_lib
| |-- inc
| | `-- print_lib.h
| `-- lib
| `-- libprint_lib.a
|-- obj
`-- src
|-- hello.c
`-- print.c
加入LDFLAGS,用于在链接过程中指定库路径
TARGETS = hello
DIR_INC = ./inc
DIR_SRC = ./src
DIR_OBJ = ./obj
CC = gcc
SRC := $(wildcard ${DIR_SRC}/*.c)
OBJ := $(patsubst ${DIR_SRC}/%.c,$(DIR_OBJ)/%.o,$(SRC))
PRINT_LIB_DIR = ./lib/print_lib/lib
PRINT_INC_DIR = ./lib/print_lib/inc
INCLUDES = -I$(DIR_INC)
INCLUDES += -I$(PRINT_INC_DIR)
LDFLAGS = $(PRINT_LIB_DIR)/libprint_lib.a
CFLAGS += $(INCLUDES)
$(TARGETS):$(OBJ)
$(CC) $^ -o $@ $(LDFLAGS)
$(DIR_OBJ)/%.o:$(DIR_SRC)/%.c
$(CC) $(CFLAGS) -c $< -o $@
clean:
rm -f $(TARGETS)
rm -f $(DIR_OBJ)/*.o
5.2动态库
目录如下
.
|-- Makefile
|-- inc
| `-- print.h
|-- lib
| `-- print_lib
| |-- inc
| | `-- print_lib.h
| `-- lib
| `-- libprint_lib.so
|-- obj
`-- src
|-- hello.c
`-- print.c
1.加入LDFLAGS,用于在链接过程中指定库路径
2.在运行前,要将对应.so拷至/usr/lib下
TARGETS = hello
DIR_INC = ./inc
DIR_SRC = ./src
DIR_OBJ = ./obj
CC = gcc
SRC := $(wildcard ${DIR_SRC}/*.c)
OBJ := $(patsubst ${DIR_SRC}/%.c,$(DIR_OBJ)/%.o,$(SRC))
PRINT_LIB_DIR = ./lib/print_lib/lib
PRINT_INC_DIR = ./lib/print_lib/inc
INCLUDES = -I$(DIR_INC)
INCLUDES += -I$(PRINT_INC_DIR)
LDFLAGS = -L $(PRINT_LIB_DIR) -lprint_lib
CFLAGS += $(INCLUDES)
$(TARGETS):$(OBJ)
$(CC) $^ -o $@ $(LDFLAGS)
$(DIR_OBJ)/%.o:$(DIR_SRC)/%.c
$(CC) $(CFLAGS) -c $< -o $@
clean:
rm -f $(TARGETS)
rm -f $(DIR_OBJ)/*.o