Final exam

Due 5:00PM Wed, March 15, 2023

Submit via Moodle as final.tar. Include your answers in final.pdf or final.txt, with source code (if any) in appropriately named C files.

There are a few "draw a diagram" tasks below. If you wish, feel free to draw your diagrams by hand and include images in your PDF submission. If you do so, please make sure the images are legible.

This is a takehome exam. You may use your textbook and online sources, as long as you cite them clearly. You may not speak with anyone other than Jeff Ondich about the content of this exam. That means in particular that you may not talk with your classmates or post questions on online forums. You may, however, ask questions during class and via the #questions channel of our class Slack workspace (and, of course, via Slack direct message to Jeff).

When answering questions on this exam, you should show your work to get full credit. For example, in the very first question 1(a), it's not sufficient to provide the correct 8-digit hexadecimal number. You must also explain (concisely and clearly) why that particular 32-bit number represents the given real number.

  1. (8 points) Floating point numbers

    Read Section 2.4 ("Floating Point") of your textbook. All of the following questions refer to the IEEE 754 single precision (32-bit) representation of real numbers.

    1. What is the representation of the real number -55.375? Give your answer as a single 8-digit hexadecimal number.
    2. To what real number does the 32-bit IEEE 754 representation 0x404ccccd correspond? Please express your answer as a fraction. (Note: since the significand appears to have a repeating pattern, you may find that extending that pattern infinitely gives you a much simpler fraction than the rounded off version stored in this 32-bit float.)
    3. What is the 32-bit representation of NaN? (i.e., "not a number"). Show your answer as an 8-digit hexadecimal number.
    4. Show a small amount of C code that can be used to cause a float-type variable x to contain NaN. (You can test this using a printf("%f", x); statement.)
    5. What is the 32-bit representation of -Inf? (i.e., "negative infinity"). Show your answer as an 8-digit hexadecimal number.
    6. Show a small amount of C code that can be used to cause a float-type variable y to contain -Inf. (You can test this using a printf("%f", x); statement.)
  2. (8 points) Fun with functions, part 1: lotsa parameters

    During our work this term, we have seen many C functions with 1, 2, or 3 parameters. Typically, we have observed those parameters getting stored by the compiler in registers rdi, rsi, and rdx. The "conventional use" column in the chart near the top of this guide to x86_64 assembly language matches our observations. So far, so good.

    But suppose a C function has a lot of parameters. For example, consider this function prototype:

    void do_something(int a, int b, int c, int d, int e, int f, int g);

    This moves us past the "conventional use" chart into unknown territory. So, for this problem, I want you to do some experiments to answer the following question as well as you can:

    Where does
    gcc -Wall -Werror -g -O0 -no-pie -fno-stack-protector -o whatever whatever.c
    on mantis.mathcs.carleton.edu put the parameters when a function has a large number of parameters?

    Give your answer as concisely as you can, but also give me enough information to make clear how you obtained your answer.

  3. (8 points) Fun with functions, part 2: recursion

    Here's a short program with a simple recursive function for you:

    1 #include <stdio.h> 2 3 int triangular_number(int n) { 4 if (n <= 1) { 5 return 1; 6 } 7 8 return n + triangular_number(n - 1); 9 } 10 11 12 int main() { 13 int k = triangular_number(6); 14 printf("%d\n", k); 15 return 0; 16 }

    Compile this program with

    gcc -Wall -Werror -g -O0 -no-pie -fno-stack-protector -o triangle triangle.c

    and do the following:

    1. Put a breakpoint at the base case of the recursion—that is, at the retq instruction where 1 is going to be returned from triangular_number. Once you have halted execution at this base-case return statement, draw a diagram of the entire system stack down to the bottom of main's stack frame. Label your diagram to make clear the meaning of the items on the stack, not just the numerical values.
    2. Start over and put a breakpoint at line 13, and imagine stepping through your code one line at a time until you're back at line 14. Give me a concise but detailed description of the sequence of instructions executed by the program during this time. That is, which assembly instructions get executed, and in which order during the full execution of triangular_number(6). (You might find it useful—or maybe not, I don't know—to think of this question as asking you to tell the story of the rip register during this time.)

    (You may ask: what's the point of all that? I want you to understand and describe what happens with both the data and the flow of control during a recursive function call.)

    (One more thing that I am not going to ask you to do, but that's pretty cool. If you compile with the -Og flag or the -O4 flag instead of -O0, the stack at the moment of the base case is surprising. The difference between optimized and non-optimized code can be quite illuminating of the ways compilers try to squeeze higher performance out of our C programs.)

  4. (8 points) Fun with functions, part 3: big parameters and return values

    Consider the short program person.c. This program defines a type person_t and some functions that use person_t objects as parameters or return values. Because a person_t variable takes up more than 8 bytes, you can't fit one into a register. This exercise will give you a chance to investigate how the gcc compiler arranges for passing parameters (and return values) that don't fit in registers.

    Answer each of the following questions, justifying your answer in terms of the assembly language generated by compiling person.c on mantis using

    gcc -Wall -Werror -g -O0 -no-pie -fno-stack-protector -o person person.c
    1. Show a diagram of the stack frame for person_test just before the first callq create_person instruction in person_test is executed.
    2. Show a diagram of the stack frames for person_test and create_person just before the add $0x58,%rsp instruction in create_person (immediately preceding two pop instructions and a retq instruction) is executed. Do this for the second time create_person is called in person_test. As in part 2 above, label your diagram in a way that clarifies the meaning of the pieces in the stack frames, not just their raw values.
    3. If we uncomment the line:

      strcpy(person1.name, "Friedrich Schiller");

      in the is_older function, does the printed output produced by this program change? Why or why not? (Explain your answer in terms of the actual assembly language code and the contents of the stack, not just in terms of more generic statements about C.)

  5. (2 points) Educating Jeff. Is there a book, a movie, a podcast, a website, a game, a writer, a musician, etc. etc. you think this particular old guy should know about? Let me know about it!

  6. (8 points) A little network programming: a shouting server

    In class this week, we'll spend time with some samples of network programming in C using the C sockets library.

    Your job: adapt the hello_server_concurrent.c sample with the following changes:

    • Name your source code shouting_server.c.
    • Use pthreads instead of fork.
    • Instead of just sending a message back to the client, the server should read whatever bytes/text the client sends, and send those same bytes back, but in upper case (e.g., if the client says "hello", the server should say back "HELLO").
  7. (2 points) Have a great break!. (Points to be awarded preemptively on the assumption that you'll at least try to get some sleep and have some fun.) Thanks a lot for being a wonderful class.