目录
首先我们建立一个hello.c 的程序。
- #include
-
- 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.1 编写
如下为此工程的目录结构,对应文件的内存如下
- .
- |-- Makefile
- |-- hello.c
- |-- print.c
- `-- print.h
print.c
- #include
- #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
- #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个目录,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
另外在实际的开发过程中,一整个系统,不可能是一个公司能够做完成,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
- #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.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