Tips:"分享是快乐的源泉💧,在我的博客里,不仅有知识的海洋🌊,还有满满的正能量加持💪,快来和我一起分享这份快乐吧😊!
喜欢我的博客的话,记得点个红心❤️和小关小注哦!您的支持是我创作的动力!数据源
存放在我的资源下载区啦!
Linux程序开发(三):MakeFile编程及Githup项目编码
目录
- Linux程序开发(三):MakeFile编程及Githup项目编码
- 1. 问答题
- 1.1. C语言程序test.c如下,编译并执行该程序。
- 1.2. 编译以下程序并运行
- 1.3. 编译以下程序
- 1.4. 以下有两个文件:
- 1.5. 以下源文件gdb3.c编译成执行文件gdb3,请在不直接运行的情况下,使用gdb调试,确认执行结果。将调试过程写下来,调试过程截图。
- 1.6. github上的一个[项目](https://github.com/literaryno4/Makefile_tutorial)源代码目录如下:
- 1.7. 分析静态链接库与动态链接库的优缺点
- 2. 编程题
- 2.1. 编写程序实现`选择排序`、 `冒泡排序`、 `快速排序`
- 2.2. 编写Makefile文件实现上一题自动编译,要求可以用make命令实现:
- 2.3. 小熊有很多苹果,每一天小熊会数出自己的苹果个数 n。如果 n 是偶数,小熊就会吃掉 n/2个苹果,如果 n 是奇数,小熊就会吃掉 (n+1)/2 个苹果。现在小熊吃了 k 天,还剩下最后一个苹果,现在小熊想知道 k 天前一共有多少苹果。当然,可能性不止一种,所以请你编写代码帮小熊计算出 k 天前他的苹果数量有多少种可能?
1. 问答题
1.1. C语言程序test.c如下,编译并执行该程序。
#include <stdio.h>
int main(int argc, char *argv[])
{
int i;
printf("Number of args:%d, args are:\n", argc); // 打印命令行参数的数量
for(int i=0;i<argc;i++)
printf("args[%d] %s\n", i, argv[i]); // 打印每个命令行参数的索引和内容
fprintf(stderr, "This message is sent to stderr.\n"); // 将一条消息输出到标准错误流stderr中
return 0;
}
请问,先后执行./test abc 234 2> a.txt
和./test abc 234 >> a.txt
命令后,文件a.txt中的内容是什么?
屏幕上输出什么?
(1)在linux上运用gcc编译器来编译test.c
gcc编译器编译:gcc test.c -o test -std=c99
# C99是C语言的一个版本,它为循环的初始化部分引入了在 for 循环中声明变量的能力。然而,旧版本的 C 语言标准不允许这样做。GCC 编译器默认按照旧版本的 C 语言标准进行编译,因此需要使用 -std=c99 或 -std=gnu99 选项来告诉编译器使用 C99 标准编译代码。
(2)执行./test abc 234 2> a.txt,观察屏幕和a.txt文件==(标准错误流)==
# 在命令行中,> 符号用于将命令的输出重定向到文件。而 2> 表示将标准错误重定向到文件。
# 输入 ./test abc 234 2> a.txt 时:
# ./test abc 234 是要执行的命令,abc 和 234 是传递给该命令的参数。
# 2> 表示将标准错误重定向。
# a.txt 是要重定向到的文件。
# 因此,命令 ./test abc 234 的标准输出被打印到屏幕上,而标准错误被重定向到了名为 a.txt 的文件。
# 屏幕上输出了:
args[0] ./test
args[1] abc
args[2] 234
# a.txt 文件里面内容是:
This message is sent to stderr
(3)执行./test abc 234 >> a.txt,观察屏幕和a.txt文件==(标准输出流)==
# 屏幕上输出了:
This message is sent to stderr.
# a.txt文件里面的内容是:
This message is sent to stderr.
Number of args:3, args are:
args[0] ./test
args[1] abc
args[2] 234
# 这个结果是因为使用了 >> 操作符来将命令的输出追加到文件 a.txt 中,而没有覆盖原有的内容。>> 操作符会将输出数据追加到文件末尾,而不是覆盖之前的数据。
# 运行命令 ./test abc 234 >> a.txt 时,./test 命令的标准输出被追加到了文件 a.txt 的末尾,而标准错误则仍然被发送到屏幕上。因此,在终端上会看到一行 This message is sent to stderr. 的输出。
# 同时,./test 程序在输出参数时,将一些信息发送到了标准错误流(stderr),而不是标准输出流(stdout)。这些数据仍然被发送到了终端上,并没有被重定向到文件中。
# 最后,使用 cat a.txt 命令查看文件内容时,会发现文件中已经存在了一行 This message is sent to stderr. 的文本。这是因为该行数据是之前执行命令 ./test abc 234 >> a.txt 时输出的,已经被追加到了文件末尾。而接下来显示的是 ./test 命令的标准输出数据,即程序输出的参数信息。
1.2. 编译以下程序并运行
// gdb1.c
#include <stdio.h>
unsigned short int *p; // 声明一个名为 p 的 unsigned short int 型指针变量
void hello(void)
{
*p = (unsigned short int)"Hello GDUFE";
// 将字符串 "Hello GDUFE" 的地址转换为 unsigned short int 类型并赋值给 p 指针所指向的内存
}
int main(void)
{
hello(); // 调用 hello() 函数
return 0; // 返回 0,表示程序正常结束
}
将上面程序编译为gdb1程序,编译时有警告,运行结果出错,请采用gdb调试器找出原因,写明过程,必要时截图。
(1)用gcc编译器编译程序,出现报错现象
gcc gdb1.c -o gdb1 -std=c99
# 在代码中,将字符串的地址转换为 unsigned short int 类型,并将其赋值给 *p。然而,这样的类型转换是不正确的,因为指针和整数的大小不同。编译器给出的警告信息是在编译过程中发现的问题,它提醒可能存在类型不匹配的错误。
(2)启动gdb调试器
gdb gdb1
(3)在 gdb
中运行程序,并设置断点在 hello
函数内部
break hello
run
(4)当程序停在断点处时,可以使用 print
命令打印变量的值
print p
(5)通过以上步骤,你会发现 p
的值是一个随机的地址,因为它未被初始化。
# 修复这个问题的方法是使用合适的指针类型,并且为指针分配合适的内存空间,以下是修改后的代码
// gdb2.c
#include <stdio.h>
#include <stdlib.h>
char* p;
void hello(void)
{
p = "Hello GDUFE";
}
int main(void)
{
hello();
printf("%s\n", p);
return 0;
}
# 这样修改后,再使用 gcc 编译器运行代码,会发现程序正常运行而不再出现警告信息。
1.3. 编译以下程序
// gdb2.c
// gdb2.c
#include <stdio.h>
#define MAX 1000 // 定义 MAX 常量为 1000,表示计算的上限
int main(int argc, char *argv[])
{
int max = MAX; // 定义并初始化变量 max 为 MAX 常量的值
int sum = 0; // 定义并初始化变量 sum 为 0
int i = 1; // 定义并初始化变量 i 为 1
while(i <= max) // 循环条件:i 不超过 max
sum += i++; // 累加 i 的值,并将 i 自增 1
printf("sum=%d\n", sum); // 输出 sum 的值
return 0;
}
以上程序如果想在执行过程中改变,实现sum等于从1加到999,然后再加10000。应该如何做?
提示:采用gdb调试,实现,当变量i==999时,设置变量i的值为10000,也就是实现从1加到999,然后再加10000。
写出过程,必要时截图。
(1)使用gcc编译器编译程序
gcc -g gdb2.c -o gdb2 // 编译源代码,并生成可执行文件 gdb2
(2) 启动 gdb
并加载可执行文件
gdb gdb2
(3)设置断点在循环结束后,即 while
循环条件为假时
break gdb2.c:12 // 设置断点在第 12 行
(4)运行程序并开始调试
run
(5)当程序停在断点处时,修改变量 i
的值为 10000,使其继续执行循环
set variable i = 10000 // 修改变量 i 的值为 10000
continue // 继续执行程序
(6)编译程序,输出结果
./gdb2
在编译并运行该程序后,最终的输出结果应为 sum=5005000。
这是因为程序通过 while 循环将变量 i 从1累加到 max(即1000),并将每次累加的结果累加到变量 sum 中。循环结束后,变量 sum 的值即为从1到1000的累加和。
在程序的最后一行使用 printf 函数将结果输出到标准输出流(通常是控制台)。因此,当运行这个程序时,它会打印出 sum=5005000。
1.4. 以下有两个文件:
- myhead.h 在/home/linux/目录下
- myapp.c 在/home/linx/myapp/目录下
在不改变源文件位置,以及不改变源文件内容的情况下,请编译以下程序为myapp执行文件:
// myhead.h
#ifndef _MYHEDA_H
#define _MYHEAD_H
#include <stdio.h>
#include <math.h>
#endif
// myapp.c
#include "myhead.h"
void main()
{
double angle = 43.78;
printf("sin(43.78)=%lf, cos(43.78)=%lf\n", sin(angle), cos(angle));
}
(1)准备文件资料
(2)使用gcc编译器编译程序
命令行:gcc -I /home/linux/ -o myapp /home/linux/myapp.c -lm
添加了 -lm 选项来链接数学库。数学库是使用 -lm 进行链接的,以提供对数学函数 cos 和 sin 的支持。
(3)运行代码,得到结果
./myapp
1.5. 以下源文件gdb3.c编译成执行文件gdb3,请在不直接运行的情况下,使用gdb调试,确认执行结果。将调试过程写下来,调试过程截图。
选做
// gdb3.c
#include <stdio.h>
int main()
{
union{
int x[2];
long y;
char z[4];
float l;
} t;
t.x[0] = 0x41;
t.x[1] = 0x42;
printf("%lx\n",t.y);
printf("%c\n",t.z[0]);
printf("%f\n",t.l);
return 0;
}
(1)需要使用 -g
选项来在编译时加入调试信息
gcc -g -o gdb3 gdb3.c
(2)启动 GDB 并加载 gdb3
可执行文件
gdb gdb3
(3)在main处设置断点,并开始执行程序,程序会暂停在第一条语句前
break main
run
(4)使用以下命令逐语句地执行程序,并查看变量的值
next
print t.y
print t.z[0]
print t.l
1.6. github上的一个项目源代码目录如下:
├── include # 本文件下包含构建目标文件所需的头文件
│ ├── become_daemon.h
│ ├── error_functions.h
│ ├── get_num.h
│ ├── inet_sockets.h
│ └── tlpi_hdr.h
├── lib # 本文件夹下包含构建目标文件所需的库文件和依赖文件
│ ├── become_daemon.c
│ ├── ename.c.inc
│ ├── error_functions.c
│ ├── get_num.c
│ └── inet_sockets.c
└── src # 本文件夹包含项目的源文件、Makefile、目标文件以及可执行文件
├── obj
│ ├── become_daemon.o
│ ├── client.o
│ ├── error_functions.o
│ ├── get_num.o
│ ├── inet_sockets.o
│ └── server.o
├── Makefile
├── client
├── client.c
├── server
└── server.c
该源代码目录编译方法:
- 生成的目标文件:
gcc -c -o obj/error_functions.o ../lib/error_functions.c
gcc -c -o obj/get_num.o ../lib/get_num.c
gcc -c -o obj/inet_sockets.o ../lib/inet_sockets.c
gcc -c -o obj/become_daemon.o ../lib/become_daemon.c
gcc -c -o obj/client.o client.c
gcc -c -o obj/server.o server.c
- 链接目标文件生成可执行文件:
gcc -o client obj/client.o obj/error_functions.o obj/get_num.o obj/inet_sockets.o obj/become_daemon.o
gcc -o server obj/server.o obj/error_functions.o obj/get_num.o obj/inet_sockets.o
请编写Makefile实现编译要求。
选做
# 编译器和编译选项
CC := gcc
CFLAGS := -Wall -Werror
# 源文件和目标文件路径
SRC_DIR := src
OBJ_DIR := $(SRC_DIR)/obj
LIB_DIR := lib
# 需要生成的目标文件
OBJS := $(OBJ_DIR)/client.o \
$(OBJ_DIR)/server.o \
$(OBJ_DIR)/error_functions.o \
$(OBJ_DIR)/get_num.o \
$(OBJ_DIR)/inet_sockets.o \
$(OBJ_DIR)/become_daemon.o
# 编译可执行文件
all: client server
client: $(OBJ_DIR)/client.o $(OBJS)
$(CC) $(CFLAGS) -o $@ $^
server: $(OBJ_DIR)/server.o $(OBJS)
$(CC) $(CFLAGS) -o $@ $^
# 编译目标文件
$(OBJ_DIR)/%.o: $(SRC_DIR)/%.c | $(OBJ_DIR)
$(CC) $(CFLAGS) -c -o $@ $<
# 创建目标文件夹
$(OBJ_DIR):
mkdir -p $@
# 清理生成的目标文件和可执行文件
clean:
rm -rf $(OBJ_DIR) client server
.PHONY: all clean
注意:将上面的代码保存为 Makefile,然后在命令行中进入源代码目录,并执行 make 命令即可编译生成可执行文件 client 和 server。执行 make clean 可以清理生成的目标文件和可执行文件。
1.7. 分析静态链接库与动态链接库的优缺点
静态链接库和动态链接库是两种常见的库文件类型。它们各具有优缺点,下面分别进行分析:
一、静态链接库:静态链接库生成的是一个目标文件(以 .a 为扩展名),它包含了链接时需要用到的一些代码和数据。这意味着在使用静态链接库的程序被编译成可执行文件时,库中的所有代码和数据都会被复制到可执行文件中,称为静态链接。
1.静态链接库的优点包括:
(1)执行速度快:静态链接库的代码和数据在编译时就被全部加载到可执行文件中,相比动态链接库需要运行时再加载,因此程序执行时更快。
(2)稳定性高:因为静态链接库的代码和数据已经被复制到可执行文件中,程序执行时不受外部库文件版本、路径等的影响,因此更加稳定。
2.静态链接库的缺点包括:
(1)可执行文件较大:静态链接库的所有代码和数据都被复制到可执行文件中,因此可执行文件体积较大,如果程序依赖多个静态链接库,体积会更大。
(2)需要重新编译:如果要更新静态链接库,需要重新编译整个程序才能生效,这会增加工作量。
二、动态链接库:动态链接库生成的是一个共享库文件(以 .so 为扩展名),它包含了需要在运行时才能确定的一些代码和数据。在使用动态链接库的程序被编译成可执行文件时,只会在可执行文件中保留对动态链接库的引用,称为动态链接。
1.动态链接库的优点包括:
(1)可执行文件较小:动态链接库只在运行时加载,因此可执行文件只需要保留对动态链接库的引用即可,不会增加可执行文件的体积。
(2)更新方便:如果要更新动态链接库,只需要替换库文件即可,不需要重新编译整个程序。
2.动态链接库的缺点包括:
(1)执行速度稍慢:动态链接库需要在运行时加载,因此程序执行时速度稍慢。
(2)稳定性略低:因为动态链接库的代码和数据没有被复制到可执行文件中,程序执行时需要依赖外部库文件,如果外部库文件版本、路径等不正确,可能会导致程序出错。
2. 编程题
2.1. 编写程序实现选择排序
、 冒泡排序
、 快速排序
要求:
键盘输入20个数值进行排序;
然后再输入0、1、2中的一个数值,分别表示采用选择排序
、 冒泡排序
还是快速排序
来排序;
输出从小到大排序结果。
选择排序
、 冒泡排序
、 快速排序
采用三个不同源文件和头文件编写,编译时先将这些源文件编译成libmysort.a
静态库,然后再编译主程序。
(1)select_sort.c(选择排序)
#include "mysort.h"
void selectSort(int arr[], int n) {
int i, j, minIndex, temp;
for (i = 0; i < n - 1; i++) {
minIndex = i;
for (j = i + 1; j < n; j++) {
if (arr[j] < arr[minIndex]) {
minIndex = j;
}
}
if (minIndex != i) {
temp = arr[i];
arr[i] = arr[minIndex];
arr[minIndex] = temp;
}
}
}
(2)bubble_sort.c(冒泡排序)
#include "mysort.h"
void bubbleSort(int arr[], int n) {
int i, j, temp;
for (i = 0; i < n - 1; i++) {
for (j = 0; j < n - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
(3)quick_sort.c(快速排序)
#include "mysort.h"
void quickSort(int arr[], int low, int high) {
int i, j, pivot, temp;
if (low < high) {
pivot = low;
i = low;
j = high;
while (i < j) {
while (arr[i] <= arr[pivot] && i < high) {
i++;
}
while (arr[j] > arr[pivot]) {
j--;
}
if (i < j) {
temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
temp = arr[pivot];
arr[pivot] = arr[j];
arr[j] = temp;
quickSort(arr, low, j - 1);
quickSort(arr, j + 1, high);
}
}
(4)mysort.h(头文件)
#ifndef MYSORT_H
#define MYSORT_H
void selectSort(int arr[], int n);
void bubbleSort(int arr[], int n);
void quickSort(int arr[], int low, int high);
#endif
(5)main.c(主程序)
#include <stdio.h>
#include "mysort.h"
int main() {
int arr[20];
int i, option;
printf("请输入20个数值:\n");
for (i = 0; i < 20; i++) {
scanf("%d", &arr[i]);
}
printf("请选择排序算法(0:选择排序,1:冒泡排序,2:快速排序):\n");
scanf("%d", &option);
switch(option) {
case 0:
selectSort(arr, 20);
printf("选择排序结果:");
break;
case 1:
bubbleSort(arr, 20);
printf("冒泡排序结果:");
break;
case 2:
quickSort(arr, 0, 19);
printf("快速排序结果:");
break;
default:
printf("无效的选项\n");
return 0;
}
for (i = 0; i < 20; i++) {
printf("%d ", arr[i]);
}
printf("\n");
return 0;
}
(6)编译静态库文件
# 这条命令将 select_sort.c 文件编译为目标文件 select_sort.o。-c 选项告诉编译器只进行编译而不进行链接。
gcc -c select_sort.c
# 这条命令将 bubble_sort.c 文件编译为目标文件 bubble_sort.o,同样使用了 -c 选项。
gcc -c bubble_sort.c
# 这条命令将 quick_sort.c 文件编译为目标文件 quick_sort.o,同样使用了 -c 选项。
gcc -c quick_sort.c
# 这条命令使用 ar 工具将目标文件 select_sort.o、bubble_sort.o 和 quick_sort.o 打包成一个静态库文件 libmysort.a。
# ar 是一个用于创建、修改和提取归档文件(静态库)的工具。
# rcs 是 ar 命令的选项,其中 r 表示插入(替换)文件到归档文件中,c 表示创建归档文件,s 表示创建索引。
ar rcs libmysort.a select_sort.o bubble_sort.o quick_sort.o
(7)编译主程序
# 这条命令是一个编译和链接命令,用于将 main.c 文件与前面生成的静态库文件 libmysort.a 进行链接,并生成可执行文件 sort。
# 下面是该命令中各个部分的含义:
# gcc main.c:这部分表示将 main.c 文件作为输入,进行编译。
# -o sort:这部分表示将生成的可执行文件命名为 sort,即输出文件名为 sort。
# -L.:这部分表示在当前目录下搜索链接时需要的库文件。-L 选项告诉链接器去指定的路径寻找库文件,. 表示当前目录。
# -lmysort:这部分表示要链接的库文件的名称。-l 选项告诉链接器链接指定的库文件,mysort 是库文件的名称(去掉 lib 前缀和 .a 后缀)。
#综合起来,这条命令的作用是将 main.c 编译为目标文件,然后与 libmysort.a 进行链接生成可执行文件 sort。链接过程中会使用 mysort 库中定义的函数和变量。最终生成的可执行文件 sort 可以运行并执行程序中的代码。
gcc main.c -o sort -L. -lmysort
(8)运行可执行文件
./sort
2.2. 编写Makefile文件实现上一题自动编译,要求可以用make命令实现:
直接编译成执行文件
(1)编写Makefile文件
# 编译器和编译选项
CC = gcc
CFLAGS = -Wall -Wextra
# 静态库和动态库名称
LIB_NAME = mysort
STATIC_LIB = lib$(LIB_NAME).a
DYNAMIC_LIB = lib$(LIB_NAME).so
# 源文件和目标文件
SRCS = main.c select_sort.c bubble_sort.c quick_sort.c
OBJS = $(SRCS:.c=.o)
# 默认目标:编译可执行文件
all: sort sort_static sort_dynamic
# 目标1:编译静态链接库库和可执行文件
sort_static: $(OBJS) $(STATIC_LIB)
$(CC) $(CFLAGS) -o $@ $(OBJS) -L. -l$(LIB_NAME)
$(STATIC_LIB): select_sort.o bubble_sort.o quick_sort.o
ar rcs $@ $^
# 目标2:编译动态链接库库和可执行文件
CFLAGS_DYNAMIC = $(CFLAGS) -fPIC
sort_dynamic: $(OBJS) $(DYNAMIC_LIB)
$(CC) $(CFLAGS) -o $@ $(OBJS) -L. -l$(LIB_NAME)
$(DYNAMIC_LIB): select_sort.o bubble_sort.o quick_sort.o
$(CC) $(CFLAGS_DYNAMIC) -shared -o $@ $^
# 目标3:编译可执行文件
sort: main.o $(OBJS)
$(CC) $(CFLAGS) -o $@ $^
# 生成main.o文件
main.o: main.c mysort.h
$(CC) $(CFLAGS) -c $<
# 目标4:清理中间文件和可执行文件
clean:
rm -f $(OBJS) main.o $(STATIC_LIB) $(DYNAMIC_LIB) sort sort_static sort_dynamic
# 目标5:一次性编译三种可执行文件
all-in-one: all
(2)直接编译为执行文件
make sort
采用静态链接库编译成执行文件
,
(1)先删除后缀名为.o的文件以及可执行文件
rm -rf bubble_sort.o main.o quick_sort.o select_sort.o sort
(2)采用静态链接库编译成执行文件
make sort_static
采用动态链接库编译成执行文件
,
(1)先删除后缀名为.o和.a的文件,以及可执行文件
rm -rf bubble_sort.o main.o quick_sort.o select_sort.o sort_static libmysort.a
(2)采用动态链接库编译成执行文件
make sort_dynamic
清理现场,恢复源代码初始状态
,
make clean
一次性编译成三种执行文件
。
make all-in-one
2.3. 小熊有很多苹果,每一天小熊会数出自己的苹果个数 n。如果 n 是偶数,小熊就会吃掉 n/2个苹果,如果 n 是奇数,小熊就会吃掉 (n+1)/2 个苹果。现在小熊吃了 k 天,还剩下最后一个苹果,现在小熊想知道 k 天前一共有多少苹果。当然,可能性不止一种,所以请你编写代码帮小熊计算出 k 天前他的苹果数量有多少种可能?
输入输出样例1:
输入:1
输出:2
输入输出样例2:
输入:5
输出:32
代码样式:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int countApple(int k) {
if (k == 0) {
return 1;
}
int prevCount = countApple(k - 1);
return prevCount * 2;
}
int main() {
int k;
printf("请输入小熊吃了几天的苹果:");
scanf("%d", &k);
int result = countApple(k);
printf("小熊在%d天前可能拥有的苹果数量的种类数为:%d\n", k, result);
return 0;
}
结果显示1:
结果显示2: