Week 02 Tutorial Answers

  1. When should the types in stdint.h be used:
    #include <stdint.h>
    
                     // range of values for type
                     //             minimum               maximum
        int8_t   i1; //                 -128                  127
        uint8_t  i2; //                    0                  255
        int16_t  i3; //               -32768                32767
        uint16_t i4; //                    0                65535
        int32_t  i5; //          -2147483648           2147483647
        uint32_t i6; //                    0           4294967295
        int64_t  i7; // -9223372036854775808  9223372036854775807
        uint64_t i8; //                    0 18446744073709551615
    
  2. How can you tell if an integer constant in a C program is decimal (base 10), hexadecimal (base 16), octal (base 8) or binary (base 2)?

    Sidenote: do you think this is good language design?

    Language trivia: what base is the constant 0 in C?

    Show what the following decimal values look like in 8-bit binary, 3-digit octal, and 2-digit hexadecimal:

    1. 1
    2. 8
    3. 10
    4. 15
    5. 16
    6. 100
    7. 127
    8. 200

    How could I write a C program to answer this question?

  3. Assume that we have the following 16-bit variables defined and initialised:

    uint16_t a = 0x5555, b = 0xAAAA, c = 0x0001;
    

    What are the values of the following expressions:

    1. a | b (bitwise OR)
    2. a & b (bitwise AND)
    3. a ^ b (bitwise XOR)
    4. a & ~b (bitwise AND)
    5. c << 6 (left shift)
    6. a >> 4 (right shift)
    7. a & (b << 1)
    8. b | c
    9. a & ~c

    Give your answer in hexadecimal, but you might find it easier to convert to binary to work out the solution.

  4. Consider a scenario where we have the following flags controlling access to a device.

    #define READING   0x01
    #define WRITING   0x02
    #define AS_BYTES  0x04
    #define AS_BLOCKS 0x08
    #define LOCKED    0x10
    
    The flags are contained in an 8-bit register, defined as:
    unsigned char device;
    

    Write C expressions to implement each of the following:

    1. mark the device as locked for reading bytes
    2. mark the device as locked for writing blocks
    3. set the device as locked, leaving other flags unchanged
    4. remove the lock on a device, leaving other flags unchanged
    5. switch a device from reading to writing, leaving other flags unchanged
    6. swap a device between reading and writing, leaving other flags unchanged
  5. Discuss the starting code for sixteen_out, one of this week's lab exercises. In particular, what does this code (from the provided main) do?

        long l = strtol(argv[arg], NULL, 0);
        assert(l >= INT16_MIN && l <= INT16_MAX);
        int16_t value = l;
    
        char *bits = sixteen_out(value);
        printf("%s\n", bits);
    
        free(bits);
    
  6. Given the following type definition

    typedef unsigned int Word;
    

    Write a function

    Word reverseBits(Word w);
    
    ... which reverses the order of the bits in the variable w.

    For example: If w == 0x01234567, the underlying bit string looks like:

    0000 0001 0010 0011 0100 0101 0110 0111
    

    which, when reversed, looks like:

    1110 0110 1010 0010 1100 0100 1000 0000
    

    which is 0xE6A2C480 in hexadecimal.

Revision questions

The following questions are primarily intended for revision, either this week or later in session.
Your tutor may still choose to cover some of these questions, time permitting.

  1. What is the output from the following program and how does it work? Try to work out the output without copy-paste-compile-execute.

    #include <stdio.h>
    
    int main(void) {
        char *str = "abc123\n";
    
        for (char *c = str; *c != '\0'; c++) {
            putchar(*c);
        }
    
        return 0;
    }
    
  2. Consider the following struct definition defining a type for points in a three-dimensional space:

    typedef struct Coord {
        int x;
        int y;
        int z;
    } Coord;
    

    and the program fragment using Coord variables and pointers to them:

    {
        Coord coords[10];
        Coord a = { .x = 5, .y = 6, .z = 7 };
        Coord b = { .x = 3, .y = 3, .z = 3 };
        Coord *p = &a;
    
        /*** A ***/
        (*p).x = 6;
        p->y++;
        p->z++;
        b = *p;
        /*** B ***/
    
    }
    

    1. Draw diagrams to show the state of the variables a, b, and p, at points A and B.

    2. Why would a statement like *p.x++; be incorrect?

    3. Write code to iterate over the coords array using just the pointer variable p the address of the end of the array, and setting each item in the array to (0,0,0). Do not use an index variable.

  3. How does the C library function
    void *realloc(void *ptr, size_t size);
    
    differ from
    void *malloc(size_t size);
    
  4. If the following program is in a file called prog.c:

    #define LIFE 42
    #define VAL random() % 20
    
    #define sq(x) (x * x)
    #define woof(y) (LIFE + y)
    
    int main(void) {
        char s[LIFE];
        int i = woof(5);
        i = VAL;
        return (sq(i) > LIFE) ? 1 : 0;
    }
    

    … then what will be the output of the following command:

    gcc -E prog.c
    

    You can ignore the additional directives inserted by the C pre-processor.

  5. What is the effect of each of the static declarations in the following program fragment:

    #include <stdio.h>
    
    static int x1;
    ...
    
    static int f(int n) {
        static int x2 = 0;
        ...
    }
    
  6. What is the difference in meaning between the following pairs (a/b and c/d) of groups of C statements:
    1. if (x == 0) {
          printf ("zero\n");
      }
      
    2. if (x == 0)
          printf ("zero\n");
      
    3. if (x == 0) {
          printf ("zero\n");
          printf ("after\n");
      }
      
    4. if (x == 0)
          printf ("zero\n");
          printf ("after\n");
      
  7. C functions have a number of different ways of dealing with errors:

    • terminating the program entirely (rare)
    • setting the system global variable errno
    • returning a value that indicates an error (e.g., NULL, EOF)
    • setting a returning parameter to an error value

    They might even use some combination of the above.

    Think about how the following code might behave for each of the inputs below. What is the final value for each variable?

    int n, a, b, c;
    n = scanf("%d %d %d", &a, &b, &c);
    

    Inputs:

    1.   42 64 999
    2.   42 64.4 999
    3.   42 64 hello
    4.   42 hello there
    5.   hello there
  8. Consider a function get_int() which aims to read standard input and return an integer determined by a sequence of digit characters read from input. Think about different function interfaces you might define to deal with input that is not a sequence of digits, or that is a very long sequence of digits.