【深入理解计算机系统csapp】C Programming Lab 实验1

概要

这个实验有几个点需要重新学一下,知识点还是模糊的:

  1. git仓库的使用和相应命令(基本操作还是看了就忘)
  2. Linux命令行指令(总是记总是忘,有必要日常操作换成终端)
  3. 数据结构的单链表的实现

《深入理解计算机系统》

底层数据结构

csapp-clab目录下文件queue.h定义的结构如下:

/* Linked list element */
typedef struct ELE {
   int value;
   struct ELE *next;
} list_ele_t;

/* Queue structure */
typedef struct {
   list_ele_t *head; /* Linked list of elements */
} queue_t;

如Figure 1所示,代码要实现一个队列。队列的顶层表示是类型为queue_t的结构,在最初代码中,这个结构只包含一个字段“head”,你需要自行添加其他字段。队列被表示为单链表,其中每个元素由结构list_ele_t表示,该结构包含字段“value”和“next”,分别存储队列值和一个指向下一个元素的指针。尽管可以在不更改list_ele_t的情况下实现本实验要求的所有函数,但欢迎你对list_ele_t进行更改,例如将单链表转换为双链表。

翻译成人话:

看代码可以知道现在队列结构只有一个头节点,题目让我们自己添加别的节点,并且现在的元素节点结构中包含value*next剩下的自己更改把他改成双链表(如果可以的话)

如图所示
在这里插入图片描述
在我们给你的C代码中,队列是一个类型为queue_t *的指针。
我们区分两种特殊情况:一个NULL队列是指针设置为NULL的队列。 而一个空(empty)队列是一个指向一个有效的queue_t结构的队列,该queue_t结构的head字段被设置为NULL。除了能正确处理包含一个或多个元素的队列,你的代码还要能正确处理这两种情况。
(保证程序的健壮性)

编程任务

你的任务是修改queue.h和queue.c中的代码以实现以下功能:

  1. q_new:创建一个新的空队列
  2. q_free: 释放队列使用的所有存储空间
  3. q_insert_head:在队列的头部插入新元素(LIFO)
  4. q_insert_tail:在队列的尾部插入新元素(FIFO)
  5. q_remove_head:从队列的头部删除一个元素
  6. q_size: 计算队列中元素的个数
  7. q_reverse:对列表重新排列,使得队列中元素的顺序都反转过来

就是完成增删改查和其他返回队列基本性质的操作

更多细节可以在这两个文件的注释中找到,包括如何处理无效操作(例如,从空队列或NULL队列中移除元素),以及函数应该具有哪些副作用和返回值。

以下是关于如何实现这些功能的一些重要注意事项:

  • 当q_new和q_insert_head需要动态分配内存时,使用malloccalloc来实现(如果不熟悉这两个函数,可以在命令行中输入man malloc或man calloc来查阅手册;或者搜索引擎之);
  • 其中q_insert_tail 和 q_size需要满足时间复杂度为O(1),即所需的时间与队列大小无关。你可以通过在queue_t数据结构中添加其他字段来实现此功能;
  • q_reverse的实现不需要分配额外的内存。你的代码应该修改现有链表中的指针。 在实现反向操作时,直接或间接调用malloc、calloc或free都视为错误;
  • 测试代码会在超过1,000,000个元素的队列上对你的程序进行测试,所以不能使用递归函数来遍历这样的长列表,因为这需要太多的栈空间。(后续课程中,我们将深入学习这背后的原理,cool吧?)

queue.h

/* 
 * Code for basic C skills diagnostic.
 * Developed for courses 15-213/18-213/15-513 by R. E. Bryant, 2017
 */

/*
 * This program implements a queue supporting both FIFO and LIFO
 * operations.
 *
 * It uses a singly-linked list to represent the set of queue elements
 */

#include <stdbool.h>

/************** Data structure declarations ****************/

/* Linked list element (You shouldn't need to change this) */
typedef struct ELE {
    int value;
    struct ELE *next;
} list_ele_t;

/* Queue structure */
typedef struct {
    list_ele_t *head;  /* Linked list of elements */
    list_ele_t *tail;
    unsigned size;
} queue_t;

/************** Operations on queue ************************/

/*
  Create empty queue.
  Return NULL if could not allocate space.
*/
queue_t *q_new();

/*
  Free all storage used by queue.
  No effect if q is NULL
*/
void q_free(queue_t *q);

/*
  Attempt to insert element at head of queue.
  Return true if successful.
  Return false if q is NULL or could not allocate space.
 */
bool q_insert_head(queue_t *q, int v);

/*
  Attempt to insert element at tail of queue.
  Return true if successful.
  Return false if q is NULL or could not allocate space.
 */
bool q_insert_tail(queue_t *q, int v);

/*
  Attempt to remove element from head of queue.
  Return true if successful.
  Return false if queue is NULL or empty.
  If vp non-NULL and element removed, store removed value at *vp.
  Any unused storage should be freed
*/
bool q_remove_head(queue_t *q, int *vp);

/*
  Return number of elements in queue.
  Return 0 if q is NULL or empty
 */
int q_size(queue_t *q);

/*
  Reverse elements in queue
  No effect if q is NULL or empty
 */
void q_reverse(queue_t *q);

头文件的作用:.h中一般放的是同名.c文件中定义的变量、数组、函数的声明,需要让.c外部使用的声明。

头文件作用详细总结

queue.c

/* 
 * Code for basic C skills diagnostic.
 * Developed for courses 15-213/18-213/15-513 by R. E. Bryant, 2017
 */

/*
 * This program implements a queue supporting both FIFO and LIFO
 * operations.
 *
 * It uses a singly-linked list to represent the set of queue elements
 */

#include <stdlib.h>
#include <stdio.h>

#include "harness.h"
#include "queue.h"

/*
  Create empty queue.
  Return NULL if could not allocate space.
*/
queue_t *q_new()
{
    queue_t *q = NULL;
    if((q = malloc(sizeof(queue_t))))
    {
      q->head = NULL;
      q->tail = NULL;
      q->size = 0;
      return q;
    }
    else
    {
      return NULL;
    }
}

/* Free all storage used by queue */
void q_free(queue_t *q)
{
    /* How about freeing the list elements? */
    /* Free queue structure */
    if (q)
    {
      list_ele_t *i = q->head;
      while(i)
      {
        list_ele_t *tmp = i;
        i = i->next;
        free(tmp);
      }
      free(q);
    }
}

/*
  Attempt to insert element at head of queue.
  Return true if successful.
  Return false if q is NULL or could not allocate space.
 */
bool q_insert_head(queue_t *q, int v)
{
    list_ele_t *newh;
    /* What should you do if the q is NULL? */
    if (q)
    {
      if ((newh = malloc(sizeof(list_ele_t))))
      {
        newh->value = v;
        newh->next = q->head;
        q->head = newh;
        if (!q->size)
        {
          q->tail = newh;
        }
        ++q->size;
        return true;
      }
      else
      {
        return false;
      }
    }
    else
    {
      return false;
    }
}


/*
  Attempt to insert element at tail of queue.
  Return true if successful.
  Return false if q is NULL or could not allocate space.
 */
bool q_insert_tail(queue_t *q, int v)
{
    /* You need to write the complete code for this function */
    /* Remember: It should operate in O(1) time */
    list_ele_t *newh;
    if (q)
    {
      if ((newh = malloc(sizeof(list_ele_t))))
      {
        newh->value = v;
        newh->next = NULL;
        if (q->tail)
        {
          q->tail->next = newh;
          q->tail = newh;
          ++q->size;
        }
        else
        {
          q->head = q->tail = newh;
          ++q->size;
        }
        return true;
      }
      else
      {
        return false;
      }
    }
    else
    {
      return false;
    }
}

/*
  Attempt to remove element from head of queue.
  Return true if successful.
  Return false if queue is NULL or empty.
  If vp non-NULL and element removed, store removed value at *vp.
  Any unused storage should be freed
*/
bool q_remove_head(queue_t *q, int *vp)
{
    /* You need to fix up this code. */
    if (!q || !q->size)
    {
      return false;
    }
    else
    {
      if (vp)
      {
        *vp = q->head->value;
      }
      list_ele_t *tmp = q->head;
      q->head = q->head->next;
      free(tmp);
      --q->size;
      return true;
    }
}

int q_size(queue_t *q)
{
    /* You need to write the code for this function */
    /* Remember: It should operate in O(1) time */
    if (!q || !q->size)
    {
      return 0;
    }
    else
    {
      return q->size;
    }
}

/*
  Reverse elements in queue
 */
void q_reverse(queue_t *q)
{
    if (q && q->size)
    {
      int cache[q->size];
      list_ele_t *tmp = q->head;
      for (int i = q->size - 1; (i >= 0) && (tmp != NULL); --i)
      {
        cache[i] = tmp -> value;
        tmp = tmp->next;
      }
      tmp = q->head;
      for (int i = 0; (i < q->size) && (tmp != NULL); ++i)
      {
        tmp->value = cache[i];
        tmp = tmp->next;
      }
    }
}

Build

拿到初始的代码时,敲make命令并不会开始build你的代码:

linux> make

你需要修改用于构造qtest的Makefile文件,你要在build和链接过程中添加queue.o。有关Makefile规则的说明可以参考 Using make and writing Makefiles。

其中的“An example of building an executable from multiple .o files”部分对本作业很有帮助。正确的修改Makefile能使得在输入make命令时,如果queue.o发生了变化,就重新生成qtest。
无论是在Linux还是在Unix环境中,make都是一个非常重要的编译命令。


不管是自己进行项目开发还是安装应用软件,我们都经常要用到makemake install。利用make工具,我们可以将大型的开发项目分解成为多个更易于管理的模块,对于一个包括几百个源文件的应用程序,使用makemakefile工具就可以简洁明快地理顺各个源文件之间纷繁复杂的相互关系。而且如此多的源文件,如果每次都要键入gcc命令进行编译的话,那对程序员来说简直就是一场灾难。而make工具则可自动完成编译工作,并且可以只对程序员在上次编译后修改过的部分进行编译。因此,有效的利用makemakefile工具可以大大提高项目开发的效率。同时掌握makemakefile之后,您也不会再面对着Linux下的应用软件手足无措了。

1.make命令详细教程
2.make详解

言归正传
按照题目说的如果直接make的话并不会编译我的代码,反而出现了以下状况:
在这里插入图片描述
大概就是因为没有修改qtest的makefile文件,里面的函数引用就未定义。

打开文件夹中的makefile文件,出现了更改提示:
在这里插入图片描述
我们只需要添加queue.o到编译过程和链接过程即可。

开始是不能编辑的,按下i键改换成insert模式即可,改成如下图可视即可
上下都需要加上需要编译的queue.o
在这里插入图片描述

vim全选删除,全选,全选复制

再次键入make,成功
在这里插入图片描述

测试

如果你的Makefile书写正确,编译器将生成一个可执行程序qtest,提供一个命令界面,你可以使用该界面创建,修改和检查队列。 关于可用命令的文档可以通过启动该程序并运行help命令找到:

linux> ./qtest
cmd>help

成功生成一个可执行程序qtest:
在这里插入图片描述

以下文件(traces/trace-eg.cmd)演示了一个示例命令序列:

在这里插入图片描述
在批处理模式下运行qtest能看到这些命令的效果:

linux> ./qtest -f traces/trace-eg.cmd

在初始代码中很多操作都没有正确实现。

traces目录包含11个trace文件,其格式为trace-k-cat.txt,其中k是trace号,cat指明它测试的属性的类别。每个trace由一系列命令组成,与上面显示的相似,来测试程序的正确性、健壮性和性能的不同方面。你可以使用这些trace文件直接与qtest进行交互,测试和调试你的程序。

按照教程输入即可
在这里插入图片描述

评测

评测阶段,我们会用15个trace文件对你的程序进行评估,除了已经发给你们的11个外,每种类型新增加一个trace(共4个)。每一个trace有一个分数值(6或7分不等),总计为100分(提供给你的11个trace总分为67分)。每执行正确一个结果获得相应的分数。程序driver.py在trace上运行qtest并计算分数。你可以在本地使用以下命令直接调用它:

linux> ./driver.py

或者

linux> make test

在这里插入图片描述

73分……人麻了……等我再改改

本地测试通过后,务必一定进行在线评测,否则没有成绩!!!

将下面三个文件都拷贝到桌面上:

unix> cp Makefile queue.c queue.h ~/Desktop

在这里插入图片描述

然后点击“提交评测”,自动评测即可。

在这里插入图片描述
满分哈哈哈,做完作业了。

代码存档

设置下邮箱和姓名
在这里插入图片描述
交完作业啦🎉
在这里插入图片描述

  • 19
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Lydia.na

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值