MIPS链表

MIPS linked list

我对如何在 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

我应该从这里去哪里?我的代码进行了汇编,但在读取两个输入后没有执行任何操作。

您的评论水平、风格和程序布局都做得很好。我在 SO 上看到的一些最好的。总的来说,努力了。

但是,存在 个错误。

我已经制作了您代码的注释版本和重构版本。见下文。


整个代码中存在许多错误,与结构或插入代码无关[至少有 26 个此类错误]。

经常重复错误的指令。这通常是想用一个常量设置一个寄存器。您的代码使用(例如)addi $t3,$t3,7 而不是正确的 addi $t3,$zero,7。我用 li $t3,7 替换了那些。在一些地方,你 确实 使用了正确的版本,所以我称之为 "typo" 错误,但有一个 lot 其中。

您的 strcmp 只比较了第一个字符而不是整个字符串。

实际的插入代码有点复杂,比需要的复杂得多(例如 insert_here 没有使用)。它也有一些严重的 logic/implementation 错误。你走在正确的轨道上,但是,在修复了许多其他不相关的错误之后,我决定重写它而不是尝试修补它。


这是带注释的版本[针对 SO space 限制进行了精简],其中我修复了大部分单行错误[用 "BUG" 注释],但代码仍然是 not 还可以运行并且 no 修复了 struct/insert 逻辑。我试图忠实于您的原始代码[请原谅无偿的样式清理]:

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) 一般规则是在 行 [你所做的] 上添加侧边栏注释。这就是我所做的,主要是。但是,对于一些很好理解的样板,注释可能有点矫枉过正 [并且实际上可能会影响可读性]。

例如,为函数建立堆栈帧的代码和在函数退出时从该帧恢复的代码很好理解。因此,也许顶部的单个块评论像 # set up stack frame 用于几行,而底部的 # restore from stack frame 而不是对每个 inst

的评论

(4) 保持边栏评论简短,以便它们适合 80 列。如果您需要更多,请将注释提升到说明上方的完整行块注释[并根据需要使用任意多行]

(5) 对于 difficult/complex 东西,可以使用伪代码或实际 C [或您选择的语言] 来制作原型。这是一个合理的假设,即任何编写 [或阅读] asm 代码的人都熟悉至少一种高级语言 [C 是最有可能的]。

对于结构代码,我在顶部块注释中添加了一个 C 结构定义。在 insert 例程中,我在顶部注释块中添加了 C 伪代码。 asm的侧边栏注释经常引用伪代码中的符号和动作

实际上,即使对于更简单的函数,进行这种原型设计也是有好处的[即使您不将代码添加为注释]。 (例如)它可能对您编写 strcmp

有所帮助

(6) 使代码尽可能简单。当代码不必要地复杂时,很容易在程序逻辑中引入错误或使用不正确的指令来实现该逻辑。这也使得以后很难发现这些错误。

(例如)在某些情况下,您的代码正在加载一个寄存器,只是稍后必须移动它。因此,在只需要一个的情况下使用 2-3 个 insts。尽量减少不必要的寄存器移动[不仅是为了速度,也是为了简化代码]。

例如,您的 strcmp 有 24 行,并且只比较了第一个字符 [即一个错误],并有几个分支。我的版本只有12行,全字符串比较,简单循环

同样,在您的插入代码中,insert_here [未使用] 是 17 行,insert 是 47 行,总共 64 行。我的工作版本是 31 行。

注意: 我使用 .eqv 伪操作到 "define" 结构偏移量。我使用 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

更新:

I agree with the line-by-line comments, as it helps me understand exactly what registers I intended to access(or messup in the previous case).

理由很简单。想象一下相反的情况:一个大型 [5000 行] asm 文件,其中 没有 注释,已知存在错误。你不能相信 logic/algorithm [它可能有错误]。你不能相信实现 [它可能有错误]。每当我遇到这样的情况时,我都会按照 before 中的描述添加注释,甚至会寻找错误(即我正在学习算法和代码,因为我这样做)。

这是在进行代码审查。即使该文件已经有评论 [就像你的那样],我也会这样做。我经常对我刚刚编写的代码进行审查,我认为它是 "complete" [即所有需要编写的代码,都已经完成了。

如果我今天完成得晚,我会把复习作为第二天的第一件事。通常,"sleeping on it" 可以很容易地发现我 不会 在前一天的评论中发现的错误 [因为我仍然 "too close" 这个问题].如果我正在写的东西要花好几天,我总是首先回顾前一天的工作。

即使您的原始评论类似于 (2),它也帮助我找到了您的 "typo" 错误。当我看到 add $t1,$t1,$zero #get zero 时,注释不匹配使得 find/fix 很容易。使用调试器单步执行代码会困难 10 倍。

评论总是帮助。最初编码 insert 时,我有:

    jal     strcmp                  # compare them -- new < cur?
    bgtz    $v0,insert_now          # if yes, insert after prev

我得到了从高到低的输出。

起初,我怀疑 strcmp 因为它 [同样] 是新代码。在查看对它的调用之前,我通常会查看较低级别的函数。我对 strcmp 进行了代码审查,看起来还不错。但是,我仍然不相信。我为 strcmp 写了一些诊断代码 [单元测试],但它通过了。

然后,我注意到上面 insert 中的注释与代码。修复很简单:将 bgtz 更改为 bltz.

另一个好的规则是:不要破坏[将错误引入]现有[工作]代码。

当我第一次查看 print_list 时,我认为:"it's using [and trashing] s0"。这没关系,因为在调用它之后,程序正在终止。但是,如果它被多次调用则不会。我 错过了 saving/restoring s0 在堆栈上的事实 [我在 阅读].

Its refreshing to see an experienced be so encouraging to a newbie like me

我们都曾是新手。没有伤害,没有犯规。

即使是经验丰富的程序员也会产生错误[有时,每天多个错误]。错误 不是 对 soul/character 的控诉。错误只是作为程序员的正常副产品[就像finding/fixing一样]。

IMO,关键是:

(1) 愿意学习

(2) 承认错误(例)肯尼迪总统[在"Bay of Pigs"]之后:"Mistakes are not errors, if we admit them"

(3) 最重要的是,将 ego 排除在外。

最弱 的程序员,当报告错误 [或疑似错误] 时:

(1) 说他们的代码工作正常[甚至没有检查错误报告是否有价值]。

(2) 拒绝该错误,因为它不是真正的错误 [当它是]

(3) 拒绝生成[或接受] can/do prove/disprove 错误

的测试用例

(4) [故意] "slow" 产生错误修复 [它没有新代码那么有趣]

(5) 在闲暇时间,拒绝"clean up" 有效但 结构不佳的代码[ 应该重构]

(6) 不屑对待顾客[即"they're all idiots"]

IMO,优秀 程序员,另一方面,主动 当涉及到错误[或潜在 个错误。

在我所在的一家公司,我们有一个没有明显错误的运输产品[实时视频处理平台]。我们正处于闲暇时光。但是,我的老板 [他也是一位优秀的程序员] 和我对某些代码产生了怀疑。没有错误报告,没有确凿的证据,只有直觉。因此,我们进行了审核。

果然,一个错误。但是,它只会触发 模糊 边缘情况。我们认为它对客户来说太过晦涩难懂,因为 我们 从未 在我们的实验室中亲眼见过它,尽管 [=161] =]all 我们的测试视频剪辑。我们 通过代码审查找到了它。但是,"bugs are bugs",所以我开始修复。

大约两周后,我们 主要 客户的客户支持代表报告说他们看到的视频出现间歇性失真 [大约每 2-3 次一次天].

这种失真可能来自我正在处理的错误吗?我还没有完全修复,但是到那时,我确实 有一个可以生成错误的单元测试。在实验室中,我为代表触发了它。他说:"Yes, that's it--the distortion the client is seeing"

该错误大约在 3 个月前引入。客户的视频与我们的不同,这使得错误发生的可能性更大。

通过积极主动,我们能够 [诚实地] 告诉客户我们 "already on top of it" 并且我们将修复客户的周转时间缩短了两周。两者都让我们很喜欢他们[即。他们从我们这里买了更多的装备。