MIPS链表
Posted
技术标签:
【中文标题】MIPS链表【英文标题】:MIPS linked list 【发布时间】:2016-08-01 00:04:25 【问题描述】:我很困惑如何在 MIPS 中创建结构。我想创建一个链表实现,它计算存储的字符串的长度,并按存储顺序对它们进行排序。到目前为止,这是我的代码:
# Global symbols
#
# string routines
.globl read_string
.globl strcmp
.globl strlen
.globl trim
.globl strloop
.globl replace
# list routines
.globl insert
.globl insert_here
.globl print_list
.globl main
# pseudo-standard library
.globl get_string
.globl malloc
.globl print_newline
.globl print_string
##################################################
# Constants
#
.data
MAX_STR_LEN: .word 50
STR_NEWLINE: .asciiz "\n"
STR_ENTER: .asciiz "enter a string: "
#################################################################
#################################################################
# Code
#
.text
##################################################
# main: repeatedly gets strings from user and enters them in list
# until a string of length less than two is entered;
# prints list in order when done
#
main:
# lines commented out - not needed in simulation:
# addi $sp, $sp, -12
# sw $ra, 0($sp)
# sw $s0, 4($sp) #$s0 will be linked list
# sw $s1, 8($sp) #$s1 will be the current string
li $s0, 0 # initialize the list to NULL
Loop_main:
la $a0, STR_ENTER
jal print_string
jal read_string
move $s1, $v0
jal trim
jal strlen
addi $t0, $zero, 2
beq $v0, $t0, Exit_loop_main
jal strcmp
jal insert
# replace newline with null terminator
# ...
# check string length; exit loop if less than 2
# ...
# insert string into list
# ...
# reassign front of list
j Loop_main
Exit_loop_main:
move $a0, $s0
jal print_list
jal print_newline
# lines commented out - not needed in simulation:
# lw $s1, 8($sp)
# lw $s0, 4($sp)
# lw $ra, 0($sp)
# addi $sp, $sp, 12
# jr $ra
# exit simulation via syscall
li $v0, 10
syscall
##################################################
# String routines
#
# read_string: allocates MAX_STR_LEN bytes for a string
# and then reads a string from standard input into that memory address
# and returns the address in $v0
read_string:
addi $sp, $sp, -8 #allocate space for 2 items on the stack
sw $ra, 0($sp) #push the jump register onto the stack
sw $s0, 4($sp) #push the head of the list onto the stack
add $t0, $t0, $zero #$t0 gets 0
la $t1, MAX_STR_LEN #$a0 gets MAX_STR_LEN
lw $a0, 0($t1) #move MAX_STR_LEN from $t1 into $a0
jal malloc #jump to malloc to allocate space for string
move $a0, $v0 #move pointer to allocated memory to $a0
add $t1, $t1, $zero #get zero
move $a1, $t1 #move zero to a1
la $a1, MAX_STR_LEN #$a1 gets MAX_STR_LEN
jal get_string #get the string into $v0
lw $s0, 4($sp) #load the head of the list
lw $ra, 0($sp) #load the jump address
addi $sp, $sp, 8 #push onto the stack space for 2 elements
jr $ra #jump back to caller function
# trim: modifies string stored at address in $a0 so that
# first occurrence of a newline is replaced by null terminator
trim:
li $t0, 10 #$t1 gets 10, ASCII value for newline
strloop:
lb $t1, 0($a0) #get byte of character of string and loop
beq $t1, $t0, replace #if $a0 = go to replace
addi $a0, $a0, 8 #increment $a0 by 8 to piont to first bit of next char
j strloop #jump back to beginning
replace:
add $t2, $t2, $zero #$t2 is set to zero, ASCII value for null terminator
sb $t2, 0($a0) #$t2 is stored into the byte starting at $a0
jr $ra #jump back to caller
# strlen: given string stored at address in $a0
# returns its length in $v0
strlen:
add $t0, $t0, $zero #$t0 gets zero
lenloop:
lb $t1, 0($a0) #get the first byte for first char in $a0
beq $t1, $zero, exitline #if $t1 == 0 (null terminator), jump to exit
addi $a0, $a0, 8 #else, increment to next byte of string for next char
addi $t0, $t0, 1 #increment $t0 for each character in string
j lenloop #jump back up to loop
exitline:
sw $t0, 0($v0) #store $t0 into $v0 to return lenght of string
jr $ra #jump back to caller
# strcmp: given strings s, t stored at addresses in $a0, $a1
# returns -1 if s < t; 0 if s == t, 1 if s > t
strcmp:
lb $t0, 0($a0) #get byte of first char in string s
lb $t1, 0($a1) #get byte of first char in string t
# lb $t3, 0($t0)
#lb $t4, 0($t1)
addi $t3, $t3, 1 #get 1 to compare
slt $t2, $t0, $t1 #if s[0] < t[0] $t2 = 1, else $t2 = 0
bne $t2, $t3, lessthan #if $t2 == 1, jump to lessthan
slt $t2, $t1, $t0 #if t[0] < s[1], $t2 = 1, else $t2 = 0
beq $t2, $t3, greaterthan #if $t2 == 1, jump to greaterthan
sw $zero, 0($v0) #$v0 gets zero
j end
lessthan:
addi $t4, $t4, -1 #$t4 gets -1
sw $t4, 0($v0) #$v0 gets -1
j end #jump to end
greaterthan:
addi $t4, $t4, 1 #$t4 gets 1
sw $t4, 0($v0) #$v0 gets 1
j end #jump to end
end:
jr $ra
# insert_here: given address of front of list in $a0
# and address of string to insert in $a1,
# inserts new linked-list node in front of list;
# returns address of new front of list in $v0
insert_here:
lw $t0, 0($a0) #$t0 get $a0
lw $t1, 0($a1) #$t1 gets $a1
addi $t2, $zero, 8 #$t2 gets 8
sw $t2, 0($a0) #$t2 gets stored into $a0
jal malloc #allocate 1 byte for the memory
move $t3, $v0 #get address of new memory from $v0 and move to $t3
sw $t1, 0($t3) #store the string pointer into bytes 0-3 of the new memory
sw $t0, 4($t3) #store the pointer to the original front of the list
sw $t3, 0($s0) #store the new node into $s0
lw $ra, 0($sp) #pop the register to jump back to off the stack
addi $sp, $sp, 4 #add to the stack
jr $ra #jump back to caller
##################################################
# List routines
#
# insert: given address of front of list in $a0
# and address of string to insert in $a1,
# inserts new linked-list node in appropriate place in list
# ...
# returns address of new front of list in $v0 (which may be same as old)
insert:
addi $sp, $sp, 4 #add space on the stack
sw $ra, 0($sp) #store jump register onto the stack
lw $t9, 0($a0) #load head of the list for later use
lw $t0, 0($a0) #load head of list into $t0
andi $t0, $t0, 240 #bitwise and with 240 (1111 0000) to extract first 4 bits for pointer to string
sw $t0, 0($a0) #store $t0 into $a0 for strcmp call
lb $t6, 0($t0) #get the byte of the first string char in the list
lw $t7, 0($a1) #get address of string
lb $t1, 0($t7) #get the byte of the first char of the string
addi $t3, $zero, 1 #$t3 gets 1
addi $t4, $zero, -1 #$t3 gets -1
alphloop: #be careful in this function may have a bug with front of the list
# slt $t2, $t1, $t0 #if $t1 < $t0, then $t2 = 1, else $t2 = 0
# beq $t2, $t3, put #if
# beq $t2, $zero, nextchar
jal strcmp #compare the strings in $a0 and $a1
move $t5, $v0 #move the value returned from strcmp into $t5
beq $t5, $t4, put #if $t5 = -1, then value is less and then put new string at head of list
beq $t5, $t3, nextstring #if $t5 = 1, then the head of the list is larger than the string and go to next string
beq $t5, $zero, close #check if it is zero, if so it is already in the list so step out
nextstring:
lw $t2, 0($a0) #store pointer to next node in $t2
andi $t8, $t9, 15 #get address of next node string
beq $t8, $zero, put #if it points to null then add node at the end
sw $t8, 0($a0) #store into $a0
j alphloop #check against the next string in loop
put:
li $t5, 8 #$t5 gets 8
move $a0, $t5 #$t5 moved into $a0
jal malloc #allocate size for node
move $t5, $v0 #move address returned by malloc to $t5
sw $a1, 0($t5) #store $a1 into address allocated
beq $t2, $zero, front #node is at front of the list, so there is no need to update pointer
sw $t2, 4($t5) #store pointer to current node into new node
addi $t0, $a0, -8 #subtract from the current node back one
sw $t5, 0($t0) #store new pointer into the node
jr $ra
front:
sw $t5, 0($s0) #make global reference to front of the node the new node if its at the front
close:
jr $ra #jump back
# print_list: given address of front of list in $a0
# prints each string in list, one per line, in order
print_list:
addi $sp, $sp, -8
sw $ra, 0($sp)
sw $s0, 4($sp)
move $s0, $a0
beq $s0, $zero, Exit_print_list
Loop_print_list:
lw $a0, 0($s0)
jal print_string
jal print_newline
lw $s0, 4($s0) # node = node->next
bne $s0, $zero, Loop_print_list
Exit_print_list:
lw $s0, 4($sp)
lw $ra, 0($sp)
addi $sp, $sp, 8
jr $ra
##################################################
# Pseudo-standard library routines:
# wrappers around SPIM/MARS syscalls
#
# assumes buffer to read into is in $a0, and max length is in $a1
get_string:
li $v0, 8
syscall
jr $ra
# malloc: takes one argument (in $a0) which indicates how many bytes
# to allocate; returns a pointer to the allocated memory (in $v0)
malloc:
li $v0, 9 # SPIM/MARS code for "sbrk" memory allocation
syscall
jr $ra
# print_newline: displays newline to standard output
print_newline:
li $v0, 4
la $a0, STR_NEWLINE
syscall
jr $ra
# print_string: displays supplied string (in $a0) to standard output
print_string:
li $v0, 4
syscall
jr $ra
我应该从这里去哪里?我的代码可以汇编,但在读取两个输入后不执行任何操作。
【问题讨论】:
真的很长。如果您将其缩短到尽可能短的长度,您将更有可能获得答案 @KonradLindenbach 是的,它很长,但我刚刚发布了一个完整的答案。 IMO,经过分析,OP真的确实需要发布那么多。我给出的答案 [带有注释、讨论等] 是约 24,000 个字符 [从 29,000 个减少]。带注释的来源占其中的 50%。 【参考方案1】:您在评论水平、风格和程序布局方面做得很好。我在 SO 上看到的一些最好的。总的来说,很努力。
但是,存在 个错误。
我已经生成了您的代码的注释版本和重构版本。见下文。
整个代码中存在许多错误,与结构或插入代码无关[至少有 26 个这样的错误]。
一个频繁的人经常重复错误的单条指令。这通常是想用一个常数来设置一个寄存器。您的代码使用(例如)addi $t3,$t3,7
而不是正确的addi $t3,$zero,7
。我用li $t3,7
替换了那些。在某些地方,您确实使用了正确的版本,所以我将其称为“错字”错误,但其中有很多。
您的strcmp
只比较了第一个字符,而不是整个字符串。
实际的插入代码有点复杂,而且比它需要的复杂得多(例如,insert_here
没有被使用)。它也有一些严重的逻辑/实现错误。你在正确的轨道上,但是,在修复了许多其他不相关的错误之后,我决定重写它而不是尝试修补它。
这是带注释的版本 [为 SO 空间限制而删减],我修复了大部分单行错误 [用“BUG”注释],但代码仍然 不 可运行并且 no 修复了结构/插入逻辑。我试图忠实于你的原始代码[请原谅无偿的风格清理]:
main:
# BUGBAD: this is the list pointer but it is _never_ set to a non-null
# value but things get stored relative to it
li $s0,0 # initialize the list to NULL
Loop_main:
la $a0,STR_ENTER
jal print_string
jal read_string
move $s1,$v0
# BUG: trim uses and trashes a0 but strlen needs the original value
jal trim
jal strlen
addi $t0,$zero,2
beq $v0,$t0,Exit_loop_main
# BUG: this strcmp serves _no_ purpose
jal strcmp
jal insert
# replace newline with null terminator
# ...
# check string length; exit loop if less than 2
# ...
# insert string into list
# ...
# reassign front of list
j Loop_main
Exit_loop_main:
move $a0,$s0
jal print_list
jal print_newline
li $v0,10
syscall
read_string:
addi $sp,$sp,-8
sw $ra,0($sp)
sw $s0,4($sp)
# BUG: this does _not_ set t0 = 0
###add $t0,$t0,$zero # $t0 gets 0
li $t0,0
# BUGFIX
# BUG: MAX_STR_LEN should be a _constant_ (e.g. 80) but this is an _address_
###la $t1,MAX_STR_LEN # $a0 gets MAX_STR_LEN
lw $t1,MAX_STR_LEN # $a0 gets MAX_STR_LEN
# BUGFIX
lw $a0,0($t1) # move MAX_STR_LEN from $t1 into $a0
jal malloc # allocate space for string
move $a0,$v0 # move pointer to allocated memory to $a0
# BUG: this does _not_ set t1 = 0
###add $t1,$t1,$zero # get zero
li $t1,0 # get zero
# BUGFIX
move $a1,$t1 # move zero to a1
# BUG: this does not set a1 = 50
###la $a1,MAX_STR_LEN # $a1 gets MAX_STR_LEN
lw $a1,MAX_STR_LEN # $a1 gets MAX_STR_LEN
# BUGFIX
jal get_string # get the string into $v0
lw $s0,4($sp)
lw $ra,0($sp)
addi $sp,$sp,8
jr $ra
# trim: modifies string stored at address in $a0 so that
# first occurrence of a newline is replaced by null terminator
trim:
# NOTE: using hex for this would be better (e.g. 0x0A)
li $t0,10 # $t1 gets 10, ASCII value for newline
strloop:
lb $t1,0($a0) # get byte of char of string and loop
beq $t1,$t0,replace # if $a0 = go to replace
# BUG: the increment should be 1
###addi $a0,$a0,8 # increment $a0 by 8 to piont to first bit of next char
addi $a0,$a0,1 # increment $a0 by 1 to point to next char
# BUGFIX
j strloop # jump back to beginning
replace:
# BUG: this does _not_ set t2 to 0
###add $t2,$t2,$zero # $t2 is set to zero, ASCII value for null terminator
li $t2,0 # t2 = zero, ASCII value for EOS
# BUGFIX
sb $t2,0($a0) # $t2 is stored into byte at $a0
jr $ra # jump back to caller
# strlen: given string stored at address in $a0
# returns its length in $v0
strlen:
# BUG: this does _not_ set t0 to zero
###add $t0,$t0,$zero # $t0 gets zero
li $t0,0
# BUGFIX
lenloop:
lb $t1,0($a0) # get the first byte for first char in $a0
beq $t1,$zero,exitline # if $t1 == 0 (null terminator), exit
# BUG: the increment here is wrong -- it should be 1
###addi $a0,$a0,8 # else, increment to next byte of string for next char
addi $a0,$a0,4 # else, increment to next byte of string
# BUGFIX
addi $t0,$t0,1 # increment $t0 for each char in string
j lenloop # jump back up to loop
exitline:
# BUG: this stores the length at the _address_ pointed to in v0
###sw $t0,0($v0) # store $t0 into $v0 to return lenght of string
move $v0,$t0
# BUGFIX
jr $ra # jump back to caller
# BUG: this only compares the first character
# strcmp: given strings s, t stored at addresses in $a0, $a1
# returns -1 if s < t; 0 if s == t, 1 if s > t
strcmp:
lb $t0,0($a0) # get byte of first char in string s
lb $t1,0($a1) # get byte of first char in string t
# lb $t3, 0($t0)
# lb $t4, 0($t1)
# BUG: this does not set t3 = 1
###addi $t3,$t3,1 # get 1 to compare
li $t3,1
# BUGFIX
slt $t2,$t0,$t1 # if s[0] < t[0] $t2 = 1, else $t2 = 0
bne $t2,$t3,lessthan # if $t2 == 1, jump to lessthan
slt $t2,$t1,$t0 # if t[0] < s[1], $t2 = 1, else $t2 = 0
beq $t2,$t3,greaterthan # if $t2 == 1, jump to greaterthan
# BUG: this does not set v0 = 0
###sw $zero,0($v0) # $v0 gets zero
li $v0,0
# BUGFIX
j end
lessthan:
# BUG: this does _not_ set t4 = -1
###addi $t4,$t4,-1 # $t4 gets -1
li $t4,-1
# BUGFIX
# BUG: this does not set v0
###sw $t4,0($v0) # $v0 gets -1
move $v0,$t4
# BUGFIX
j end # jump to end
greaterthan:
# BUG: this does _not_ set t4 = 1
###addi $t4,$t4,1 # $t4 gets 1
li $t4,1
# BUGFIX
# BUG: this does not set v0
###sw $t4,0($v0) # $v0 gets 1
move $v0,$t4
# BUGFIX
j end # jump to end
end:
jr $ra
# BUG: the front of the list is _always_ s0
# insert: given address of front of list in $a0
# and address of string to insert in $a1,
# inserts new linked-list node in appropriate place in list
# ...
# returns address of new front of list in $v0 (which may be same as old)
insert:
# BUG: should be -4
###addi $sp,$sp,4
addi $sp,$sp,-4
# BUGFIX
sw $ra,0($sp)
lw $t9,0($a0) # load head of the list for later use
lw $t0,0($a0) # load head of list into $t0
# BUG: anding a _pointer_ against 0xF0 makes _no_ sense
# NOTE: better to use hex for bit patterns
###andi $t0,$t0,240 # bitwise and with 240 (1111 0000) to extract first 4 bits for pointer to string
# BUGFIX
# BUG: this block of code is on the right track, but, wrong
# storing into a0 (the struct) for strcmp makes _no_ sense
sw $t0,0($a0) # store $t0 into $a0 for strcmp call
lb $t6,0($t0) # get the byte of the first string char in the list
lw $t7,0($a1) # get address of string
lb $t1,0($t7) # get the byte of the first char of the string
# NOTE: while we can set these here, we're burning two regs across the
# strcmp call -- cleaner to move this below the call
addi $t3,$zero,1 # $t3 gets 1
addi $t4,$zero,-1 # $t3 gets -1
# be careful in this function may have a bug with front of the list
alphloop:
# slt $t2, $t1, $t0 #if $t1 < $t0, then $t2 = 1, else $t2 = 0
# beq $t2, $t3, put #if
# beq $t2, $zero, nextchar
# BUG: strcmp destroys the values of a0 and a1, so the second time through
# here they have bogus values
# BUGBAD: strcmp uses them as pointers to the _strings_ but here, we're using
# a0 as a _struct_ pointer!!!
jal strcmp # compare the strings in $a0 and $a1
move $t5,$v0 # move the value returned from strcmp into $t5
beq $t5,$t4,put # if $t5 == -1, then value is less and then put new string at head of list
beq $t5,$t3,nextstring # if $t5 == 1, then the head of the list is larger than the string and go to next string
beq $t5,$zero,close # check if it is zero, if so it is already in the list so step out
nextstring:
lw $t2,0($a0) # store pointer to next node in $t2
# NOTE: use hex for bit masks (e.g. 0x0F)
# BUG: this makes no sense
andi $t8,$t9,15 # get address of next node string
beq $t8,$zero,put # if it points to null then add node at the end
sw $t8,0($a0) # store into $a0
j alphloop # check against the next string in loop
put:
# NOTE: what is 8??? obviously, it's the size in bytes of a node, so the
# comment should say that
li $t5,8 # $t5 gets 8
move $a0,$t5 # $t5 moved into $a0
jal malloc # allocate size for node
move $t5,$v0 # move address returned by malloc to $t5
sw $a1,0($t5) # store $a1 into address allocated
beq $t2,$zero,front # node is at front of the list, so there is no need to update pointer
sw $t2,4($t5) # store pointer to current node into new node
addi $t0,$a0,-8 # subtract from the current node back one
sw $t5,0($t0) # store new pointer into the node
jr $ra
front:
sw $t5,0($s0) # make global reference to front of the node the new node if its at the front
close:
jr $ra
这是清理、重构、更正和可运行的代码。
我推荐几件事:
(1) 对于函数内的标签,为避免与其他函数冲突,标签应以函数名称为前缀(例如,对于您的 trim
函数 [我将其重命名为 nltrim
],您有一个标签strloop
[我改名为nltrim_loop
])
(2) 注释应该用现实世界的术语来描述意图,而不仅仅是描述实际的 asm 指令。
我知道你才刚刚开始,但是(例如)这个:
addi $t3,$zero,7 # sets the value of $t3 to 7
应该替换为更具描述性的内容:
addi $t3,$zero,7 # count = 7
(3) 一般规则是在每一行 上添加一个侧边栏注释[你这样做了]。这是我做的,主要是。但是,对于一些很好理解的样板,cmets 可能是矫枉过正[并且实际上可能会干扰可读性]。
例如,为函数建立堆栈框架的代码以及在函数退出时从该框架恢复的代码都很好理解。因此,可能是顶部的单个块注释,例如几行的 # set up stack frame
和底部的 # restore from stack frame
,而不是每个 inst 上的 cmets
(4) 保持侧边栏 cmets 短,以便它们适合 80 列。如果您需要更多,请将注释提升为指令上方的完整行块注释[并根据需要使用尽可能多的行]
(5) 对于困难/复杂的东西,可以使用伪代码或实际 C [或您选择的语言] 进行原型设计。任何编写 [或阅读] asm 代码的人都熟悉至少一种高级语言 [C 最有可能],这是一个合理的假设。
对于结构代码,我在顶部块注释中添加了一个 C 结构定义。在insert
例程中,我在顶部注释块中添加了 C 伪代码。 asm 的侧边栏 cmets 常指伪代码中的符号和动作
实际上,即使对更简单的函数进行这种原型设计也是有好处的(即使您不将代码添加为 cmets)。 (例如)写你的 strcmp
时它可能会有所帮助
(6) 使代码尽可能简单。当代码不必要地复杂时,很容易在程序逻辑中引入错误或使用不正确的指令来实现该逻辑。这也使得以后很难发现这些错误。
(例如)在某些情况下,您的代码正在加载一个寄存器,只是稍后必须移动它。因此,在只需要一个的情况下使用 2-3 个 inst。尽量减少不必要的寄存器移动[不仅仅是为了速度,还有更简单的代码]。
例如,您的strcmp
有 24 行,并且只比较了第一个字符 [即一个错误],并有几个分支。我的版本只有 12 行,做了完整的字符串比较,而且是一个简单的循环。
同样,在您的插入代码中,insert_here
[未使用] 是 17 行,insert
是 47 行,总共 64 行。我的工作版本是 31 行。
注意: 我使用.eqv
伪操作来“定义”结构偏移量。我使用mars
,这适用,但我不知道spim
是否支持.eqv
。您始终可以对偏移量进行硬编码,但这会降低代码的可读性并且容易出错。对于具有 [say] 10 个元素的结构,这种形式的某种形式非常方便。大多数其他汇编器都有某种形式的 .eqv
等价物。
不管怎样,代码如下:
# Global symbols
# struct node
# struct node *node_next;
# char *node_str;
# ;
.eqv node_next 0
.eqv node_str 4
.eqv node_size 8 # sizeof(struct node)
# NOTE: we don't actually use this struct
# struct list
# struct node *list_head;
# struct node *list_tail;
# ;
.eqv list_head 0
.eqv list_tail 4
# string routines
.globl read_string
.globl strcmp
.globl strlen
.globl nltrim
# list routines
.globl insert
.globl print_list
.globl main
# pseudo-standard library
.globl get_string
.globl malloc
.globl print_newline
.globl print_string
# Constants
.data
MAX_STR_LEN: .word 50
STR_NEWLINE: .asciiz "\n"
STR_ENTER: .asciiz "enter a string: "
# global registers:
# s0 -- list head pointer (list_head)
# Code
.text
# main: repeatedly gets strings from user and enters them in list
# until a string of length less than two is entered;
# prints list in order when done
#
main:
li $s0,0 # list_head = NULL
main_loop:
# prompt user for string
la $a0,STR_ENTER
jal print_string
# read in string from user
jal read_string
# save the string pointer as we'll use it repeatedly
move $s1,$v0
# strip newline
move $a0,$s1
jal nltrim
# get string length and save the length
move $a0,$s1
jal strlen
# stop if given empty string
blez $v0,main_exit
# insert the string
jal insert
j main_loop
main_exit:
move $a0,$s0
jal print_list
jal print_newline
# exit simulation via syscall
li $v0,10
syscall
##################################################
# String routines
#
# read_string: allocates MAX_STR_LEN bytes for a string
# and then reads a string from standard input into that memory address
# and returns the address in $v0
read_string:
addi $sp,$sp,-8
sw $ra,0($sp)
sw $s0,4($sp)
lw $a1,MAX_STR_LEN # $a1 gets MAX_STR_LEN
move $a0,$a1 # tell malloc the size
jal malloc # allocate space for string
move $a0,$v0 # move pointer to allocated memory to $a0
lw $a1,MAX_STR_LEN # $a1 gets MAX_STR_LEN
jal get_string # get the string into $v0
move $v0,$a0 # restore string address
lw $s0,4($sp)
lw $ra,0($sp)
addi $sp,$sp,8
jr $ra
# nltrim: modifies string stored at address in $a0 so that
# first occurrence of a newline is replaced by null terminator
nltrim:
li $t0,0x0A # ASCII value for newline
nltrim_loop:
lb $t1,0($a0) # get next char in string
beq $t1,$t0,nltrim_replace # is it newline? if yes, fly
beqz $t1,nltrim_done # is it EOS? if yes, fly
addi $a0,$a0,1 # increment by 1 to point to next char
j nltrim_loop # loop
nltrim_replace:
sb $zero,0($a0) # zero out the newline
nltrim_done:
jr $ra # return
# strlen: given string stored at address in $a0
# returns its length in $v0
#
# clobbers:
# t1 -- current char
strlen:
move $v0,$a0 # remember base address
strlen_loop:
lb $t1,0($a0) # get the current char
addi $a0,$a0,1 # pre-increment to next byte of string
bnez $t1,strlen_loop # is char 0? if no, loop
subu $v0,$a0,$v0 # get length + 1
subi $v0,$v0,1 # get length (compensate for pre-increment)
jr $ra # return
# strcmp: given strings s, t stored at addresses in $a0, $a1
# returns <0 if s < t; 0 if s == t, >0 if s > t
# clobbers: t0, t1
strcmp:
lb $t0,0($a0) # get byte of first char in string s
lb $t1,0($a1) # get byte of first char in string t
sub $v0,$t0,$t1 # compare them
bnez $v0,strcmp_done # mismatch? if yes, fly
addi $a0,$a0,1 # advance s pointer
addi $a1,$a1,1 # advance t pointer
bnez $t0,strcmp # at EOS? no=loop, otherwise v0 == 0
strcmp_done:
jr $ra # return
# insert: inserts new linked-list node in appropriate place in list
#
# returns address of new front of list in $s0 (which may be same as old)
#
# arguments:
# s0 -- pointer to node at front of list (can be NULL)
# s1 -- address of string to insert (strptr)
#
# registers:
# s2 -- address of new node to be inserted (new)
# s3 -- address of previous node in list (prev)
# s4 -- address of current node in list (cur)
#
# clobbers:
# a0, a1 (from strcmp)
#
# pseudo-code:
# // allocate new node
# new = malloc(node_size);
# new->node_next = NULL;
# new->node_str = strptr;
#
# // for loop:
# prev = NULL;
# for (cur = list_head; cur != NULL; cur = cur->node_next)
# if (strcmp(new->node_str,cur->node_str) < 0)
# break;
# prev = cur;
#
#
# // insertion:
# new->node_next = cur;
# if (prev != NULL)
# prev->node_next = new;
# else
# list_head = new;
insert:
addi $sp,$sp,-4
sw $ra,0($sp)
# allocate a new node -- do this first as we'll _always_ need it
li $a0,node_size # get the struct size
jal malloc
move $s2,$v0 # remember the address
# initialize the new node
sw $zero,node_next($s2) # new->node_next = NULL
sw $s1,node_str($s2) # new->node_str = strptr
# set up for loop
li $s3,0 # prev = NULL
move $s4,$s0 # cur = list_head
j insert_test
insert_loop:
lw $a0,node_str($s2) # get new string address
lw $a1,node_str($s4) # get current string address
jal strcmp # compare them -- new < cur?
bltz $v0,insert_now # if yes, insert after prev
move $s3,$s4 # prev = cur
lw $s4,node_next($s4) # cur = cur->node_next
insert_test:
bnez $s4,insert_loop # cur == NULL? if no, loop
insert_now:
sw $s4,node_next($s2) # new->node_next = cur
beqz $s3,insert_front # prev == NULL? if yes, fly
sw $s2,node_next($s3) # prev->node_next = new
j insert_done
insert_front:
move $s0,$s2 # list_head = new
insert_done:
lw $ra,0($sp)
addi $sp,$sp,4
jr $ra
# print_list: given address of front of list in $a0
# prints each string in list, one per line, in order
print_list:
addi $sp,$sp,-8
sw $ra,0($sp)
sw $s0,4($sp)
beq $s0,$zero,print_list_exit
print_list_loop:
lw $a0,node_str($s0)
jal print_string
jal print_newline
lw $s0,node_next($s0) # node = node->node_next
bnez $s0,print_list_loop
print_list_exit:
lw $s0,4($sp)
lw $ra,0($sp)
addi $sp,$sp,8
jr $ra
# Pseudo-standard library routines:
# wrappers around SPIM/MARS syscalls
#
# assumes buffer to read into is in $a0, and max length is in $a1
get_string:
li $v0,8
syscall
jr $ra
# malloc: takes one argument (in $a0) which indicates how many bytes
# to allocate; returns a pointer to the allocated memory (in $v0)
malloc:
li $v0,9 # SPIM/MARS code for "sbrk"
syscall
jr $ra
# print_newline: displays newline to standard output
print_newline:
li $v0,4
la $a0,STR_NEWLINE
syscall
jr $ra
# print_string: displays supplied string (in $a0) to standard output
print_string:
li $v0,4
syscall
jr $ra
更新:
我同意逐行的 cmets,因为它可以帮助我准确了解我打算访问的寄存器(或在前一种情况下搞砸了)。
理由很简单。想象一下相反的情况:一个包含 no cmets 的大型 [5000 行] asm 文件,该文件已知存在错误。你不能相信逻辑/算法[它可能有错误]。你不能相信实现[它可能有错误]。每当我遇到这样的情况时,我都会按照我在之前所描述的那样添加 cmets,甚至寻找错误(即,我正在学习算法和代码)。
这是在进行代码审查。即使文件已经有 cmets [就像你的那样],我也会这样做。我经常对我刚刚编写的我认为“完整”的代码进行审查 [即所有需要编写的代码,都已经]。
如果我在当天晚些时候完成,我会在第二天作为第一件事进行审核。通常,“睡在上面”可以很容易地发现我在前一天的评论中不会发现的错误[因为我仍然“太接近”问题]。如果我写的东西要花几天时间,我总是第一件事就是回顾前一天的工作。
即使您的原始 cmets 类似于 (2),它也帮助我找到了您的“错字”错误。当我看到add $t1,$t1,$zero #get zero
时,评论不匹配很容易找到/修复。使用调试器单步执行代码会困难 10 倍。
评论总是帮助。最初编码insert
时,我有:
jal strcmp # compare them -- new < cur?
bgtz $v0,insert_now # if yes, insert after prev
我得到了从高到低的输出。
起初,我怀疑strcmp
,因为它[同样]是新代码。我通常在查看对它的调用之前查看较低级别的函数。我对strcmp
进行了代码审查,看起来还不错。但是,我仍然没有被说服。我为strcmp
编写了一些诊断代码[单元测试],但它通过了。
然后,我注意到 cmets 与上面 insert
中的代码。修复很简单:将 bgtz
更改为 bltz
。
另一个好的规则是:不要破坏[将错误引入]现有[工作]代码。
当我第一次查看 print_list
时,我想:“它正在使用 [和垃圾] s0”。这没关系,因为在调用它之后,程序正在终止。但是,如果它被多次调用,则不会。我错过了您正在在堆栈中保存/恢复s0这一事实[我在第二次阅读时意识到]。
看到一个有经验的人如此鼓励像我这样的新手,真是令人耳目一新
在某些时候,我们都是新手。没有伤害,没有犯规。
即使是经验丰富的程序员也会产生错误 [有时,多个错误 / 天]。错误不是对一个人的灵魂/性格的控诉。错误只是程序员的正常副产品[就像发现/修复它们一样]。
IMO,关键是:
(1) 愿意学习
(2) 承认错误(例如)肯尼迪总统 [在“猪湾”之后]:“如果我们承认错误,错误就不是错误”
(3) 最重要的是,将自我排除在外。
最弱的程序员,当一个错误[或可疑的错误]被报告时:
(1) 说他们的代码正在运行 [甚至不检查错误报告是否有价值]。
(2) 否认该错误不是真正的错误[当它是时]
(3) 拒绝生成 [或接受] 可以/确实证明/反驳错误的测试用例
(4) [故意]“缓慢”地修复错误[它不像新代码那样有趣]
(5) 在闲暇时间,拒绝“清理”正在工作但也结构不佳的代码[并且应该重构]
(6) 以轻蔑的态度对待客户 [i.e. “他们都是白痴”]
另一方面,IMO,优秀程序员在遇到错误[或潜在错误]时积极。
在我所在的一家公司,我们有一个没有明显错误的发货产品[一个实时视频处理平台]。我们正处于闲暇时间。但是,我的老板(也是一位优秀的程序员)和我对某些代码产生了怀疑。没有错误报告,没有确凿的证据,只是一种预感。因此,我们进行了审查。
果然,有一个错误。但是,它只会触发 模糊 边缘情况。我们认为对于客户来说太晦涩难懂了,因为我们在我们的实验室里从来没有真正亲眼见过它,尽管我们所有测试视频剪辑。我们仅通过代码审查发现了它。但是,“错误就是错误”,所以我开始修复。
大约两周后,我们主要客户的客户支持代表报告说他们看到的视频中存在间歇性失真[大约每 2-3 天一次]。 p>
这种失真可能来自我正在处理的错误吗?我还没有完全修复,但是到那时,我确实有一个可以生成错误的单元测试。在实验室中,我为代表触发了它。他说:“是的,就是这样——客户看到的失真”
该错误是在大约 3 个月前引入的。客户只是有与我们不同的视频,这使得错误发生的可能性更大。
通过积极主动,我们能够 [诚实地] 告诉客户我们“已经做好了准备”,并且我们将解决问题的周转时间缩短了两周。两者都让我们深受他们喜爱[即他们从我们这里购买了更多装备]。
【讨论】:
我真的不同意你在每一行都添加评论。那是无端的噪音。我真的相信汇编应该像其他所有语言一样被注释和缩进。 @KonradLindenbach 你不同意(3)。但是,你有没有注意到第二句话有时会离开它们?我在 40 多年前编写了我的第一个 asm pgm。我也看过很多我没有写的。大多数时候,asm is 侧边栏被注释掉了。 IBM 在 asm 中完成了整个大型机操作系统。而且,他们评论了每一行[我什至做过一个]。一个好的 cmt [in any lang] 显示 intent (“什么/为什么”)。 asm 是“如何”。对于 asm,像 "set $t1 to value of 6" 这样的 cmt 没有用,但是 "x = 6" ["x" 应该记录在 fnc 的顶部 cmt 块中] 是有用的。 @KonradLindenbach 它允许在算法和代码之间进行“交叉检查”。 (例如)在上面,也许x = 6
是您想要的,但是,您选择了错误的 reg(即它应该是 $t2)。试图从 asm(即)实现中“判断”意图可能会出现问题。在更高级别的语言中,代码需要在代码和 cmts 之间取得平衡(即更容易从代码中看到意图)。但是,您不能为寄存器提供更具描述性的名称 [不使用 .eqv cur $s4
--我不建议这样做,因为 reg 使用过于本地/瞬态],因此显示控制/算法流的 cmts 帮助
@CraigEstey 哇,这不仅回答了我的问题——它还教会了我更多关于如何在 asm 中进行编程的知识。我同意逐行 cmets,因为它可以帮助我准确了解我打算访问的寄存器(或在前一种情况下的混乱)。还有一个旁注,我非常感谢您的回复的乐于助人和彻底的性质。看到一个有经验的人对我这样的新手如此鼓舞,真是令人耳目一新:)以上是关于MIPS链表的主要内容,如果未能解决你的问题,请参考以下文章