文章目录
gcc
c语言
.c文件 → .exe文件,几个过程:
顺序号 | 字符 | 名称 | 功能 |
---|---|---|---|
1 | -E | 预处理 | 把.h .c展开形成一个文件。宏定义直接替换,头文件、库文件直接展开,生成 .i文件 |
2 | -s | 汇编 | .i文件生成一个汇编代码文件 .s文件 |
3 | -c | 编译 | .s文件生成一个 .obj文件 |
4 | -o | 链接 | .o文件链接 .exe文件 |
预处理: gcc -E hello.c -o helle.i
(将hello.c生成hello.i)
编译 : gcc -S hello.i -o hello.s
()
汇编 : gcc -c hello.s -o hello.o
()
链接: gcc -o hello.o -o hello
()
Makefile
1、Makefile规则书写
工程的组成:
一般而言,基本工程复杂版工程目录
- 主函数和功能函数 源文件 *.c
- 功能函数的头文件―头文件.h宏定义结构体函数声明变量共用体
- 库文件目录库文件 .so .a (包装函数)
- 执行文件目录:二进制文件编译生成的程序
- makefile:工程管理文件(管理上面的所有文件)
语法
- #是注释
- 显示规则:
目标文件:依赖文件
[tab键位]执行指令
-
makefile核心:目标和依赖
依赖:一般指的是你要编译的文件对象,也就是.c目标:一般指的是你要生成的执行文件
例子:gcc *.c -o main
依赖:gcc *.c
目标:main -
makefile书写规则
- 目标:依赖(多文件时,各个依赖文件之间用空格连接)
目标文件一定要有,依赖文件可以没有 - <tab键规则>
- 在代码中的全部表示:
$@
:代表所有目标文件
$^
:代表所有依赖文件
练习
- 练习1:makefile版的helloworld
目标文件一定要存在,依赖文件可以不要
target: # 目标文件名
echo hello word
执行的时候使用:make
运行结果:
如果在each前面+@就只会输出helloword
- 练习2:写一个makefile,实现gcc main.c add.c -o main
目标:
gcc main.c add. c -o main
// add函数
#includee<stdio.h>
int add (int a , int b)
{
printf("%d+%d=%d") ;
}
// 主函数
int main (void)
{
add (12,13) ;
return 0;
}
main : main. c add.c // 目标文件:依赖文件
gcc $^ -o $@
编译器,依赖文件,指定生成目标文件
- 练习3:写一个Makefile,实现三个数获取最大值
三个文件:main.c max.c max.h
max.h文件:
#ifndef _MAX_H
#define _MAX_H
#include <stdio.h>
#include <stdlib.h>
void max (int a ,int b ,int c) ;//函数声明
#endif
max.c文件中:
#include "max.h"
大实现三个数获取最大值*/
void max (int a ,int b ,int c)
{
/*思路
int max=a > b ? a : b;
int maxl=max >c ? max : c;
(a > b ? a : b) > c ? (a > b ? a : b):c;
*/
int max=(a > b ? a : b)> c ? (a > b ? a : b) :c;
printf("最大值为%d",max);
}
main.c函数里面:
#include "max.h"
int main (int argc ,char **argv)
{
//调用
max (atoi(argv [1]) ,atoi(argv [2] ) ,atoi (argv[3]));
return 0 ;
}
Makefile文件里:
main:main.c max.c
gcc $* -o $@
运行结果:
但我们已经对文件执行了make指令,再按一次make后会提示:make:'main' is up to date
:表示 这段代码里面你的代码较上一次执行没有修改不需要再次编译
当一个Makefile文件中有多条命令的时候执行make命令要制定执行哪一个
add : main1.c add.c
gcc $^ -o $@
ax: main.c max.c
gcc $^ -o $@
clean:
rm max add
想要只执行max,执行命令:makc max
第一个目标文件是我的最终目标,也就是递归的执行
以执行一个编译链接C语言程序为例:他的命令行和最后的执行翻译过程是相反的
hello: hello.o
gcc hello.o -o hello
hello.o:hello.s
gcc -c hello.s -o hello.o
hello.s: hello.i
gcc -s hello.i -o hello.s
hello.i: hello.c
gcc -E hello.c -o hello.i
将Makefile文件移到和.c文件同一个文件夹,然后执行指令make
伪目标
伪目标,就是执行指令没有目标文件
伪目标:.phony:
.phony:
clear:
rm -rf hello.o hello.s hello.i hello
2、Makefile变量操作
变量种类
变量的种类
- 自定义变量:和shell编程一样,默认字符串类型
命名规则:- 命名规则和c语言的命名规则一致
- 大小写敏感
- 不使用#
A = hello
B = hello world
C = $(B) lisi # 将B和其他的字符串连接起来,
all:
echo $A
echo $B
echo $c
注意:
- 引用变量的时候,和shell编程一样,加上符号$
如果访问不成功,那么给变量加上括号 - makefile不是shell脚本,所以等号两边有空格也可以
- 所有的变量类型都是字符串,所以使用的时候不需要的加 " "
例题:将所有的依赖文件全部存储定义到变量里面
C_SOURCE = main1.c add.c
main: $ (C_SOURCE)
gcc $^ -o $@
clean :
rm main
- 系统变量环境
有些变量是系统预设,可以直接使用常用:
CC :编译器的名字默认cc等于gcc
CC = arm-linux-gcc
RM:册除文件相当于rm -f
C_SOURCE = main1.c add.c
main : $ (C_SOURCE)
$ (CC) $^ -o $@
clean :
$ (RM) main
- 自动化变量
$^
代表所有的依赖文件
$@
代表所有目标文件
C_SOURCE = mainl.c add.c 依赖文件
_BIN = main 执行文件目标
C_CLEAN = clean 删除文件目录
$ (C_BIN):$ (c_sOURCE)
$(CC) $^ -o $@
$ (C_CLEAN):
$(RM) $(C_BIN)
伪指令
当使用(RM),使用make clean出现错误make : 'clean' is up to date.
需要makefile加上一个伪指令
伪指令:.PHONY:clean
C_SOURCE = main1.c add.c 依赖文件
C_BIN = main 执行文件目标
CCLEAN = clean 删除文件目录
$ (C_BIN):$ (C_SOURCE)
$ (CC)$^ -o $@
.PHONY:$(c_CLEAN)
$ (c_CLEAN):
$(RM) $(C_BIN)
3、Makefile复杂文件
wildcard 与 patsubst
$(wildcard)
作用:在某一个路径下寻找对应匹配文件
用法:$( wildcard argl,arg2 , arg3 )
例如:寻找当前文件下的所有*.c
C_SOUCRE = $(wildcard *.c)
C_BIN = main
C_CLEAN = clean
$(C_BIN):$(C_SOUCRE)
$(CC) $^ -o $@
$(c_CLEAN):
$(RM) $(C_BIN)
$patsubst
作用:将所有的.c文件转化为.o文件
用法:$(patsubst %c,%o,依赖文件)
C_SOUCRE = $( wildcard * .c)
C_OBJ = $(patsubst %c,%o,$ (c_soUCRE))
C_BIN = main
C_CLEAN =clean
$(C_BIN):$(c_OBJ)
$(CC) $^ -o $@ -wall
多目录工程
序号 | 目录 | 名称 | 放置文件 |
---|---|---|---|
1 | 源文件目录 | src | 放置 *.c文件 |
2 | 头文件目录 | include | 放置 *.h头文件 |
3 | 库文件目录 | lib | 放置 *.so文件 |
4 | 可执行文件 | bin | 最终生成的二进制 |
连接:
参数 | 连接类型 |
---|---|
-I | 工头文件连接(大写的i) |
-L | 库文件目录连接 |
-l | 库文件访问(小写的L) |
头文件,放在include文件夹里
头文件,放在include文件夹里
#ifndef _SQ_H
#define _SQ_H
#include <stdio.h>
#include <math. h>
void sqrts ();
#endif
源文件,放在src文件夹里
源文件,放在src文件夹里
#include "sq.h"
void sqrts ()
{
double num;
double a=rand () 8100;
printf ( "a %lf\n " ,a) ;
num=sqrt (a) ;
printf ( "num 8lf\n " ,num) ;
}
主函数源文件,放在src文件夹里
#include "sq.h"
int main (int argc , char **argv)
{
sqrts () ;
}
当我们gcc调用的时候,会出现头文件出错的问题,需要连接一下头文件用-I
让src文件里的源文件和include文件里的头文件连接
gcc ./src/*.c -o main -I ./include/
写成Makefile
c_SRC = $( wildcard ./src/*.c)
C_OBJ = $(patsubst %c,%o,$ (c_SRC))
I_DIR = -L ./include
L_DIR =-L ./lib -lm
CBIN = ./bin/main
C_CLEAN = clean
$(C_BIN):$(C_SRC)
$(cc) $^ -o $@ $(I_DIR) $(L_DIR)
.PHONY:$(C_CLEAN)
$(C_CLEAN):
$(RM) $(C_BIN)
4、Makefile库的制作
-
什么是库?
库在linux环境中以二进制的形式存储,在编译的需要链接库 -
库的种类与格式
静态库:lib* .a(开头要有lib)和 libada.a
动态库:lib*.so 和 libadd.so
两种库的特点
静态库:lib**.a
特点:
1. 静态库就是**编译的时候需要静态连接**,直接将库里面的函数加入到你的可执行程序
2. 由于是静态连接,所以在**编译之后不需要静态库即可使用**
相当于到图书馆里借书,把书带走了
动态库:lib**.so
特点:
1. 程序**编译的时候**并没有将库的内容编译,**只是进行了连接**
2. 由于是动态链接,所以在**执行文件**的时候**需要动态库的支持**
相当于到图书馆里看书
静态库制作的步骤
主函数测试数据:main.c
功能函数实现:add.c add.h
将功能函数编译为库文件(二进制文件)
- 将功能文件*.c编译成* .o文件
gcc add.c -o add.o -c
- 将*.o文件塞入.a文件之中,文件名开头需要添加lib
ar rcs libadd.a add.o
- 联合编译
gcc main.c libadd.a -o main
- 测试
./ main
动态库制作的步骤
主函数测试数据:main. c
功能函数实现:add.c add.h
将功能函数编译为库文件(二进制文件)
- 将功能函数源文件编译为*.o文件
gcc add.c -o add.o -fPIC -c
- 将*.o文件塞入.a文件之中,文件名开头需要添加lib
gcc -shared -fPIC -o libadd.so add.o
- 联合编译需要链接动态库
gcc main.c -o main -ladd
4.测试
./main
当./ main是出现错误: error while loading shared libraries :libadd.so: cannot open shared object file :No such file or directory
解决方案:将对应的动态库拷贝到linux系统中的lib文件中sudo cp libadd.so /lib