home *** CD-ROM | disk | FTP | other *** search
/ The Unsorted BBS Collection / thegreatunsorted.tar / thegreatunsorted / programming / asm_programming / CHAP11-1.DOC < prev    next >
Text File  |  1990-06-25  |  25KB  |  618 lines

  1.  
  2.  
  3.  
  4.                                                                            104
  5.  
  6.                        CHAPTER 11 - ADDRESSING MODES AND POINTERS
  7.  
  8.  
  9.              In this chapter we are going to cover all possible ways of
  10.              getting data to and from memory with the different addressing
  11.              modes. Read this carefully, since it is likely this is the only
  12.              time you will ever see ALL addressing possibilities covered. 
  13.  
  14.              The easiest way to move data is if the data has a name and the
  15.              data is one or two bytes long. Take the following data:
  16.  
  17.              ; -----
  18.              variable1 dw  2000
  19.              variable2 db  -26
  20.              variable3 dw  -589
  21.              ; -----
  22.  
  23.              We can write:
  24.  
  25.                  mov  variable1, ax
  26.                  mov  cl, variable2
  27.                  mov  si, variable3
  28.  
  29.              and the assembler will write the appropriate machine code for
  30.              moving the data. What can we do if the data is more than two
  31.              bytes long? Here is some more data:
  32.  
  33.              ; -----
  34.              variable4 db  "This is a string of ascii data."
  35.              variable5 dd  -291578
  36.              variable6 dw  600 dup (-11000)
  37.              ; -----
  38.  
  39.              Variable4 is the address of the first byte of a string of ascii
  40.              data. Variable5 is a single piece of data, but it won't fit into
  41.              an 8086 register since it is 4 bytes long. Variable6 is a 600
  42.              element long array, with each element having the value -11000. In
  43.              order to deal with these, we need pointers.
  44.  
  45.              Some of you will be flummoxed at this point, while those who are
  46.              used to the C language will feel right at home. A pointer is
  47.              simply the address of a variable. We use one of the 8086
  48.              registers to hold the address of a variable, and then tell the
  49.              8086 that the register contains the address of the variable, not
  50.              the variable itself. It "points" to a place in memory to send the
  51.              data to or retrieve the data from. If this seems a little
  52.              confusing, don't worry; you'll get the hang of it quickly. 
  53.  
  54.              As I have said before, the 8086 does not have general purpose
  55.              registers. Many instructions (such as LOOP, MUL, IDIV, ROL) work
  56.              only with specific registers. The same is true of pointers. You
  57.              may use only  BX, SI, DI, and BP as pointers. The assembler will
  58.              give you an error if you try using a different register as a
  59.              pointer.
  60.  
  61.              ______________________
  62.  
  63.              The PC Assembler Tutor - Copyright (C) 1989 Chuck Nelson
  64.  
  65.  
  66.  
  67.  
  68.              Chapter 11 - Addressing Modes                                 105
  69.              _____________________________
  70.  
  71.  
  72.  
  73.              There are two ways to put an address in a pointer. For variable4,
  74.              we could write either:
  75.  
  76.                  lea  si, variable4
  77.  
  78.              or:
  79.  
  80.                  mov  si, offset variable4
  81.  
  82.              Both instructions will put the offset address of variable4 in
  83.              SI.{1} SI now 'points' to the first byte (the letter 'T') of
  84.              variable4. If we wanted to move the third byte of that array
  85.              (the letter 'i') to CL, how would we do it? First, we need to
  86.              have SI point to the third byte, not the first. That's easy:
  87.  
  88.                  add  si, 2
  89.  
  90.              But if we now write:
  91.  
  92.                  mov  cl, si
  93.  
  94.              we will generate an assembler error because the assembler will
  95.              think that we want to move the data in SI (a two byte number) to
  96.              CL (one byte). How do we tell the assembler that we are using SI
  97.              as a pointer? By enclosing SI in square brackets:
  98.  
  99.                  mov  cl, [si]
  100.  
  101.              since CL is one byte, the assembler assumes you want to move one
  102.              byte. If you write:
  103.  
  104.                  mov  cx, [si]
  105.  
  106.              then the assembler assumes that you want to move a word (two
  107.              bytes). The whole thing now is:
  108.  
  109.                  lea  si, variable4
  110.                  add  si, 2
  111.                  mov  cl, [si]
  112.  
  113.              This puts the third byte of the string in CL. Remember, if a
  114.              register is in square brackets, then it is holding the ADDRESS of
  115.              a variable, and the 8086 will use the register to calculate where
  116.              the data is in memory.
  117.  
  118.              What if we want to put 0s in all the elements of variable6?
  119.              ____________________
  120.  
  121.                 1 LEA stands for load effective address. Note that with LEA,
  122.              we use only the name of the variable, while with:
  123.  
  124.                  mov  si, offset variable4
  125.  
  126.              we need to use the word 'offset'. The exact difference between
  127.              the two will be explained later.
  128.  
  129.  
  130.  
  131.  
  132.              The PC Assembler Tutor                                        106
  133.              ______________________
  134.  
  135.              Here's the code:
  136.  
  137.                       mov  bx, offset variable6
  138.                       mov  ax, 0
  139.                       mov  cx, 600
  140.                  zero_loop:
  141.                       mov  [bx], ax
  142.                       add  bx, 2
  143.                       loop zero_loop
  144.  
  145.              We add 2 to BX each time since each element of variable6 is a
  146.              word (two bytes) long. There is another way of writing this:
  147.  
  148.                       mov  bx, offset variable6
  149.                       mov  cx, 600
  150.                  zero_loop:
  151.                       mov  [bx], 0
  152.                       add  bx, 2
  153.                       loop zero_loop
  154.  
  155.              Unfortunately, this will generate an assembler error. Why? If the
  156.              assembler sees:
  157.  
  158.                       mov  [bx], ax
  159.  
  160.              it knows that you want to move what is in AX to the address in
  161.              BX, and AX is one word (two bytes) long so it generates the
  162.              machine code for a word move. If the assembler sees:
  163.  
  164.                       mov  [bx], al
  165.  
  166.              it knows that you want to move what is in AL to the address in
  167.              BX, and AL is one byte long, so it generates the machine code for
  168.              a byte move. If the assembler sees:
  169.  
  170.                       mov  [bx], 0
  171.  
  172.              it doesn't know whether you want a byte move or a word move. The
  173.              8086 assembler has implicit sizing. It is the assembler's job to
  174.              look at each instruction and decide whether you want to operate
  175.              on a byte or a word. Other microprocessors do things differently.
  176.              On the Motorola 68000, the assembler uses explicit sizing. Each
  177.              instruction must explicitly state whether it is a byte or a
  178.              word.{2} On the 68000 you have:
  179.  
  180.                       move.b    #213, (A1)
  181.                       move.w    #213, (A1)
  182.  
  183.              The first instruction says to move a byte (the number 213) to the
  184.              address in register A1 while the second instruction says to move
  185.  
  186.  
  187.  
  188.              ____________________
  189.  
  190.                 2 Any of you who use the 68000 assembler know that this is
  191.              fudging the facts a little bit.
  192.  
  193.  
  194.  
  195.  
  196.              Chapter 11 - Addressing Modes                                 107
  197.              _____________________________
  198.  
  199.              a word (the number 213) to the address in register A1.{3} 
  200.  
  201.              Back to the 8086. If the 8086 assembler looks at an instruction
  202.              and it can't tell whether you want to move a byte or a word, it
  203.              generates an error. When you use pointers with constants, you
  204.              should explicitly state whether you want a byte or a word. The
  205.              proper way to do this is to use the reserved words BYTE PTR or
  206.              WORD PTR.
  207.  
  208.                       mov  [bx], BYTE PTR 213
  209.                       mov  [bx], WORD PTR 213
  210.  
  211.              These stand for byte pointer and word pointer respectively. I
  212.              find this terminology exceptionally clumsy, but that's life.
  213.              Whenever you are moving a constant with a pointer, you should
  214.              specify either BYTE PTR or WORD PTR.
  215.  
  216.              The Microsoft assembler makes some assumptions about the size of
  217.              a constant. If the number is 256 or below (either positive or
  218.              negative), you MUST explicitly state whether it is a byte or a
  219.              word operation. If the number is 257 or above (either positive or
  220.              negative), the assembler assumes that you want a word operation.
  221.  
  222.              Here's the previous code rewritten correctly:
  223.  
  224.  
  225.                       mov  bx, offset variable6
  226.                       mov  cx, 600
  227.                  zero_loop:
  228.                       mov  [bx], WORD PTR 0
  229.                       add  bx, 2
  230.                       loop zero_loop
  231.  
  232.              Let's add 435 to every element in the variable6 array:
  233.  
  234.                       mov  bx, offset variable6
  235.                       mov  cx, 600
  236.                  add_loop:
  237.                       add  [bx], WORD PTR 435
  238.                       add  bx, 2
  239.                       loop add_loop
  240.  
  241.              How about multiplying every element in the array by 12?
  242.  
  243.                       mov  di, offset variable6
  244.                       mov  cx, 600
  245.                       mov  si, 12
  246.                  mult_loop:
  247.                       mov  ax, [di]
  248.                       imul si
  249.                       mov  [di], ax
  250.                       add  di, 2
  251.                       loop mult_loop
  252.  
  253.              ____________________
  254.  
  255.                 3 A1 is a 68000 register.
  256.  
  257.  
  258.  
  259.  
  260.              The PC Assembler Tutor                                        108
  261.              ______________________
  262.  
  263.              None of these examples did any error checking, so if the result
  264.              was too large, the overflow was ignored. This time we used DI for
  265.              a change of pace. Remember, we may use BX, SI, DI or BP, but no
  266.              others. You will notice that in all these examples, we started at
  267.              the beginning of the array and went step by step through the
  268.              array. That's fine, and that's what we normally would do, but
  269.              what if we wanted to look at individual elements? Here's a sample
  270.              program:
  271.  
  272.              ; + + + + + START DATA BELOW THIS LINE
  273.              ; 
  274.              poem_array  db "She walks in Beauty, like the night"
  275.                          db "Of cloudless climes and starry skies;"
  276.                          db "And all that's best of dark and bright"
  277.                          db "Meet in the aspect ratio of 1 to 3.14159"
  278.              character_count  db  149
  279.              ; + + + + + END DATA ABOVE THIS LINE
  280.  
  281.              ; + + + + + START CODE BELOW THIS LINE
  282.  
  283.                  mov  bx, offset poem_array
  284.                  mov  dl, character_count
  285.  
  286.              character_loop:
  287.                  sub  ax, ax              ; clear ax
  288.                  call get_unsigned_byte
  289.                  dec  al                  ; character #1 = array[0]
  290.                  cmp  al, dl              ; out of range?
  291.                  ja   character_loop      ; then try again
  292.                  mov  si, ax              ; move char # to pointer register
  293.                  mov  al, [bx+si]         ; character to al
  294.                  call print_ascii_byte
  295.                  jmp  character_loop
  296.  
  297.              ; + + + + + END CODE ABOVE THIS LINE
  298.  
  299.              You enter a number and the program prints the corresponding
  300.              character. Before starting, we put the array address in BX and
  301.              the maximum character count in DL. After getting the number from
  302.              get_unsigned_byte, we decrement AL since the first character is
  303.              actually poem_array[0]. The character count has been reduced by 1
  304.              to reflect this fact. It also makes 0 an illegal entry. Notice
  305.              that the program checks to make sure you don't go past the end of
  306.              the poem. This time we use BX to mark the beginning of the array
  307.              and SI to count the number of the character.
  308.  
  309.              Once again, there are only specific combinations of pointers that
  310.              can be used. They are:
  311.  
  312.                  BX with either SI or DI (but not both)
  313.                  BP with either SI or DI (but not both)
  314.  
  315.              My version of the Microsoft assembler (v5.1) recognizes the forms
  316.              [bx+si], [si+bx], [bx][si], [si][bx], [si]+[bx] and [bx]+[si] as
  317.              the same thing and produces the same machine code for all six.
  318.  
  319.  
  320.  
  321.  
  322.  
  323.  
  324.              Chapter 11 - Addressing Modes                                 109
  325.              _____________________________
  326.  
  327.              We can get even more complicated, but to show that, we need
  328.              structures. In databases they are called records. In C they are
  329.              called structures; in any case they are the same thing - a group
  330.              of different types of data in some standard order. After the
  331.              group is defined, we usually make an array with the identical
  332.              structure for each element of the array.{4} Let's make a
  333.              structure for an address book.
  334.  
  335.                  last_name  db  15 dup (?)
  336.                  first_name db  15 dup (?)
  337.                  age        db  ?
  338.                  tel_no     db  10 dup (?)
  339.  
  340.              In this case, all the data is bytes, but that is not necessary.
  341.              It can be anything. Each separate piece of data is called a
  342.              FIELD. We have the last_name field, the first_name field, the age
  343.              field, and the tel_no field. Four fields in all. The structure is
  344.              41 bytes long. What if we want to have a list of 100 names in our
  345.              telephone book? We can allocate memory space with the following
  346.              definition:
  347.  
  348.                  address_book   db  100 dup ( 41 dup (' ')) {5}
  349.  
  350.              Well, that allocates room in memory, but how do we get to
  351.              anything? First, we need the array itself:
  352.  
  353.                  mov  bx, offset address_book
  354.  
  355.              Then we need one specific entry. Let's take entry 29 (which is
  356.              address_book[28]). Each entry is 41 bytes long, so:
  357.  
  358.                  mov  ax, 28    ; entry (less 1)
  359.                  mov  cx, 41    ; entry length
  360.                  mul  cx
  361.                  mov  di, ax    ; move to pointer
  362.  
  363.              That gives us the entry, but if we want to get the age, that's
  364.              not the first byte of the structure, it's the 31st byte (actually
  365.              address_book[28] + 30 since the first byte is at +0). We get it
  366.              by writing:
  367.  
  368.                  mov  dl, [bx+di+30]
  369.  
  370.              This is the most complex thing we have - two pointers plus a
  371.              constant. The total code is then:
  372.  
  373.                  mov  bx, offset address_book
  374.                  mov  ax, 28    ; entry (less 1)
  375.                  mov  cx, 41    ; entry length
  376.              ____________________
  377.  
  378.                 4 If you don't know about structures or records, now would be
  379.              a good time to stop and go to a reference book about them. They
  380.              are not actually covered here.
  381.  
  382.                 5 Nesting of dup statements is allowed. Rather than having
  383.              uninitialized data, this has blanks in all the spaces.
  384.  
  385.  
  386.  
  387.  
  388.              The PC Assembler Tutor                                        110
  389.              ______________________
  390.  
  391.                  mul  cx        ; entry offset from array[0]
  392.                  mov  di, ax    ; move entry offset to pointer
  393.                  mov  dl, [bx+di+30]  ; total address
  394.  
  395.              Though the machine code has only one constant in the code, the
  396.              assembler will allow you to put a number of constants in the
  397.              assembler instruction. It will add them together for you and
  398.              resolve them into one number.{6}
  399.  
  400.              Once again, there are a limited number of registers - they are
  401.              the same registers as before:
  402.  
  403.                  BX with either SI or DI (but not both) plus constant
  404.                  BP with either SI or DI (but not both) plus constant
  405.  
  406.              We can work with structures on the machine level, but it looks
  407.              like it's going to be hard to keep track of where each field is.
  408.              Actually, it isn't so bad because of:
  409.  
  410.                                OUR FRIEND, THE EQU STATEMENT
  411.  
  412.              The assembler allows you to do substitution. If you write:
  413.  
  414.                  somestuff EQU  37 * 44
  415.  
  416.              then every place that the assembler finds the word "somestuff",
  417.              it will substitute what is on the right side of the EQU. Is that
  418.              a number or text? Sometimes it's a number, sometimes it's text.
  419.              Here are four statements which are defined totally in terms of
  420.              numbers. This is from the assembler listing. (The assembler lists
  421.              how it has evaluated the EQU statement on the left after the
  422.              equal sign.)
  423.  
  424.  
  425.  
  426.                                  
  427.               = 0023               statement1 EQU  5 * 7 
  428.               = 0025               statement2 EQU  statement1 + 6 - 4  
  429.               = 000F               statement3 EQU  statement2 - 22 
  430.               = 001F               statement4 EQU  statement3 + 16 
  431.  
  432.              and the assembler thinks of these as numbers (these numbers are
  433.              in hex). Now in the next set, with only a minor change:
  434.  
  435.  
  436.               = [bp + 3]                    statement1 EQU  [bp + 3] 
  437.               = [bp + 3] + 6 - 4            statement2 EQU  statement1 + 6 - 4
  438.               = [bp + 3] + 6 - 4 - 22       statement3 EQU  statement2 - 22 
  439.              ____________________
  440.  
  441.                 6 And it does it quite well. The assembler correctly evaluated
  442.              the following:
  443.  
  444.                     add   ax, (-3*81)+44/8+[si+27]+6+[bx]-7+(43*96)-2 
  445.  
  446.              Not bad, huh?
  447.  
  448.  
  449.  
  450.  
  451.  
  452.              Chapter 11 - Addressing Modes                                 111
  453.              _____________________________
  454.  
  455.               = [bp + 3] + 6 - 4 - 22 + 16  statement4 EQU  statement3 + 16 
  456.               
  457.              the assembler thinks of it as text. Obviously, the fact that it
  458.              can be either may cause you some problems along the way. Consult
  459.              the assembler manual for ways to avoid the problem.
  460.  
  461.  
  462.              Now we have a tool to deal with structures. Let's look at that
  463.              structure again.
  464.  
  465.                  last_name  db  15 dup (?)
  466.                  first_name db  15 dup (?)
  467.                  age        db  ?
  468.                  tel_no     db  10 dup (?)
  469.  
  470.              We don't actually need a data definition to make the structure,
  471.              we need equates:
  472.  
  473.                  LAST_NAME      EQU  0
  474.                  FIRST_NAME     EQU  15
  475.                  AGE            EQU  30
  476.                  TEL_NO         EQU  31
  477.  
  478.              this gives us the offset from the beginning of each record. If we
  479.              again define:
  480.  
  481.                  address_book   db  100 dup ( 41 dup (' '))
  482.  
  483.               then to get the age field of entry 87, we write:
  484.  
  485.                  mov  bx, offset address_book
  486.                  mov  ax, 86    ; entry (less 1)
  487.                  mov  cx, 41    ; entry length
  488.                  mul  cx        ; entry offset from array[0]
  489.                  mov  di, ax    ; move entry offset to pointer
  490.                  mov  dl, [bx+di+AGE]  ; total address
  491.  
  492.              This is a lot of work for the 8086, but that is normal with
  493.              complex structures. The only thing that takes a lot of time is
  494.              the multiplication, but if you need it, you need it.{7}
  495.  
  496.              How about a two dimensional array of integers, 60 X 40
  497.  
  498.                  int_array  dw  40 dup  ( 60 dup ( 0 ))
  499.  
  500.              These are initialized to 0. For our purposes, we'll assume that
  501.              the first number is the row number and the second number is the
  502.              column number; i.e. array [6,13] is row 6, column 13. We will
  503.              have 40 rows of 60 columns. For ease of calculation, the first
  504.              array element is int_array [0,0]. (If it is your array, you can
  505.  
  506.  
  507.  
  508.  
  509.              ____________________
  510.  
  511.                 7 You will see more of the EQU statement.
  512.  
  513.  
  514.  
  515.  
  516.              The PC Assembler Tutor                                        112
  517.              ______________________
  518.  
  519.              set it up any way you want {8}). Each row is 60 words (120 bytes)
  520.              long. To get to int_array [23, 45] we have:
  521.  
  522.                  mov  ax, 120   ; length of one row in bytes
  523.                  mov  cx, 23    ; row number
  524.                  mul  cx
  525.                  mov  bx, ax    ; row offset to bx
  526.                  mov  si, 45    ; column offset
  527.                  sal  si, 1     ; multiply column offset by 2 (for word size)
  528.                  mov  dx, [bx+si]    ; integer to dx
  529.  
  530.              Using SAL instead of MUL is about 50 times faster. Since most
  531.              arrays you will be working with are either byte, word, or double
  532.              word (4 bytes) arrays, you can save a lot of time. Let
  533.              ELEMENT_NUMBER be the array number (starting at 0) of the desired
  534.              element in a one-dimensional array. For byte arrays, no
  535.              multiplication is needed. For a word:
  536.  
  537.                  mov  di, ELEMENT_NUMBER
  538.                  sal  di,1      ; multiply by 2
  539.  
  540.              and for a double word (4 bytes):
  541.  
  542.                  mov  di, ELEMENT_NUMBER
  543.                  sal  di, 1
  544.                  sal  di, 1     ; multiply by 4
  545.  
  546.              This means that a one-dimensional array can be accessed very
  547.              quickly as long as the element length is a power of 2 - either 2,
  548.              4 or 8. Since the standard 8086 data types are all 1, 2, 4, or 8
  549.              bytes long, one dimensional arrays are fast. Others are not so
  550.              fast.
  551.  
  552.              As a quick review before going on, these are the legal ways to
  553.              address a variable on the 8086:
  554.  
  555.                  (1) by name.
  556.  
  557.                            mov  dx, variable1
  558.  
  559.                  It is also possible to have name + constant.
  560.  
  561.                            mov  dx, variable1 + 27
  562.  
  563.                  The assembler will resolve this into a single offset number
  564.                  and will give the appropriate information to the linker.
  565.  
  566.                  (2) with the single pointers BX, SI, DI and BP (which are
  567.                  enclosed in square brackets).
  568.  
  569.                            mov  cx, [si]
  570.              ____________________
  571.  
  572.                 8 Bearing in mind that all compiled languages have fixed
  573.              formats for arrays. If you want your array to interact with C,
  574.              Fortran, Pascal or Basic, you'd better be sure you have the right
  575.              format.
  576.  
  577.  
  578.  
  579.  
  580.              Chapter 11 - Addressing Modes                                 113
  581.              _____________________________
  582.  
  583.                            xor  al, [bx]
  584.                            add  [di], cx
  585.                            sub  [bp], dh
  586.  
  587.                  (3) with the single pointers BX, SI, DI and BP (which are
  588.                  enclosed in square brackets) plus a constant.
  589.  
  590.                            mov  cx, [si+421]
  591.                            xor  al, 18+[bx]
  592.                            add  93+[di]-7, cx
  593.                            sub  (54/7)+81-3+[bp]-19, dh
  594.  
  595.                  (4) with the double pointers [bx+si], [bx+di], [bp+si],
  596.                  [bp+di]  (which are enclosed in square brackets).
  597.  
  598.                            mov  cx, [bx][si]
  599.                            xor  al, [di][bx]
  600.                            add  [bp]+[di], cx
  601.                            sub  [di+bp], dh
  602.  
  603.                  (5) with the double pointers [bx+si], [bx+di], [bp+si],
  604.                  [bp+di]  (which are enclosed in square brackets) plus a
  605.                  constant.
  606.  
  607.                            mov  cx, [bx][si+57]
  608.                            xor  al, 45+[di+23][bx+15]-94
  609.                            add  [bp]+[di]-444, cx
  610.                            sub  [6+di+bp]-5, dh
  611.  
  612.              These are ALL the addressing modes allowed on the 8086. As for
  613.              the constants, it is the ASSEMBLER'S job to resolve all numbers
  614.              in the expression into a single constant. If your expression
  615.              won't resolve into a constant, it is between you and the
  616.              assembler. It has nothing to do with the 8086 chip. 
  617.  
  618.