CS360 Midterm for Spring, 2022. Questions, Answers and Grading

The exam was given on Canvas with question banks used for many questions. When I used a bank, I give you here an example question.

Question 1

I gave you a blank question 1 so that you could have a scratch "essay" box. It was worth 0 points.

Question 2

This came from a bank. Example: Our machine has four-byte pointers and is little-endian. The syntax of memcpy is:

void memcpy(void *dest, const void *src, int bytes); 

Here's a procedure:

void a(unsigned int *x)
{ 
  unsigned char *c;
  unsigned int y, z;

  c = (unsigned char *) x;
  y = 0;
  z = 0;
 
  memcpy(&y, c+2, sizeof(unsigned int));
  memcpy(&z, x+3, 3);

  printf("1 0x%02x\n", c[0]);
  printf("2 0x%02x\n", c[5]);
  printf("3 0x%02x\n", c[10]);
  printf("4 0x%02x\n", c[15]);
  printf("5 0x%08x\n", (unsigned int) (x+1));
  printf("6 0x%08x\n", x[1]);
  printf("7 0x%08x\n", y);
  printf("8 0x%08x\n", z);
}

When we run it, x is 0x04e05030, and here are the values of the 16 bytes of memory starting with x:

 Address     Value
0x04e05030 0x893b6165
0x04e05034 0x50f6dc88
0x04e05038 0x04e05034
0x04e0503c 0xd4cfa7ce

Please answer with the 8 lines of output of this procedure. Please don't put anything extraneous into your answer box.

Answer

We'll label the bytes as offsets to the pointers:

 Address     Value
             +3 +2 +1 +0
0x04e05030   89 3b 61 65
0x04e05034   50 f6 dc 88
0x04e05038   04 e0 50 34
0x04e0503c   d4 cf a7 ce

1 0x65        # Little endian means that its the last two digits on the 0x04e5030 line.
2 0xdc        # This is the +1 byte for pointer 0x04e05034
3 0xe0        # This is the +2 byte for pointer 0x04e05038
4 0xd4        # This is the +3 byte for pointer 0x04e0503c
5 0x04e05034  # Adding 1 to x adds 4 to the pointer.
6 0x50f6dc88  # The four bytes starting at 0x04e05034, printed as an integer.
7 0xdc88893b  # y is 3b 89 88 dc, but in little endian: 0xdc88893b
8 0x00cfa7ce  # z is ce a7 cf 00, but in little endian: 0x00cfa7ce
Grading: 2 points per question. There's some partial credit.


Question 3

This came from a bank. Example: Here are the prototypes to strcpy and strcat:

char *strcpy(char *dest, const char *source);
char *strcat(char *dest, const char *source);

Here is a procedure:

void a(char *b, char *c, char *d)
{
  strcpy(b+2, c);
  strcpy(b+12, c);
  strcat(b, d);
  b[2] = '\0';
  printf("1 %s\n", b);
  printf("2 %s\n", b+5);
  printf("3 %s\n", b+10);
  printf("4 %s\n", b+15);
  printf("5 %s\n", b+20);
}

Please put what the output to a() is when you run it with the following arguments. I've put the numbers in to help you count:

     0123456789012345678901
b = "TennesseeVolunteersWin"
c = "Fred"
d = "123"

Answer

Start by showing b, b+2 and b+12:

     01234567890123456789012
b =  TennesseeVolunteersWin*
       ^         ^
       |         |
      b+2       b+12

The first strcpy copies "Fred" to b+2:

     01234567890123456789012
b =  TeFred*eeVolunteersWin*
       ^         ^
       |         |
      b+2       b+12

The second strcpy() copies "Fred" to b+12:

     01234567890123456789012
b =  TeFred*eeVolFred*rsWin*
       ^         ^
       |         |
      b+2       b+12

The strcat() finds the null character, which is at (b+6). It puts "123" there:

     01234567890123456789012
b =  TeFred123*olFred*rsWin*
       ^         ^
       |         |
      b+2       b+12

Finally, we set b[2] to the null character:

     01234567890123456789012
b =  Te*red123*olFred*rsWin*
       ^         ^
       |         |
      b+2       b+12

So:

1 Te
2 d123
3 olFred
4 d
5 in
Grading: 2 points per question. There's some partial credit.


Question 4

Also from a bank. Here's an example:

You are on a little-endian machine with 8-byte pointers. You are running the following procedure:

typedef struct {
  unsigned long dv;
  unsigned long *dp;
} DS;

void a(unsigned long *p)
{
  unsigned int *ip;
  unsigned long **r;
  DS *d;
  unsigned long x;

  ip = (unsigned int *) p;
  r = (unsigned long **) p;
  d = (DS *) p;
  x = (unsigned long) (p+4);

  printf("1 0x%02lx\n", p[4] & 0xff);
  printf("2 0x%02lx\n", r[1][0] & 0xff);
  printf("3 0x%02lx\n", r[0][2] & 0xff);
  printf("4 0x%02lx\n", d->dv & 0xff);
  printf("5 0x%02lx\n", *(d->dp) & 0xff);
  printf("6 0x%02lx\n", (x+1) & 0xff);
  printf("7 0x%02x\n",  ip[2] & 0xff);
  printf("8 0x%02x\n",  ip[11] & 0xff);
}

You run a() with p = 0x00002e1529b17620. Here's the state of memory starting at that address:

    Address                 Value
                       7 6 5 4 3 2 1 0
0x00002e1529b17620  0x00002e1529b17640
0x00002e1529b17628  0x00002e1529b17648
0x00002e1529b17630  0x00002e1529b17638
0x00002e1529b17638  0x00002e1529b17630
0x00002e1529b17640  0x00002e1529b17650
0x00002e1529b17648  0x00002e1529b17620
0x00002e1529b17650  0x00002e1529b17658
0x00002e1529b17658  0x00002e1529b17628

Please enter the output of a().

1 0x50   # p[4] is 0x00002e1529b17650, so this is byte 0 of that: 0x50
2 0x20   # r[1] is 0x00002e1529b17648.  r[1][0] is 0x00002e1529b17620: 0x20
3 0x58   # r[0] is 0x00002e1529b17640.  r[0][2] is at address 0x00002e1529b17650: 0x00002e1529b17658: 0x58
4 0x40   # d = &(d->dv) = 0x00002e1529b17620, so d->v is 0x00002e1529b17640: 0x40
5 0x20   # &(d->dp) = 0x00002e1529b17628, so d->dp = 0x00002e1529b17648. *(d->dp) = 0x00002e1529b17620: 0x20
6 0x41   # x = p+4 = 0x00002e1529b17640.  x+1 simply adds one to that: 0x41
7 0x48   # ip = 0x00002e1529b17620, so (ip+2) = 0x00002e1529b17628.  ip[2] is the lower 4 bytes, 
         # which are 0x29b17648.  (ip[2] & 0xff) will be the lowest byte: 0x48
8 0x15   # ip+11 is 0x00002e1529b17620 + 0x2c = 0x00002e1529b1764c.  So this is the
         # high four bytes of 0x00002e1529b17648, which is 0x00002e15: 0x15.
Grading: 1.5 points per question. There's some partial credit.


Question 5

Suppose you are writing a program to test whether two files are hard links to each other.  Tell me in English how your program would perform that test.

Answer

You would call stat(), and then check the "inode number" field in each of the stat buf's. That field is named st_ino. If the two inode numbers equal each other, then the two files are hard links to each other. Otherwise, they are not. Grading: 5 points.


Question 6

Here's program 1:

int main()
{
  unsigned int hash, l;
  hash = 0;

  while (fread(&l, sizeof(unsigned int), 1, stdin) == 1) hash = add_to_hash(hash, l);
  printf("0x%x\n", hash);
}

And here's program 2:

int main()
{
  unsigned int hash, l;
  hash = 0;

  while (read(0, &l, sizeof(unsigned int)) == 1) hash = add_to_hash(hash, l);
  printf("0x%x\n", hash);
}

Let's suppose that standard input is four megabytes. Please tell me which of these programs will run faster, and then explain why. Your explanation should be a paragraph, and you should use numbers to support your reasoning. In particular, it will be a good idea for you to estimate how long each program will take to run.

Answer

Program 1 is going to be much faster. The reason is that program 2 makes a read() system call for every four bytes of standard input. Fread() will buffer, typically 8K at a time, and then return four bytes at a time from the buffer. If system calls take 100 microseconds, then Program 2 will take 100 seconds to run (since it makes roughly a million system calls), while program 1 will take a half a second.

Grading: 7 points. You didn't have to have correct numbers, but you didn't get full points unless you gave some numbers to back up your explanation.


Question 7

Write a program in C to count the number of subdirectories of the current directory that contain a file whose name is f1.txt, and whose size is at least 100 bytes. I don't care about include files, so don't bother with them.

If you encounter errors, simply exit(1).

Here are helpful prototypes and structs: Obviously, these are the calls that you should make to solve this problem -- please don't get cute and try popen() or system().

int stat(const char *path, struct stat *buf);
int lstat(const char *path, struct stat *buf);
DIR * opendir(const char *filename);
struct dirent * readdir(DIR *dirp);

struct direct {
  /* Useless stuff omitted */
  char *d_name;
};

struct stat { 
    dev_t    st_dev;    /* device inode resides on */
    ino_t    st_ino;    /* inode's number */
    mode_t   st_mode;   /* inode protection mode */
    nlink_t  st_nlink;  /* number of hard links to the file */
    uid_t    st_uid;    /* user-id of owner */
    gid_t    st_gid;    /* group-id of owner */
    dev_t    st_rdev;   /* device type, for special file inode */
    time_t   st_atime;  /* time of last access */
    time_t   st_mtime;  /* time of last data modification */
    time_t   st_ctime;  /* time of last file status change */
    off_t    st_size;   /* file size, in bytes */
    u_long   st_flags;  /* user defined flags for file */
    u_long   st_gen;    /* file generation number */
};

Answer

This one was a challenge. I think that the best way to solve this is to traverse the current directory and call stat() on every file. If it's a directory (and not "." or ".."), then construct the name of "f1.txt" in that directory. Then call stat() on that to check its size. Here you go:

#include <sys/stat.h>
#include <stdio.h>
#include <string.h>
#include <dirent.h>
#include <stdlib.h>

int main()
{
  DIR *d;
  struct dirent *de;;
  struct stat buf;
  char *name;
  int n;

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

  /* Traverse the current directory. */

  for (de = readdir(d); de != NULL; de = readdir(d)) {

    /* Only work on files that are directories, and that aren't . or .. */

    if (strcmp(de->d_name, ".") != 0 && strcmp(de->d_name, "..") != 0) {
      if (stat(de->d_name, &buf) != 0) exit(1);
      if (S_ISDIR(buf.st_mode)) {

        /* Create the filename.  This can be much more efficient, as in the
           lecture on the prsize program, but this is an exam, so as long
           as you don't have a memory leak, you're good. */

        name = (char *) malloc(sizeof(char) * (strlen(de->d_name)+20));
        sprintf(name, "%s/f1.txt", de->d_name);

        /* See if the file exists and is at least 100 bytes. */

        if (stat(name, &buf) == 0 && buf.st_size >= 100) n++;
        
        /* No memory leak. */
        free(name);
      }
    }
  }
  printf("%d\n", n);
  return 0;
}

Grading

Grading was broken into three parts, each worth five points. I don't explain the assignment of points in your grade file -- it should be pretty self-explanatory with the code above and explanation below.
  1. "traversal / checking for . and .. / directory test" -- If you traversed the subdirectory (a lot of you did this), you needed to test first. If you simply constructed the name and used stat(), you didn't need to test first.

  2. "allocating, constructing and freeing filename" -- If you skipped this and instead traversed the subdirectory, checking for "f1.txt" in the name, you got 2 of these 5

  3. "stat() for xxx/f0.txt, checking the size and incrementing total" -- this is pretty self-explanatory.


Question 8

Please implement the procedure add_to_data(), which has the following prototype:

void add_to_data(JRB tree, char *name, char *alias);

The tree is keyed on name. Each node's val is a dllist of aliases (which are strings). add_to_data() should create a node for the given name if it doesn't exist. Then, it should add the alias to the name's dllist.

It will be called as follows from the main():

  add_to_data(tree, is->fields[0], is->fields[1]);

Do not bother with include files. I don't care. However, the rest of your code should be polished. You don't have to error check.

Here is relevant information from dllist.h and jrb.h:

typedef struct dllist {
  struct dllist *flink;
  struct dllist *blink;
  Jval val;
} *Dllist;

Dllist new_dllist();
void free_dllist(Dllist);
void dll_append(Dllist, Jval);
void dll_prepend(Dllist, Jval);
void dll_insert_b(Dllist, Jval);
void dll_insert_a(Dllist, Jval);
void dll_delete_node(Dllist);
#define dll_traverse(ptr, list) for (ptr = list->flink; ptr != list; ptr = ptr->flink)

typedef struct jrb_node {
  struct jrb_node *flink;
  struct jrb_node *blink;
  Jval key;
  Jval val;
  /* Other stuff */
} *JRB;

JRB make_jrb();   
JRB jrb_insert_str(JRB tree, char *key, Jval val);
JRB jrb_find_str(JRB root, char *key);
JRB jrb_find_gte_str(JRB root, char *key, int *found);
void jrb_delete_node(JRB node);  
void extern void jrb_free_tree(JRB root);  
#define jrb_traverse(ptr, lst) for (ptr = list->flink; ptr != list; ptr = ptr->flink)

Answer

Garden variety nested insertion:

void add_to_data(JRB tree, char *name, char *alias)
{
  JRB tmp;
  Dllist l;

  tmp = jrb_find_str(tree, name);
  if (tmp == NULL) {
    tmp = jrb_insert_str(tree, strdup(name), new_jval_v((void *) new_dllist()));
  }
  l = (Dllist) tmp->val.v;
  dll_append(l, new_jval_v((void *) strdup(alias)));
}

Grading: 5 points.


Question 9

Now, write the following procedure:

void print(JRB tree);

This should print the tree from the previous question.  The format should be one line per node of the tree.  For each node, print the name, followed by a colon, and then the aliases, each separated by a space.

Answer

Nested traversal:

void print(JRB tree)
{
  JRB tmp;
  Dllist l, tmpl;

  jrb_traverse(tmp, tree) {
    l = (Dllist) tmp->val.v;
    printf("%s:", tmp->key.s);
    dll_traverse(tmpl, l) printf(" %s", tmpl->val.v);
    printf("\n");
  }
}

Grading: 5 points.