Linux程序开发(三):MakeFile编程及Githup项目编码

Tips:"分享是快乐的源泉💧,在我的博客里,不仅有知识的海洋🌊,还有满满的正能量加持💪,快来和我一起分享这份快乐吧😊!

喜欢我的博客的话,记得点个红心❤️和小关小注哦!您的支持是我创作的动力!数据源存放在我的资源下载区啦!

Linux程序开发(三):MakeFile编程及Githup项目编码

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;    // 定义并初始化变量 sum0
        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

该源代码目录编译方法:

  1. 生成的目标文件:
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
  1. 链接目标文件生成可执行文件:
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:

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

  • 24
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

卡林神不是猫

如果您觉得有帮助可以鼓励小卡哦

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

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

打赏作者

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

抵扣说明:

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

余额充值