【Linux】实验二 Makefile 的编写及应用

静态和动态库的转换可以参考这篇文章哦!!

实验二 Makefile 的编写及应用

实验目的

  1. 掌握 GCC 编译与链接的基本使用方式、测试编译流程
  2. 了解 Makefile 的基本概念和基本结构.
  3. 初步掌握编写简单 Makefile 的方法.
  4. 了解递归 Make 的编译过程
  5. 初步掌握利用 GNU Make 编译应用程序的方法

实验内容

1、完成一个在字符界面下的小学数学教学软件。该软件主要实现计算机自动出题,使用者回答
问题,计算机判断对错,测试结束后给出成绩。程序的整体流程如图 3-1 所示。
2、首先用 vi 编写基本程序,
3、对代码进行编译,链接,并执行查看效果
4、编写 Makefile 实现自动化编译
在这里插入图片描述

具体步骤:

一、进入文件夹

切换到桌面(个人习惯)

cd ./Desktop/

生成单个文件夹

mkdir MathExam

切换到生成的文件中

cd ./MathExam/

二、生成各个.c .h文件

1. exam.h
vi exam.h
#ifndef EXAM_H
#define EXAM_H
#include "mat.h"
int GetExamNum(); //获取一次练习的题目数
void SetExamNum(int num); //设置一次练习的题目数
void PrintFormula(Formula *formula, int IsPrintAns); //显示算式
int ExamFormula(Formula *formula); //出题,并自动检查每题算式的答案是否正确
void ExamPaper(); //根据设置的题目数,调用 ExamFormula()并完成整份练习的得分统计
#endif
2. exam.c
vi exam.h
#include "exam.h"

int ExamPaNum = 5; //设置出题数 

//获取一次练习的题目数
int GetExamNum() 
{
	return ExamPaNum;
}

//设置一次练习的题目数
void SetExamNum(int num)
{
	 ExamPaNum= num;
}

//显示算式
void PrintFormula(Formula *formula, int IsPrintAns)
{
	int op1,op2,Optype;
	op1 = formula->Op1;
	op2 = formula->Op2;
	Optype = formula->OpType;
	printf("%d%c%d=%d\n",op1,Optype,op2,IsPrintAns);
}

int CorrectCnt=0;
//出题,并自动检查每题算式的答案是否正确
int ExamFormula(Formula *formula)
{
	int youAnswer,correctAnswer;
	correctAnswer = formula->Answer;
	scanf("%d",&youAnswer);
	if(correctAnswer== youAnswer)
	{
		CorrectCnt++;
		printf("回答正确!!\n");
	}
	else
	{
		printf("回答错误!!\n");
		printf("正确答案如下:");
		PrintFormula(formula,correctAnswer);
	}
}

//根据设置的题目数,调用 ExamFormula()并完成整份练习的得分统计
void ExamPaper()
{
	CorrectCnt = 0;//重新考试则把得分清空 
	int preDiff;
	preDiff = GetMaxNum();
	printf("当前题目数量为%d\n",ExamPaNum);
	printf("当前的题目的难度是%d.\n", preDiff);
	Formula formulator;
	Formula *formula = &formulator;
	int E_cnt;
	for(E_cnt=0; E_cnt < ExamPaNum; E_cnt++)
	{
		printf("第%d题为:",E_cnt+1);
		GetRandFormula(formula);
		SetAnswer(formula);
		ExamFormula(formula);
		printf("------------:\n");
	}
	printf("您的最终得分为:%d\n",CorrectCnt);

}
3. mat.h
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#ifndef MAT_H
#define MAT_H
//定义加/减/乘/除四种运算符
#define MT_ADD 0
#define MT_SUB 1
#define MT_MUL 2
#define MT_DIV 3
 
typedef struct _Formula 
{
	int Op1; //操作数 1
	int Op2; //操作数 2
	char OpType; //运算符:加/减/乘/除
	int Answer; //结果
} Formula;
int GetMaxNum();
void SetMaxNum(int max); //设置计算的最大数
int GetRandOp(); //随机获取 4 种运算符中的一种
char OpTypeToChar(int optype); //整型的运算符转为字符型运算符
void SetAnswer(Formula *formula);//记录算式的正确答案
void GetRandFormula(Formula *formula); //生成随机算式
#endif
4. mat.c
#include "mat.h"
int maxDifficulu=1;//设置初始难度为1 

//获取当前题目难度 
int GetMaxNum()
{
	return maxDifficulu;
}

//设置计算的最大数
void SetMaxNum(int max) //设置计算的最大数
{
	if(max>3)
	{
		printf("难度最大为3,以帮您设置为最大难度\n");
		maxDifficulu = 3;
	}
   maxDifficulu = max;
}

//随机获取 4 种运算符中的一种
int GetRandOp() 
{
	int optype=0;
	srand((unsigned)time(NULL));
	optype=rand()%4;	 //theop就是你要的随机运算符。
	return optype; 
}

//整型的运算符转为字符型运算符
char OpTypeToChar(int optype)
{
	char opType;
	char op[4] = {'+','-','*','/'};
	opType=op[optype];
	return opType;
}


//记录算式的正确答案
void SetAnswer(Formula *formula)
{
	int op1,op2;
	int result;
	char opType;

	op1 = formula->Op1;
	op2 = formula->Op2;
	opType = formula->OpType;
	
	if(opType == '+') 
	{
		result = op1+op2;
	}
	else if(opType == '-') 
	{
		result = op1-op2;
	}	
	else if(opType == '*') 
	{
		result = op1*op2;
	}
	else if(opType == '/') 
	{
		result = op1/op2;
	}
	formula->Answer = result;
}

//生成随机算式
void GetRandFormula(Formula *formula)
{
	char opType;
	int temp_op1,temp_op2;
	Formula temp_formula;
	
	srand((unsigned)time(NULL));
	//按照相应的难度出题 
	if(maxDifficulu == 1)
	{
		temp_op1 = rand()%10;
		temp_op2 = rand()%10+1;
	}
	if(maxDifficulu == 2)
	{
		temp_op1 = rand()%30;
		temp_op2 = rand()%30+1;
	}
	if(maxDifficulu == 3)
	{
		temp_op1 = rand()%50;
		temp_op2 = rand()%50+1;
	}
	
	opType=OpTypeToChar(GetRandOp());
	formula->OpType = opType;
	formula->Op1 = temp_op1;
	formula->Op2 = temp_op2;
	printf("%d%c%d=",formula->Op1,formula->OpType,formula->Op2);
}
5. main.c
#include "mat.h"
#include "exam.h"

int PrintMenu();
void ChangeDifficulty();
void ChangeScale();
int main() 
{
	int choice;
	printf(" * Maths Exam *\n");
	do {
		choice = PrintMenu();
		switch (choice) 
		{
			case 1:
				ExamPaper();
				break;
			case 2:
				ChangeDifficulty();
				break;
			case 3:
				ChangeScale();
				break;
			case 4:
				printf ("Goodbye!\n");
				break;
			default:
				printf("Wrong Choice!\n");
				break;
		}
	} while (choice != 4);
	return 0;
}

int PrintMenu() 
{
	int choice;
	printf("Main Menu:\n");
	printf(" 1) Exam; 2) Set Difficulty; 3) Set Scale; 4) Quit\n");
	printf("Choice: ");
	scanf("%d", &choice);
	return choice;
}

void ChangeDifficulty() 
{
	int preDiff,nexDiff;
	preDiff = GetMaxNum();
	printf("当前的题目的难度是%d.\n", preDiff);
	printf("---------------------\n");
	printf("= 本程序共有以下难度 =\n");
	printf("= 1:10以内的计算题  =\n");
	printf("= 2:30以内的计算题  =\n");
	printf("= 3:50以内的计算题  =\n");
	printf("---------------------\n");
	printf("请选择难度: ");
	scanf("%d", &nexDiff);
	SetMaxNum(nexDiff);
}

void ChangeScale() 
{
	int examnum;
	printf("当前的试题数量为 %d.\n", GetExamNum());
	printf("---------------------\n");
	printf("输入设置新的试题数量: ");
	scanf("%d", &examnum);
	SetExamNum(examnum);
}

三、编译建立的文件

1. 只编译不链接 main.o
gcc -c main.c

可以发现当前文件夹下多了一个 main.o 文件

2. 使用 file 查看 main.o 的格式,并尝试执行 main.o
file main.o

会打印出 log:“main.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped”
表明 main.o 实际上是一个 relocatable 文件。

修改 main.o 的文件属性(权限)为可执行:

chmod 777 main.o

再尝试执行 main.o 文件:

./main.o

此时是执行不了的

在这里插入图片描述

3. 对 main.o 进行链接,并尝试执行

那么怎样才能生成可执行文件呢? 可执行文件需要通过链接来生成。
使用 gcc 将 main.o 链接为 matexam 文件:

gcc –c mat.c exam.c
gcc mat.o exam.o main.o -o matexam

通过 ls 命令查看当前目录下是否生成源代码 mat.c exam.c main.c 所对应的 object 文件 mat.o ;
exam.o main.o 和可执行文件 matexam,运行可执行文件 matexam,并记录运行结果。

./matexam

在这里插入图片描述


四、将 mat.o exam.o 做成静态库,并静态链接执行

这一步可以再生成个文件夹,我生成了个 static

1. 重新编译 mat.c 和 exam.c 生成静态库文件
1.1 重新编译 mat.c 和 exam.c 文件
gcc –c mat.c exam.c main.c

在这里插入图片描述

1.2 将 mat.o 和 exam.o 打包到静态库中
ar rc libexam.a mat.o exam.o  # 生成 libexam.a 静态库文件

ar命令 是一个建立或修改备存文件,或是从备存文件中抽取文件的工具,ar可让您集合许多文件,成为单一的备存文件。在备存文件中,所有成员文件皆保有原来的属性与权限。
简单说就是 把后面的 2个.o文件 从文件夹中拿出来 放在.a文件里
[rc]是参数。
[r] - 替换归档文件中已有的文件或加入新文件;
[c] - 不在必须创建库的时候给出警告。

在这里插入图片描述

使用 file 查看 libexam.a:

file libexam.a

在这里插入图片描述

返回的结果说明:“libexam.a: current ar archive” 实际上 libxxx.a 只是将指定的.o 文件打包汇集在一起,它的本质上还是 Relocatable File集合。
小贴士:可重定位文件(Relocatable File) 与其他目标文件链接来创建可执行文件或者共享目标文件的代码和数据。

3. 链接 main.o 和静态库文件并执行
gcc -o main2 main.o -L./ -lexam 

在这里插入图片描述

说明 1: -L./ 表明库文件位置在当前文件夹
说明 2: -lexam 表示链接 libexam.a 文件,使用“-l”参数时,前缀“lib”和后缀“.a”是需要省略
的。

4. 执行
./main_static

在这里插入图片描述


五、将 mat.o exam.o 做成动态库,动静态链接执行

这一步可以再生成个文件夹,我生成了个 dynamic

1. 生成对应的目标文件, 适用于动态库编译
gcc -fPIC -c exam.c mat.c main.c

必须要加 -fPIC 参数,不然会有下面的报错

在这里插入图片描述

在这里插入图片描述

2. 将 mat.o 和 exam.o 打包到动态库中
gcc -shared -fpic -olibfunc.so mat.o exam.o 

动态库打包,-fPIC: 生成与位置无关的代码

在这里插入图片描述

3. 链接 main.o 和动态库文件并执行
gcc main.o -o test -lfunc -L. 

最终链接 -L 指出动态库路径 -o 指出动态库名称

在这里插入图片描述

4. 执行
./test

可能会遇到的错误:error while loading shared libraries: libxxx.so通用解决方法
在这里插入图片描述


六、利用 GNU make 自动编译应用程序方法

bilibili视频学习点击这里!!!!

1、基本规则的Makefile
(1)利用文本编辑器创建一个 Makefile 文件,并将其保存到与 main.c 相同的目录下。
# Makefile test for exam program
#written by Emdoor
matexam : mat.o exam.o main.o
	gcc mat.o exam.o main.o -o matexam
mat.o : mat.h mat.c
	gcc -c mat.c -o mat.o
exam.o : mat.h exam.h exam.c
	gcc -c exam.c -o exam.o
main.o : mat.h exam.h main.c
	gcc -c main.c -o main.o
(2)再执行如下命令
make
ls
./matexam

在这里插入图片描述

(3)请在 Makefile 文件中添加一个 clean 目标,实现删除.o 和 matexam 文件。并执行 make clean

在原本的Makefile的最后加入:

clean : 
	-rm -f *.o matexam

-f表示要删去的是 当前文件夹下所有的.o文件

在这里插入图片描述
再执行

make clean

在这里插入图片描述

(4)加入 CC、CFLAGS 以及 OBJS 变量,重新编辑 Makefile 文件

#Makefile test for hello program
#written by Emdoor
CC= gcc
CFLAGS=
OBJS=hello.o

mekefile中 输入以下代码(可以把上面这个也一起加进去)
在这里插入图片描述


2、使用自动变量的 Makefile [GNU make]

在这里插入图片描述
在这里插入图片描述

修改 Makefile 文件,在规则中使用$^ $@$<等自动变量,给出修改后的 Makefile 文件内容。重复第 (2)、(3) 步操作,查看并记录所生成的文件和运行的结果。
说明:
$@ 目标的完整名称 相当于是 要生成的可执行文件
$< 规则第一个依赖文件名
$^ 所有的依赖文件,以空格分开,不包含重复的依赖文件。
使用自动化变量可以不必写出目标文件名及依赖文件表列。尤其在生成多个目标文件生成一个可执行文件中优点更加突出。

下面的方框可以改写为上面的方框 这只是举个例子!!!!!
在这里插入图片描述

.PHONY : clean 是防止调用命令的时候,如果文件夹下面有名为clean的文件,会导致冲突的问题。

# Makefile test for hello program
#written by Emdoor
helloTest : hello.o
         gcc -o $@ $^
hello.o : hello.c
          gcc -c $<
 .PHONY : clean
clean : 
        -rm *.o helloTest                       

在这里插入图片描述


3、使用模式规则实现的 Makefile。

CFILES = $(wildcard *.c) 一般来获取工作目录下的所有的.c文件列表
OBJS = $(CFILES:%.c=%.o) CFILES变量中所有以.c结尾的文件名替换成对应的以.o结尾的文件名,然后赋回给CFILES,最终赋给OBJS

# Makefile test for hello program
#written by Emdoor
CC= gcc
CFILES = $(wildcard *.c)
OBJ =$(CFILES:%.c = %.o)
OBJS = hello
OBJS : $(OBJ)
        $(CC) $(OBJ) -o $(OBJS)
$(OBJ) : $(CFILES)
        $(CC) -c $< -o $@
.PHONY : clean
clean :
        @echo "remove file:"
        rm -f hello *.o

在这里插入图片描述


思考题三比较有搞头

用 make 管理相对复杂的项目时,项目中的源代码如果分布在多个子目录中,Makefile 文件
该如何组织?

参考这个博客!!!!!!!!!!!

每天进步一点点 笔记仅供自学,用来回看复习,不一定适合你,如有错误请指出。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

KevinGuo457

哈哈哈资助我买两包辣条叭

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值