比较基础的Pre教程,主要分为四个部分
- Git基本操作
- c-exercise
- make-exercise
- mips-exercise
Git基本操作
在进入正式的练习之前,首先是前置知识点,我们需要在任务列表处初始化分支后,在评测机上通过命令行进入工作目录:
cd ~/12345678(你的学号)
此时应该会显示 /home/git/12345678(你的学号)
确认所处位置无误以后,可以使用git branch -a来查看你所处的分支和远端分支,正常情况下会显示:
*protected remotes/origin/HEAD -> origin/protected remotes/origin/protected
其中*protected代表你处于的本地分支是protected,remotes/origin/protected代表远端origin仓库下存在一个protected分支,而remotes/origin/HEAD -> origin/protected代表远端的默认分支是origin/protected,意思就是你使用git clone repo.git的时候,本地默认处于protected分支,这是系统根据origin/HEAD为你创建的本地分支
现在你需要进行本次实验,要做的就是拉取远端新初始化的分支,使用git fetch拉取所有远端分支,以c-exercise为例,此时再使用git branch -a你应该会看到:
*protected remotes/origin/HEAD -> origin/protected remotes/origin/protected remotes/origin/c-exercise
现在我们想要在c-exercise分支上面开发,我们可以使用:
git checkout -b c-exercise origin/c-exercise
此时你再次使用git branch -a确认你会看到:
protected *c-exercise remotes/origin/HEAD -> origin/protected remotes/origin/protected remotes/origin/c-exercise
如果你不小心创建了不想要的分支,可以使用git branch -D branch-name来删除你不需要的本地分支
此外,你也可以使用git checkout -b branch-name来创建本地的新分支,如果此时你需要同步远端分支的内容,可以使用git reset --hard origin/branch来完成,以c-exercise为例:
git checkout -b c-exercise git reset --hard origin/make-exercise
如果需要切换不同的分支可以使用
git checkout branchName
之后你就可以进行相应的开发了,后续就不再详细介绍该过程
c-exercise
本题的主要任务就是使用C语言实现一些基本的库函数
第一部分是实现size_t strlen(const char *s),遍历字符串直到遇'\0'即可
size_t strlen(const char *s) {
size_t i=0;
while(*(s+i)!='\0')i++;
return i;
}
第二部分则是实现char *strcat(char *dst, const char *src),可以先判断两个字符串是不是NULL进行处理,接着把dst移动至末尾'\0',然后执行*dst=*src即可,别忘了最后加上'\0'
char *strcat(char *dst, const char *src) {
if (dst == NULL || src == NULL)
return dst;
char *res = dst;
while (*dst != '\0')
dst++;
while (*src != '\0') {
*dst = *src;
dst++;
src++;
}
*dst = '\0';
return res;
}
第三部分则是实现char *strncat(char *dst, const char *src, size_t n),相比于上一题,只要在while循环加入n的条件控制即可
char *strncat(char *dst, const char *src, size_t n){
if(dst==NULL||src==NULL)return dst;
char *res = dst;
while(*dst!='\0')dst++;
while(*src!='\0'&&n--){
*dst++=*src++;
}
*dst='\0';
return res;
}
第四部分则是实现char* strsep(char** stringp, const char* delim),题目初看可能有些不好懂,意思其实就是delim所指向的字符串是一个字符合集,这里面的每个字符都可以被当作分隔符,你需要在stringp中查找这些字符,把找到的第一个字符合集中的字符置'\0'并把源字符串指针后移一位,然后返回被分割的原字符串的指针头,所以初始时需要保存stringp的指针头,之后我是使用了两层while进行查找
char* strsep(char** stringp, const char* delim){
if(*stringp==NULL)return NULL;
char *start = *stringp;
int flag=0;
while(**stringp!='\0'){
char *destr = delim;
while(*destr!='\0'){
if(*destr==**stringp){
**stringp='\0';
(*stringp)++;
flag=1;
break;
}
destr++;
}
if(flag)break;
(*stringp)++;
}
if(!flag){
*stringp=NULL;
}
return start;
}
make-exercise
本题主要任务就是学习Makefile的基本语法
给定代码段如:
all: target
target: target.c
gcc target.c -o target
如果我们执行make all,它会先去寻找它后面的依赖target(不过依赖也可能不止一个,示例只给出了一种的情况),而target的依赖是target.c是目录下的C语言源码,不用再继续往下找了,执行后续命令行即可
此外,只使用make时,会默认构建第一个目标
明白了这一点,我们就可以完成任务了
题目要求执行make或者make out时利用 calc 计算 case_all,并将结果输出到 out 文件中
所以我们Makefile的第一个目标应该是out,它依赖于calc和case_all,于是我们得到
out: calc case_all
./calc < case_all > out
case_add: casegen
./casegen add 100 > case_add
case_sub: casegen
./casegen sub 100 > case_sub
case_mul: casegen
./casegen mul 100 > case_mul
case_div: casegen
./casegen div 100 > case_div
case_all: case_add case_sub case_mul case_div
cat case_add > case_all
cat case_sub >> case_all
cat case_mul >> case_all
cat case_div >> case_all
calc: calc.c
gcc calc.c -o calc
casegen: casegen.c
gcc casegen.c -o casegen
最后补上clean
clean:
rm -f out calc casegen case_* *.o
值得注意的是作业给出的文件开头
.PHONY: clean
这是一个特殊的目标,有了它以后我们每次make clean系统就不会去目录下寻找有没有clean这个文件,而是直接执行
mips-exercise
本题主要利用汇编的知识完成代码补全
先看第一部分
str: .asciiz "Hello World\n" # Null-terminated string "Hello World" stored at label 'str' .align 2 # align to 4-byte boundary (2^2) now at 0x1010 based on 0x1000
字符串"Hello World\n"占了13个字节,此时使用.align 2代表对齐2的2次方,也即4字节,所以此时地址偏移了16字节
.byte 3 .align x .word 7,8,9
接着题目要求3和7是直接相邻的,但是3此时的地址偏移量是17,而word会自动对齐4字节,也就是会在3后面补齐3个0,为了阻止系统自动补0,我们使用.align 0对齐2的0次方即1,从而阻止后面的word补0
la a0, str addiu sp, sp, x /*Nonleaf function 'print_str'*/ jal print_str
函数print_str需要调用其他函数,所以需要栈空间来保存返回地址$ra
而即使原本只要4字节的空间,栈空间要对齐8字节,所以补齐为8,故x=-8
addiu sp, sp, x
返回后恢复栈空间原位置即可x=8
/* Set the first four parameters. */ li a0, 0 li a1, 1 li a2, 2 li a3, 3 /* use 'addiu sp, sp, <x>' to push a proper-sized frame onto the stack for Nonleaf function 'hello'. */ /* Your code here. (4/6) */ addiu sp, sp, -24 lw t1, var li t2, 5 /* use 'sw t1, <x>(sp)' to store t1 at the proper place of the stack */ /* so that t1 is 5th argument of function hello. */ /* Your code here. (5/6) */ sw t1, 16(sp) /* use 'sw t2, <x>(sp)' to store t2 at the proper place of the stack */ /* so that t2 is 6th argument of function hello. */ /* Your code here. (6/6) */ sw t2, 20(sp) /* use 'j' to call the function 'hello', we use 'j' instead of 'jal' because 'hello' is 'noreturn' */ j hello
对于第4问,GPT老师给出的解释是参数量大于4,所以需要预留至少16字节的空间,加上要保存另外两个参数,所以是24字节
第五、六问则是因为栈是向下生长的,越往后的参数在越上面

Comments 2 条评论
lm好厉害
( ̄3 ̄)( ̄3 ̄)