CS360 Midterm Exam - March 9, 2021 - Answers and Grading

James S. Plank
If the question came from a bank, then I'll show the answer to the example question from exam.html.

Question 1: 5 points

The value of i is 0x2cab2a74. That is a pointer to i[0]. Therefore, the pointer to i[1] is that value plus four: 0x2cab2a78. The question states that the four bytes at that address are 0x542cc170, so that is the answer.

Grading


Question 2: 20 points

Let's talk about what gets pushed onto the stack from the time main() is called: So:

Grading

Every question was worth 2 points. Here's partial credit (all of the programs in the bank had the same stack structure, so you can map the answers here to your question).


Question 3: 16 points

Please see the inline comments. The file is: ndistinct.c.

/* ndistinct.c from CS360 midterm exam on 3/9/2021. */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <dirent.h>
#include <sys/stat.h>
#include "jrb.h"

/* Since inodes are longs, you need to write your own comparison function. */

int compare(Jval v1, Jval v2)
{
  if (v1.l < v2.l) return -1;
  if (v1.l > v2.l) return 1;
  return 0;
}

/* Use opendir() / readdir() to enumerate files.
   Use stat to find each files' inode number.
   Store the inodes in a red-black tree so that you can discover duplicates. */

int main()
{
  JRB tree;                 /* The tree. */
  DIR *d;                   /* The open directory. */
  struct dirent *de;        /* Each file in the directory. */
  struct stat buf;          /* The file's metadata. */
  int ndistinct;            /* The number of distinct files. */

  /* Do initialization */

  tree = make_jrb();
  d = opendir(".");
  if (d == NULL) { perror("."); exit(1); }
  ndistinct = 0;

  /* Enumerate the files and look up each inode in the tree.
     Only insert an inode if it's not there already. */

  for (de = readdir(d); de != NULL; de = readdir(d)) {
    if (stat(de->d_name, &buf) != 0) { perror(de->d_name); exit(1); }
    if (jrb_find_gen(tree, new_jval_l(buf.st_ino), compare) == NULL) {
      ndistinct++;
      jrb_insert_gen(tree, new_jval_l(buf.st_ino), new_jval_i(0), compare);
    }
  }

  /* Print the final answer.  I don't close the directory or free the tree,
     because that is done automatically when the program ends. */

  printf("%d\n", ndistinct);
  return 0;
}

Grading

In this one, you typically started at a point value for your basic solution, and then you had deductions. The starting point values:


Question 4: 16 points

See the inline comments in the file: nconcat.c. I added a main() to it which calls nconcat() on argv:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

char *nconcat(const char **str)
{
  int length;        /* For keeping track of the length of the strings. */
  char *rv;          /* The return string. */
  int i;

  /* First, calculate the length of the return string.
     I add one for each string, to account for the spaces between strings,
     and then for the final null character. */

  length = 0;
  for (i = 0; str[i] != NULL; i++) {
    length += strlen(str[i]);
    length++;
  }

  /* Allocate the return string. */

  rv = (char *) malloc(sizeof(char) * length);
  if (rv == NULL) { perror("malloc"); exit(1); }

  /* Build the string incrementally, using the length so that
     strcpy() always starts at the end of rv. */

  strcpy(rv, "");
  length = 0;
  for (i = 0; str[i] != NULL; i++) {
    if (i != 0) {
      strcpy(rv+length, " ");
      length++;
    }
    strcpy(rv+length, str[i]);
    length += strlen(str[i]);
  }

  return rv;
}

int main(int argc, const char **argv)
{
  printf("%s\n", nconcat(argv));
}

Grading

You typically started with 16 and had deductions. The major deductions:


Question 5: 10 points

It really doesn't matter whether we use fread() or read(). We are simply reading 16 bytes, and that will be either one fread() call or one read() call. The system call overhead will be the same.

In fact, you can view read() as being slightly superior here. If we're using fread(), then we have to call fopen() on each file, which will allocate a 4K to 8K buffer, and then each fread() call will call read() to read 4K to 8K so that it performs buffering. That will be more expensive than read() simply reading 16 bytes and then closing the file.

(Now, if you want to argue that you have to read 4K anyway, because your disk sector size will be on the order of 4K, that's also a fine argument.)

Grading

If you said fread() and gave me some discourse about buffering, you got 5 points. If you told me the correct answer, you got 10 points.

Question 6: 3 points

The key to this question was to remember that integers have to align on 4-byte quantities, and doubles have to align on 8-byte quantities. So:
struct a194 {
  char c0;             /* Bytes 0-3: We need three bytes of padding after c0. */
  int i0;              /* Bytes 4-7. */
  int i1;              /* Bytes 8-11. */
  int i2;              /* Bytes 12-15. We don't need padding here, because this ends on an 8-byte boundary. */
  double d0;           /* Bytes 16-23. */
};
The answer here is 24.

Grading


Question 7: 10 points

Grading

3 points, 3 points, 4 points, with some partial credit