2021-2022-diocs-Linux C语言编程基础(必做)

20191218 2021-2022-diocs-Linux C语言编程基础(必做)

一、任务详情

  1. 基于Ubuntu或OpenEuler完成下面的任务(OpenEuler有加分)
  2. 选择教材第二章的一节进行编程基础练习(2.10,2.11,2.12,2.13,2.14任选一个)
  3. 建立自己的项目目录,包含自己学号信息(如20190100linkedlist),构建项目结构(src, include, bin, lib, docs, test...),然后把相应代码和文档放置到正确位置,用tree命令查看项目结构,提交截图(5分)
  4. 进行gcc相关练习(ESc, iso, -I等)提交相关截图(5分)
  5. 进行静态库,动态库制作和调用练习,提交相关截图(5分)
  6. 进行gdb相关练习,至少包含四种断点的设置,提交相关截图(10分)
  7. 编写makefile(5分)

二、实践过程

所有实践内容均在OpenEuler下完成,相应代码已上传至码云:第三周代码

  1. 习题2.11
    实现代码
#include <stdio.h>
#include <stdlib.h>
#define TElemType int
//初始化队头和队尾指针开始时都为0
int front=0,rear=0;

typedef struct BiTNode{
    TElemType data;//数据域
    struct BiTNode *lchild,*rchild;//左右孩子指针
}BiTNode,*BiTree;
void CreateBiTree(BiTree *T){
    *T=(BiTNode*)malloc(sizeof(BiTNode));
    (*T)->data=1;
    (*T)->lchild=(BiTNode*)malloc(sizeof(BiTNode));
    (*T)->rchild=(BiTNode*)malloc(sizeof(BiTNode));
   
    (*T)->lchild->data=2;
    (*T)->lchild->lchild=(BiTNode*)malloc(sizeof(BiTNode));
    (*T)->lchild->rchild=(BiTNode*)malloc(sizeof(BiTNode));
    (*T)->lchild->rchild->data=NULL;
    (*T)->lchild->rchild->lchild=NULL;
    (*T)->lchild->rchild->rchild=NULL;
   
    (*T)->rchild->data=3;
    (*T)->rchild->lchild=(BiTNode*)malloc(sizeof(BiTNode));
    (*T)->rchild->lchild->data=6;
    (*T)->rchild->lchild->lchild=NULL;
    (*T)->rchild->lchild->rchild=NULL;
   
    (*T)->rchild->rchild=(BiTNode*)malloc(sizeof(BiTNode));
    (*T)->rchild->rchild->data=7;
    (*T)->rchild->rchild->lchild=NULL;
    (*T)->rchild->rchild->rchild=NULL;
   
    (*T)->lchild->lchild->data=4;
    (*T)->lchild->lchild->lchild=NULL;
    (*T)->lchild->lchild->rchild=NULL;
}
//入队函数
void EnQueue(BiTree *a,BiTree node){
    a[rear++]=node;
}
//出队函数
BiTNode* DeQueue(BiTNode** a){
    return a[front++];
}
//输出函数
void displayNode(BiTree node){
    if (node->data == 0)
    	printf("- ");
	else printf("%d ",node->data);
}
int main() {
    BiTree tree;
    //初始化二叉树
    CreateBiTree(&tree);
    BiTNode * p;
    //采用顺序队列,初始化创建队列数组
    BiTree a[20];
    //根结点入队
    EnQueue(a, tree);
    //当队头和队尾相等时,表示队列为空
    while(front<rear) {
        //队头结点出队
        p=DeQueue(a);
        displayNode(p);
        //将队头结点的左右孩子依次入队
        if (p->lchild!=NULL) {
            EnQueue(a, p->lchild);
        }
        if (p->rchild!=NULL) {
            EnQueue(a, p->rchild);
        }
    }
    return 0;
}


其中,对教材上所提供的代码作出的主要修改在如下部分,添加了一个节点是否为空的判断

void displayNode(BiTree node){
    if (node->data == 0)
    	printf("- ");
	else printf("%d ",node->data);

在OpenEuler下运行结果,实现题目要求。

  1. gcc相关练习
    同时复习C语言文件操作,实现功能为文本文件和二进制文件的转换。
    分为三个文件,BinIO.c中存放main函数,BinIO_func.c中存放其余被调用函数,BinIO.h中存放所需头文件以及结构体定义。
    BinIO.c
#include "BinIO.h"
/**
 *代码实例是将文本文件写入二进制文件,然后从二进制文件中读取,再写入到
 *文本文件中去
 */
int main(int argc,char *argv[]){
    if(argc !=4 ){
        printf("缺少参数
");
        exit(EXIT_FAILURE);
    }
    text_to_bin(argv);
    bin_to_text(argv);
    return  0;
}

BinIO_func.c

#include "BinIO.h"
void text_to_bin();
void bin_to_text();

void text_to_bin(char *argv[]){
    FILE *source_file_pointer;
    FILE *des_file_pointer;
    Stu stu = {1,"demo",0,0,0};
    source_file_pointer = fopen(argv[1],"r");
    if(source_file_pointer == NULL){
        printf("open text source file failed
");
        exit(EXIT_FAILURE);
    }

    des_file_pointer = fopen(argv[2],"wb");
    if(des_file_pointer == NULL){
        printf("open bin source file failed
");
        exit(EXIT_FAILURE);
    }

    while(fscanf(source_file_pointer,"%d %s %d %d %d",&stu.xh,stu.name,&stu.math_score,&stu.english_score,&stu.chinese_score) != EOF ){
        fwrite(&stu,sizeof(stu),1,des_file_pointer);
    }
    int source_close_result = fclose(source_file_pointer);
    if(source_close_result == EOF){
        printf("close source file failed
");
        exit(EXIT_FAILURE);
    }else{
        printf("close source file success
");
    }

    int des_close_result = fclose(des_file_pointer);
    if(des_close_result == EOF){
        printf("close des file  failed
");
        exit(EXIT_FAILURE);
    }else{
        printf("close des file success
");
    }

}


void bin_to_text(char *argv[]){
    FILE *bin_source_file_pointer;
    FILE *text_des_file_pointer;
    Stu stu = {1,"z",0,0,0};
    bin_source_file_pointer = fopen(argv[2],"rb");
    if(bin_source_file_pointer == NULL){
        printf("open bin_source_file failed
");
        //perror(argv[2]);
        exit(EXIT_FAILURE);
    }else{
        printf("open bin_source_file success
");
    }

    text_des_file_pointer = fopen(argv[3],"w");
    if(text_des_file_pointer == NULL){
        printf("open text des file failed
");
        perror(argv[3]);
        exit(EXIT_FAILURE);
    }else{
        printf("open text_des file success
");
    }

    while(fread(&stu,sizeof(stu),1,bin_source_file_pointer)){
        fprintf(text_des_file_pointer,"%d %s %d %d %d
",stu.xh,stu.name,stu.math_score,stu.english_score,stu.chinese_score);
    }

    int bin_source_file_close_result = fclose(bin_source_file_pointer);
    if(bin_source_file_close_result == EOF){
        printf("close bin_source_close_file error
");
        exit(EXIT_FAILURE);
    }else{
        printf("close bin_source_close_file success
");
    }
    int text_des_file_close_result = fclose(text_des_file_pointer);
    if(text_des_file_close_result == EOF){
        printf("close text_des_file error
");
        exit(EXIT_FAILURE);
    }else{
        printf("close text_des_file success
");
    }
}

BinIO.h

#ifndef _BINIO_H_
#define _BINIO_H
 
#include<stdio.h>
#include<stdlib.h>

typedef struct student{
    int xh;
    char name[20];
    int math_score;
    int english_score;
    int chinese_score;
}Stu;

void text_to_bin();
void bin_to_text();

#endif

实践过程

在直接编译时一定注意每次都不能忘掉-Iinclude,以确定头文件所在位置

编译好后tree下的项目结构图

首次运行时发现文件内容并没有被改变,检查问题
在进行编程时,我本来是将这几部分合在一起的并且能够正常运行。为了练习gcc的使用(多文件链接)才将一个文件拆开,可是真正运行时发现程序根本没有办法终止,而是一直在往temp这个二进制文件中写入数据。我特意在较为熟悉的Windows下尝试运行,发现


程序一直在执行文本文件向二进制文件的写入操作,可以看到new1.txt文件甚至已经到了8GB!受时间关系此问题还没解决,在此做一个占位。
于是我又尝试了另一C程序的编译运行

用C语言大数的阶乘(数组实现)
实现代码

#include <stdio.h>

int a[10000];

int main()
{
	int n, digit = 1, temp, i, j, carry;
	scanf("%d", &n);
	a[0] = 1;
	for (i = 2; i <= n; i++)  
	{
		carry = 0;
		for (j = 1; j <= digit; j++)
		{
			temp = a[j-1] * i + carry; 
			a[j-1] = temp % 10; 
			carry = temp / 10; 
		}
		while (carry)
		{
			a[++digit - 1] = carry % 10;
			carry = carry / 10; 
		}
	}
	for (j = digit; j >= 1; j--)
		printf("%d", a[j-1]);

	return 0;
}

  1. 静态库、动态库相关练习
  • 制作静态库和动态库

    • 静态库

      注意在第一次制作静态库时加上-Iinclude链接头文件,生成.a静态库之后可以不需要再加,直接编译即可。
    • 动态库

      注意每次都要加-Iinclude。
  • myod(选做)项目中的练习
    tree下的结构图

    用静态库运行myod结果

    可以看到实现了od -tx -tc XXX的要求

  • 动态库

    上面这张截图中我忘记添加-o参数指定输出路径,在当前目录下生成了a.out,下图是修改后的。

    下图是在动态库下的运行结果

  1. gdb的练习

首先编译生成可执行文件(这里的test.c的功能是:从键盘读入一个整数,判断其是否是回文数和素数)。

gcc -g src/test.c src/test_fucnc.c -o resource/test
其中-g选项告诉gcc在编译程序时加入调试信息。

在其中遇到的问题

开始以为是由于调用了math.h库,而本目录和include目录中都不包含的这个,需要重新写头文件并放在include目录下

然而并不是这个问题,查阅资料发现在Linux系统下,C源文件若调用了math库里的函数,则编译时要加上-lm,表示链接到math库。

问题成功解决

接下来
使用gdb resource/test

可参考博客:https://blog.csdn.net/weixin_33881050/article/details/92279415
然后你就会看到屏幕出现许多信息,是一些关于gdb的版本信息说明之类内容的,但是它对调试程序没用,可以加上-q参数。

下面是一些常用的GDB调试命令

(gdb)help:查看命令帮助,具体命令查询在gdb中输入help + 命令,简写h

(gdb)run:重新开始运行文件(run-text:加载文本文件,run-bin:加载二进制文件),简写r

(gdb)start:单步执行,运行程序,停在第一执行语句

(gdb)list:查看原代码(list-n,从第n行开始查看代码。list+ 函数名:查看具体函数),简写l

(gdb)set:设置变量的值

(gdb)next:单步调试(逐过程,函数直接执行),简写n

(gdb)step:单步调试(逐语句:跳入自定义函数内部执行),简写s

(gdb)backtrace:查看函数的调用的栈帧和层级关系,简写bt

(gdb)frame:切换函数的栈帧,简写f

(gdb)info:查看函数内部局部变量的数值,简写i

(gdb)finish:结束当前函数,返回到函数调用点

(gdb)continue:继续运行,简写c

(gdb)print:打印值及地址,简写p

(gdb)quit:退出gdb,简写q

(gdb)break+num:在第num行设置断点,简写b

(gdb)info breakpoints:查看当前设置的所有断点

(gdb)delete breakpoints num:删除第num个断点,简写d

(gdb)display:追踪查看具体变量值

(gdb)undisplay:取消追踪观察变量

(gdb)watch:被设置观察点的变量发生修改时,打印显示

(gdb)i watch:显示观察点

(gdb)enable breakpoints:启用断点

(gdb)disable breakpoints:禁用断点

(gdb)x:查看内存x/20xw 显示20个单元,16进制,4字节每单元

(gdb)run argv[1] argv[2]:调试时命令行传参 

练习过程截图

  • 设置断点
    包括函数断点、临时断点、行断点、条件断点

  • 跟踪调试

    • 单步跟踪
    • 下一步调试
  • 综合实践
    所测试程序功能为:从键盘读入两个数,输出它们的和。



  1. Makefile的练习

    makefile在之前已经完成过,有关的练习见myod选做项目博客:20191218 2021-2022-diocs-MyOD
    注意gdb调试中参数n(next)和参数s(step)的区别,其中n是直接跳过一个函数,而s是进入函数一步。我们在调试时要优先使用n,再使用s,这样先大后小有助于缩小问题范围。
原文地址:https://www.cnblogs.com/20191218tangqiheng/p/15327422.html