Comp 210 Lab 12: JAM; for-Loops and Structures in C

Table of Contents

  • Running the JAM machine
  • for loops
  • Structures in C

  • Running the Jam2000 Machine

    To start the Jam2000 simulator, evaluate the expression (load "/home/comp210/Projects/Jam/jam.ss"). Scheme's load function just processes the contents of the indicated file. You can take a look at the file jam.ss; it is just Scheme code. (Though all it does is load some other files; it might be more interesting to look at, say, jam-cpu.ss.)

    Loading jam.ss defines the simulator code, and starts it running as well. You'll see:

    [00000]:  00000000 (halt) %
    
    The first number in brackets is the address of the "current" memory bucket (0 in this case). The next number is the contents of the bucket (initially 0). If this corresponds to an encoded instruction, then the decoded (assembly) version follows in parentheses. Finally, the % is the prompt for the Jam2000 simulator. The ``current'' memory bucket is the one named by the PC, the in general prompt should be interpreted as
    [PC]:  mem[PC]  (disassemble mem[PC]) %
    
    where ``mem[PC]'' means ``take the number stored in register PC, interpret it as a memory address, and fetch the contents of the indicated memory location.'' For example, mem[17] denotes the contents of the 17th memory location (which might happen to be 4321).

    Here is a short Jam2000 program which adds 8 and 14:

    +00000849      ; (ldi  4 8)  ; Load  +8 into registor 4
    +00001439      ; (ldi 14 3)  ; Load +14 into register 3
     00034510      ; (add 5 4 3) ; R5 <- R4 + R3
    
    Type each of these three encoded instructions in response to a prompt. You can omit leading zeroes and the +. Notice how the display updates: every time you type a number, that number is placed into mem[PC], and the PC is incremented by 1. (This is just the process of the Jam2000 simulator interface, not the Jam2000 machine itself.) Thus you are placing these instructions into mem[0], mem[1], mem[2].

    Type ? for a list of further Jam commands. From this you can find out that typing m will ask you for a memory range, and then print those memory locations. Print the contents of memory locations 0 through 15. Similarly, press s to see the state of the PC and registers.

    Now, we'll execute the program we just typed in above. How to do that? Yep, like the help screen said, type x for "execute". Nothing seemed to happen (even after looking at the status again). Well, one small change--the program counter stepped from 4 (instruction "halt") to 5. What happened? Recall from class, what the CPU does repeatedly:

    1. Fetch the number mem[PC].
    2. Decode the number, since it will be interpreted as an encoded instruction.
    3. Increment the PC by 1.
    4. Perform the previously fetched/decoded instruction.
    So what happened when we typed x above? (Hint: What was the PC?) Aha! Try setting the PC to 0 (How? See the help screen), and then execute the program. To check if it was successful, re-examine the status of the registers.

    Exercise: Translate the following Jam assembly code into machine code starting at location 2000, and execute it. (You'll want to use the handy JAM reference card (ps), also available from the "Readings" link from the class page.) This program calculates 1 + 2 + 3 + ... + 10; blank lines and labels are solely to improve readability.

                          ; r0 will contain "i", which will count 1..10
                          ; r1 will contain "max", which is 10
                          ; r2 will contain "sumSoFar"
                          ; r3 will contain the constant 1
             (ldi 0 1)    ; i <-- 1
             (ldi 1 10)   ; max <-- 10
             (ldi 2 0)    ; sumSoFar <-- 0
             (ldi 3 1)    ; r3 <-- 1
             
             loop:
             (sub 4 0 1)  ; compare i with max (well, compute i-max)
             (bgz 4 done) ; if i-max > 0, branch to label "done"
             (add 2 2 0)  ; sumSoFar = sumSoFar + i
             (add 0 0 3)  ; i <-- i + 1
             (jmpi loop)  ; go back and repeat
             
             done:
             (print 2)    ; print sumSoFar
             (halt)
    
    Exercise for those who finish the above quickly:
    Modify the above to sum the cubes of 1..10.

    To quit the simulator, type q, and you will be back at the Dr. Scheme prompt.


    for-Loops in C

    The remainder of the lab is not closely correlated with lecture, and is not intended to be. It is showing you some additional features of C which are niced to know, but are distracting from the key ideas of lecture.

    You've seen while loops in class, and have probably already compiled some code using them. If you've looked closely at the Extended C Primer, you may have also read a little about a nifty syntax for a special case of a while loop: the for loop.

    Here is a simple for loop in C:

    int sum = 0;
    for ( int i = 1;  i <= 10;  i = i + 1 ) {
      sum = sum + i*i*i;
      }
    cout "The sum of the cubes of 1 through 10 is " << sum << ".\n";
    
    Here's what the code means:
    1. Initially, create a variable i and initialize it to 0.
    2. if i ≤ 10, then perform the body of loop (inside the braces), and
    3. do i = i+1. Now go back to Step 2.
    Whenever, in Step 2, the condition i <= 10 is false, we leave the loop. Note that as written, the scope of the placeholder i is inside the loop only.

    Syntax of C's for

    The general syntax of a for statement is:
    for ( init; test; end-of-loop-update )
       action
    moreCode...
    
    1. init is performed just once, before anything else in the for loop.
    2. if test is false, we're done; exit the loop and continue with moreCode.... But if test is true, then perform action, and
    3. do the end-of-loop-update. Go back to Step 2.
    A couple of notes: Yes, you must have exactly two semicolons in the for statement, separating the three parts.
    The action can either be:
  • a single statement followed by a semicolon, or
  • an open-brace, {, followed by many statements followed by } (but no semicolon after the close-brace). Think of it as a begin. It doesn't hurt to always include the curly-braces even when you have only statement inside (as was done above).
  • (This same specification for an action also applies to the bodies of while loops, if expressions, etc.)

    Though obvious, it's worth noting: Anything that can be written with a for loop could be translated into a while loop instead. And vice-versa: if somehow your C compiler stopped recognizing while loops, you wouldn't be too put out because you could write for loops. So in some sense adding the second construct to the language doesn't gain anything beyond convenience and clarity.

    Questions to ponder: what is the smallest subset of C which could still be equivalent to having all of the language? (How about Scheme, which doesn't have while or for built-in? How about Jam, which doesn't even have function-calls, much less recursion?) If some sets of commands might be more powerful than other sets, can we be sure that Scheme (or C) has all the possible commands we might ever want?

    Exercise: Write a C function printDots which takes in a number n, and prints that many dots on the screen.

    Note that if you were going to translate the above function into Scheme, the loop should correspond to tail-recursion.

    Exercise: Write a C function pyramid which takes a number m, and prints

    1. a line with m dots, followed by
    2. a line with m-1 dots, followed by
    3. a line with m-2 dots, followed by
      ...
    4. a line with one dot.
    (Hint: use printDots that you've already written.)

    Structures in C

    The Scheme structure
    (define-structure (vehicle idNum numWheels price))  
    
    (define rig (make-vehicle 4713  18  9999.99))    
    
    translates into C as follows.
    struct Vehicle {
        int idNum;
        int numWheels;
        float price;
        };
    
    int main() {
      Vehicle* rig = new Vehicle;
      rig->idNum = 4713;
      rig->numWheels = 18;
      rig->price = 9999.99;
    
      return 0;
      }
    
    The first statement, starting
    struct Vehicle { ...
    is analagous to Scheme's define-structure. It creates a new type, named "Vechicle*".

    Let's dissect the second part of this, which happens to be inside the function main().

    1. Vehicle* rig
      This declares the placeholder rig, which is of type Vehicle*. Hey, what is that star doing there, as part of the name Vechicle*? Think of it as telling C "this is something that gets treated with those special hat-variables, when evaluating". So using the star makes structures behave in C the same way they did in Scheme. Include the star only when defining placeholders (or giving them as formal parameters), not when you use the placeholder rig anywhere else! (And as in the example, don't use the star in the new statement, next.)
    2. ...rig = new Vehicle;
      The new operator actually creates the structure (like Scheme's make-struct), and we assign that value to the placeholder rig.

    3. rig->idNum
      is the idNum field inside the structure referred to by rig. It is the analog of Scheme's (vehicle-idNum rig)

    4. rig->idNum = 4713;
      When on the left hand side of the ``gets'' operator =, this changes the value of rig->idNum. So the line here is actually the analog of set-vehicle-idNum!.
    Task: Copy the above code into your own program, use cout to print out the rig's price, compile and run.

    A word of caution: if declaring several placeholders to hold structures, you must use a star when defining each one:

    Vehicle* mercedes, bmw;                   // NOT what you want (though legal)
    Vehicle *rig, *trike;                     // Better.
                                              // Notice that this declares
                                              // without giving any initial value.
    rig = new Vehicle;                        // We'd better provide a value
    trike = new Vehicle;                      // before using these variables!
    
    trike->numWheels = 3;
    rig->numWheels   = 6 * trike->numWheels;  // (this star just means multiply!)
    ...
    
    We will not discuss what the mercedes/bmw line would do, at least not until later.

    Passing Structures as Arguments

    When passing a structure to a function, remember to use * when specifying the type:
    /* cooler( Vehicle* x, Vehicle* y )
     * Return true iff x is more cool than y
     */
    bool cooler( Vehicle* x, Vehicle* y ) {
    
      // More wheels make a vehicle cool.
      // Ties are decided by which costs the least.
    
      if (x->numWheels != y-> numWheels)
        return (x->numWheels > y->numWheels);
      else
        return (x->price > y->price);
      }
    
    This is really just following the same rule when declaring variables: always use "*" with the struct's name, except when using "new".

    Advanced question: (Well, advanced in the sense that we won't discuss this in lecture until next week, with set-structure-field!.) Try the following code, and venture an explanation as to what's going on.

    Vehicle *trike = new( Vehicle );
    Vehicle *rig;
    rig = trike;
    trike->numWheels = 3;
    rig->numWheels   = 6 * trike->numWheels;  // this star just means multiply!
    
    What is the value of rig->numWheels? How about trike->numWheels?

    For discussion of what's going on, you can look here.

    If you have any questions on things you've skimmed in the C Primer, or bizarro "what-if"s, ask away!


    Back to Comp 210 Home