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
变量中列出的所有程序。换句话说,当你执行make
或make all
时,它将会编译和链接vector_test
、**bit_ops
和lfsr
**这三个程序。
$(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.o
和test_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
**文件)的依赖关系。每一行的含义如下:
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中的规则,用于定义特定构建和测试任务:
- 模式规则
.c.o:
:- 这是一个模式规则,用于将C源文件(**
.c
文件)编译成对象文件(.o
**文件)。 $(CC) -c $(CFLAGS) $<
: 这行指定了编译的具体命令。$(CC)
是编译器(在这个Makefile中设置为gcc
),**c
标志告诉编译器生成对象文件而不是完整的程序,$(CFLAGS)
是编译器标志(包含调试信息、所有警告和使用C99标准),$<
是自动变量,代表规则中的第一个依赖项,即当前要编译的.c
**文件。- 这个规则适用于所有**
.c
到.o
**的转换,使得Makefile更加通用和简洁。
- 这是一个模式规则,用于将C源文件(**
- 特定的测试目标
vector-memcheck
:- 这个规则是为了运行内存检查工具valgrind来测试**
vector_test
程序(由$(VECTOR_PROG)
**指定)。 $(MEMCHECK) ./$(VECTOR_PROG)
: 在这里,$(MEMCHECK)
是之前根据操作系统设置的valgrind命令,./$(VECTOR_PROG)
指定要运行的程序(在这个例子中是vector_test
)。这个命令的作用是使用valgrind来检查**vector_test
**程序在运行时的内存使用情况,比如是否有内存泄漏。
- 这个规则是为了运行内存检查工具valgrind来测试**
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个问题:
- Which target is part of a rule that deletes all the compiled programs? The target is
clean
. - Which target is part of a rule that makes all the compiled programs? The target is
all
. - Which compiler is currently being used?
gcc
- What C standard are we currently using?
C99
- How would we reference a variable FOO in a makefile?
$(FOO)
- What operating system does the term “Darwin” represent?
macOS
- 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;
}
- 局部变量的生命周期:在
bad_vector_new()
中,变量v
是一个局部变量,而retval
是指向它的指针。当函数返回时,v
超出作用域,它占用的内存可能会被用于其他用途,使得retval
成为一个悬空指针。当你试图在这个函数外部使用retval
时,这会导致未定义的行为。 - 返回局部变量:在
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:全面检测内存泄漏,不仅仅检测未释放的内存,还会检测处理时出现的一些问题;