CS61C 2020计算机组成原理Lab02

Makefile是一个特殊的文件,用于控制构建(编译和链接)过程

Exercise 0:

根据下面Makefile文件的内容,回答7个问题,先弄明白这个文件的意思

UNAME_S := $(shell uname -s)
CC=gcc
LD=gcc
CFLAGS=-ggdb -Wall -std=c99
LDFLAGS=

变量定义:

  • UNAME_S := $(shell uname -s): 使用shell命令**uname -s获取操作系统名称,并将其赋值给变量UNAME_S**。
  • CC=gcc: 设置C编译器为**gcc**。
  • LD=gcc: 设置链接器为**gcc**。
  • CFLAGS=-ggdb -Wall -std=c99: 设置C编译器标志。**ggdb添加gdb调试信息,Wall开启所有警告,std=c99**使用C99标准。
  • LDFLAGS=: 设置链接器标志,当前为空。
ifeq ($(UNAME_S), Darwin)
    MEMCHECK=valgrind --tool=memcheck --leak-check=full --track-origins=yes --dsymutil=yes --suppressions=osx_vector.supp
endif

ifeq ($(UNAME_S), Linux)
    MEMCHECK=valgrind --tool=memcheck --leak-check=full --track-origins=yes
endif

条件赋值:

  • 为**MEMCHECK**变量赋不同的值,取决于操作系统。
  • 如果是Darwin(macOS),使用特定的valgrind命令行选项。
  • lfsr.o如果是Linux,使用另一组valgrind选项。
# bit_ops.o 和 test_bit_ops.o 是bit_ops程序的对象文件(.o文件)
# 也是编译后的中间文件, 它们将被链接以形成最终的可执行程序
BIT_OPS_OBJS = bit_ops.o test_bit_ops.o  #定义bit_ops程序的对象文件
# BIT_OPS_PROG 定义了最终的可执行程序的名称,即“bit_ops”
BIT_OPS_PROG = bit_ops

LFSR_OBJS = lfsr.o test_lfsr.o
LFSR_PROG = lfsr

VECTOR_OBJS=vector.o vector_test.o
VECTOR_PROG=vector_test

# 定义了一个变量BINARIES,它包含了所有要构建的可执行程序的名称
BINARIES=$(VECTOR_PROG) $(BIT_OPS_PROG) $(LFSR_PROG)
# 
all: $(BINARIES)

all: $(BINARIES):

  • 这是一个特殊的目标**all,它是Makefile的默认目标。这意味着当你在命令行中只输入make并没有指定具体的目标时,make命令将会执行all**目标。
  • all: $(BINARIES)的意思是,要构建all目标,就需要构建BINARIES变量中列出的所有程序。换句话说,当你执行makemake all时,它将会编译和链接vector_test、**bit_opslfsr**这三个程序。
$(BIT_OPS_PROG): $(BIT_OPS_OBJS)
	$(CC) $(CFLAGS) -g -o $(BIT_OPS_PROG) $(BIT_OPS_OBJS) $(LDFLAGS)

$(LFSR_PROG): $(LFSR_OBJS)
	$(CC) $(CFLAGS) -g -o $(LFSR_PROG) $(LFSR_OBJS) $(LDFLAGS)

这两段代码是Makefile中的规则,用于指定如何构建特定的程序,第一个规则是

  • $(BIT_OPS_PROG): $(BIT_OPS_OBJS): 这表示**bit_ops程序(值来自变量BIT_OPS_PROG)依赖于bit_ops.otest_bit_ops.o这两个对象文件(值来自变量BIT_OPS_OBJS)。换句话说,要构建bit_ops**程序,首先需要有这两个对象文件。
  • $(CC) $(CFLAGS) -g -o $(BIT_OPS_PROG) $(BIT_OPS_OBJS) $(LDFLAGS): 这是构建程序的命令。它使用**gcc编译器($(CC))和指定的编译器标志($(CFLAGS))来链接对象文件。g是添加调试信息的标志,o $(BIT_OPS_PROG)指定输出的可执行文件名(bit_ops),$(BIT_OPS_OBJS)是要链接的对象文件,$(LDFLAGS)**是链接器标志(当前为空)。

这些规则的目的是告诉Make如何从对象文件创建可执行程序。当执行**make**命令时,Make会查找这些规则,根据依赖关系先编译必要的源文件生成对象文件,然后再链接这些对象文件生成最终的可执行文件。

lfsr.c: lfsr.h
test_lfsr.c: lfsr.h

bit_ops.c: bit_ops.h
test_bit_ops.c: bit_ops.h

这四行是Makefile中的依赖性声明,它们指定了源代码文件(**.c文件)对头文件(.h**文件)的依赖关系。每一行的含义如下:

  1. lfsr.c: lfsr.h:
    • 这表示**lfsr.c文件依赖于lfsr.h头文件。意思是,如果lfsr.h被修改了,那么lfsr.c**应该被重新编译。
.c.o:
	$(CC) -c $(CFLAGS) $<

vector-memcheck: $(VECTOR_PROG)
	$(MEMCHECK) ./$(VECTOR_PROG)

这两段代码是Makefile中的规则,用于定义特定构建和测试任务:

  1. 模式规则 .c.o::
    • 这是一个模式规则,用于将C源文件(**.c文件)编译成对象文件(.o**文件)。
    • $(CC) -c $(CFLAGS) $<: 这行指定了编译的具体命令。$(CC)是编译器(在这个Makefile中设置为gcc),**c标志告诉编译器生成对象文件而不是完整的程序,$(CFLAGS)是编译器标志(包含调试信息、所有警告和使用C99标准),$<是自动变量,代表规则中的第一个依赖项,即当前要编译的.c**文件。
    • 这个规则适用于所有**.c.o**的转换,使得Makefile更加通用和简洁。
  2. 特定的测试目标 vector-memcheck:
    • 这个规则是为了运行内存检查工具valgrind来测试**vector_test程序(由$(VECTOR_PROG)**指定)。
    • $(MEMCHECK) ./$(VECTOR_PROG): 在这里,$(MEMCHECK)是之前根据操作系统设置的valgrind命令,./$(VECTOR_PROG)指定要运行的程序(在这个例子中是vector_test)。这个命令的作用是使用valgrind来检查**vector_test**程序在运行时的内存使用情况,比如是否有内存泄漏。
clean:
	-rm -rf core *.o *~ "#"*"#" Makefile.bak $(BINARIES) *.dSYM

这行是Makefile中的一个特殊目标,名为 clean。它定义了一个命令序列,用于清理项目中生成的所有临时文件和编译生成的文件。让我们分解这条命令:

  • clean:: 这定义了一个名为 clean 的目标。在命令行中运行 make clean 会执行这个目标下的命令。
  • rm -rf core *.o *~ "#"*"#" Makefile.bak $(BINARIES) *.dSYM: 这是实际执行的命令,用于删除多种类型的文件:
    • rm: 这是删除文件和目录的命令。前面的 `` 表示即使某些文件或目录不存在,也不会导致 make 命令失败。
    • rf: 这些是 rm 命令的选项。r 代表递归删除,用于目录及其内容;f 代表强制删除,不会因为文件不存在而出错,也不会提示确认。
    • core: 删除名为 core 的文件,这通常是程序崩溃时生成的核心转储文件。
    • .o: 删除所有扩展名为 .o 的对象文件。
    • ~: 删除所有以 ~ 结尾的备份文件。
    • "#"*"#": 删除所有以 # 开头和结尾的文件,这些通常是临时文件。
    • Makefile.bak: 删除名为 Makefile.bak 的备份文件。
    • $(BINARIES): 删除所有由 $(BINARIES) 变量定义的可执行文件。
    • .dSYM: 在 macOS 上,删除所有 .dSYM 目录,这些目录包含了调试符号。

总的来说,clean 目标在项目中非常有用,它可以清理编译过程中生成的所有临时文件和可执行文件,为一个全新的编译过程提供干净的环境。

vector.c: vector.h
vector_test.c: vector.h

需要回答的7个问题:

  1. Which target is part of a rule that deletes all the compiled programs? The target is clean.
  2. Which target is part of a rule that makes all the compiled programs? The target is all.
  3. Which compiler is currently being used? gcc
  4. What C standard are we currently using? C99
  5. How would we reference a variable FOO in a makefile? $(FOO)
  6. What operating system does the term “Darwin” represent? macOS
  7. What line creates the lfsr program from its object files? (Give its line number.) is 31

Exercise 1: Bit Operations

#include <stdio.h>
#include "bit_ops.h"

// Return the nth bit of x.
// Assume 0 <= n <= 31
unsigned get_bit(unsigned x,
                 unsigned n) {
    // YOUR CODE HERE
    // Returning -1 is a placeholder (it makes
    // no sense, because get_bit only returns
    // 0 or 1)
    return (x & (1 << n)) >> n;
}
// Set the nth bit of the value of x to v.
// Assume 0 <= n <= 31, and v is 0 or 1
void set_bit(unsigned * x,
             unsigned n,
             unsigned v) {
    // 我的方法:
    unsigned mask = 1 << n;
    if(v == 1){
        *x = *x || mask;
    }else {
        *x = *x & ~mask;
    }

    // gpt4的方法:
    // (*x & ~mask) 这一步是让 x的第n位变成0,且其他位不变,  使用| (v << n) 是去设置第n位变成v
    *x = (*x & ~mask) | (v << n);
}
// Flip the nth bit of the value of x.
// Assume 0 <= n <= 31
void flip_bit(unsigned * x,
              unsigned n) {
    // YOUR CODE HERE
    unsigned mask = 1<< n;
    *x = *x ^ mask;
}

Exercise 2: Linear Feedback Shift Register

在这里插入图片描述

void lfsr_calculate(uint16_t *reg) {
    /* YOUR CODE HERE */
    uint16_t bit;
    bit = (*reg ^ (*reg >> 2) ^(*reg >> 3) ^ (*reg >> 5)) & 1;
    *reg = (*reg >> 1 )| (bit << 15);
}

Exercise 3: Linked Lists

typedef struct node {
	int val;
	struct node *next;
} node;
/* Add a node to the end of the linked list. Assume head_ptr is non-null. */
void append_node (node** head_ptr, int new_data) {
	/* First lets allocate memory for the new node and initialize its attributes */
	node* new_node = (node*)malloc(sizeof(node));

	// Initialize its attributes
    new_node->val = new_data;
    new_node->next = NULL;

	/* If the list is empty, set the new node to be the head and return */
	if (*head_ptr == NULL) {
		*head_ptr = new_node;
		return;
	}
	node* curr = *head_ptr;
	while (curr->next != NULL) {
		curr = curr->next;
	}
	/* Insert node at the end of the list */
	curr->next = new_node;
}

/* Reverse a linked list in place (in other words, without creating a new list).
   Assume that head_ptr is non-null. */
void reverse_list (node** head_ptr) {
	node* prev = NULL;
	node* curr = *head_ptr;
	node* next = NULL;
	while (curr != NULL) {
		next = curr->next;
		curr->next = prev;
		prev = curr;
		curr = next;
	}
	/* Set the new head to be what originally was the last node in the list */
	*head_ptr = prev;
}

在这里插入图片描述

Exercise 4: Memory Management

Explain why bad_vector_new() and also_bad_vector_new() are bad:

/* Bad example of how to create a new vector */
vector_t *bad_vector_new() {
    /* Create the vector and a pointer to it */
    vector_t *retval, v;
    retval = &v;

    /* Initialize attributes */
    retval->size = 1;
    retval->data = malloc(sizeof(int));
    if (retval->data == NULL) {
        allocation_failed();
    }

    retval->data[0] = 0;
    return retval;
}

/* Another suboptimal way of creating a vector */
vector_t also_bad_vector_new() {
    /* Create the vector */
    vector_t v;

    /* Initialize attributes */
    v.size = 1;
    v.data = malloc(sizeof(int));
    if (v.data == NULL) {
        allocation_failed();
    }
    v.data[0] = 0;
    return v;
}
  1. 局部变量的生命周期:在 bad_vector_new() 中,变量 v 是一个局部变量,而 retval 是指向它的指针。当函数返回时,v 超出作用域,它占用的内存可能会被用于其他用途,使得 retval 成为一个悬空指针。当你试图在这个函数外部使用 retval 时,这会导致未定义的行为。
  2. 返回局部变量:在 also_bad_vector_new() 中,函数返回局部变量 v 的副本。对于大型结构体来说,这种做法效率低下,如果结构体包含指向动态分配内存的指针(就像在这个案例中一样),也可能导致问题。这将导致当 v 在函数末尾超出作用域时,其内存被释放,使返回的副本中包含一个悬空指针。

fill in the functions vector_new(), vector_get(), vector_delete(), and vector_set() in vector.c (as well as the function headers in vector.h)

vector.c:

/* Create a new vector with a size (length) of 1
   and set its single component to zero... the
   RIGHT WAY */
vector_t *vector_new() {
    /* Declare what this function will return */
    vector_t *retval;

    /* First, we need to allocate memory on the heap for the struct */
    retval = malloc(sizeof(vector_t));

    /* Check our return value to make sure we got memory */
    if (retval == NULL) {
        allocation_failed();
    }

    /* Now we need to initialize our data.
       Since retval->data should be able to dynamically grow,
       what do you need to do? */
    retval->size = 1;
    retval->data = malloc(sizeof(int));

    /* Check the data attribute of our vector to make sure we got memory */
    if (retval->data = NULL) {
        free(retval);				//Why is this line necessary?
        allocation_failed();
    }

    /* Complete the initialization by setting the single component to zero */
    retval->data[0]= 0;

    /* and return... */
    return retval;
}

/* Return the value at the specified location/component "loc" of the vector */
int vector_get(vector_t *v, size_t loc) {

    /* If we are passed a NULL pointer for our vector, complain about it and exit. */
    if(v == NULL) {
        fprintf(stderr, "vector_get: passed a NULL vector.\n");
        abort();
    }

    /* If the requested location is higher than we have allocated, return 0.
     * Otherwise, return what is in the passed location.
     */
    if (loc < v->size) {
        return v->data[loc];
    } else {
        return 0;
    }
}

/* Free up the memory allocated for the passed vector.
   Remember, you need to free up ALL the memory that was allocated. */
void vector_delete(vector_t *v) {
    /* YOUR SOLUTION HERE */
    if(v != NULL){
        free(v->data);
        free(v);
    }
}

/* Set a value in the vector. If the extra memory allocation fails, call
   allocation_failed(). */
void vector_set(vector_t *v, size_t loc, int value) {
    /* What do you need to do if the location is greater than the size we have
     * allocated?  Remember that unset locations should contain a value of 0.
     */
    if(v == NULL) {
        fprintf(stderr, "vector_set: passed a NULL vector.\n");
        abort();
    }

    /* YOUR SOLUTION HERE */
    if(loc >= v->size){
        // Resize the data array
        int *new_data = realloc(v->data, (loc + 1) * sizeof(int));
        if(new_data == NULL){
            allocation_failed();
        }
        // Initialize new elements to 0
        for(size_t i = v->size; i <= loc; i++) {
            new_data[i] = 0;
        }
        v->data = new_data;
        v->size = loc + 1;
    }
    v->data[loc] = value;
}

vector.h:

/* Create a new vector */
vector_t *vector_new();

/* Free up the memory allocated for the passed vector */
/* YOUR CODE HERE */
void vector_delete(vector_t *v);

/* Return the value in the vector */
int vector_get(vector_t *v, size_t loc);

/* Set a value in the vector */
/* YOUR CODE HERE */
void vector_set(vector_t *v, size_t loc, int value);

Also, implement a rule for the vector_test target in the makefile.

$(VECTOR_PROG):$(VECTOR_OBJS)
	$(CC) $(CFLAGS) -g -o $(VECTOR_PROG) $(VECTOR_OBJS) $(LDFLAGS)

vector.c: vector.h
vector_test.c: vector.h

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
因为mac系统无法使用valgrind,因此这一部分,就跟着网站上的解释一下就是了

valgrind --tool=memcheck --leak-check=full --track-origins=yes [OS SPECIFIC ARGS] ./<executable>
valgrind --tool=memcheck --leak-check=full --track-origins=yes [OS SPECIFIC ARGS] ./<executable>

–tool=memcheck:使用 memcheck 工具检测内存错误,包括使用未初始化的变量、读写越界等;
–leak-check=full:全面检测内存泄漏,不仅仅检测未释放的内存,还会检测处理时出现的一些问题;

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值