December Code

C pointer interview questions, with answers

Pointers are the topic C interviews are built around, because they are where C stops hiding the machine. A pointer is an address with a type attached, and every classic question follows from that: what & and * do, why adding 1 to a pointer moves it by a whole element, which pointers are safe to dereference, and what const means in each position of a declaration. The questions arrive as a few lines and 'what does this print?', so each answer below comes with code that was compiled and run.

The questions start with the two operators, move to arithmetic and the kinds of invalid pointer, and end with the declarations interviewers like to read aloud. Then take the free C diagnostic — ten questions across every C topic in the bank — to see which of these you can explain but not yet predict.

The questions, with answers

  1. 1.What is a pointer in C, and what do the & and * operators do?

    In short: A pointer is a variable holding the address of an object of a given type; & takes an object's address, and * follows a pointer to the object it points to.

    A declaration such as int *p says that p holds the address of an int. &n evaluates to the address of n, so p = &n makes p point to n, and *p then names n itself: reading *p reads n, and assigning to *p writes n, which is why the code prints the same value twice. The * in a declaration and the * in an expression are two uses of one symbol, and the declaration's * binds to the name, so int *p, q; declares one pointer and one plain int. A pointer's type matters for two reasons: it decides how many bytes *p reads, and how far p + 1 moves. An address is printed with %p and a cast to void *, and on systems that randomise memory layout it changes from run to run.

    int n = 42;
    int *p = &n;
    *p += 8;
    printf("%d %d\n", n, *p);
    // 50 50
  2. 2.How does pointer arithmetic work in C?

    In short: Adding an integer to a pointer moves it by that many elements, not bytes, and subtracting two pointers into the same array gives the number of elements between them.

    Arithmetic on a pointer is scaled by the size of the type it points to: if p points into an array of double, p + 1 is the address of the next double, sizeof(double) bytes further on, and p[i] is defined as *(p + i). Subtracting two pointers into the same array gives a ptrdiff_t counting elements, printed with %td, which is how the code finds how far apart two elements are. The rules are strict: arithmetic and comparisons are defined only within one array, plus the position one past its end, which may be formed and compared but not dereferenced. Stepping outside that range is undefined behaviour even if the result is never read, and arithmetic on void * is a gcc extension, not standard C.

    double a[4] = {1, 2, 3, 4};
    double *p = a, *q = &a[3];
    printf("%td\n", q - p);
    // 3
    printf("%.1f\n", *(p + 2));
    // 3.0
  3. 3.What is the difference between a null pointer, a wild pointer and a dangling pointer?

    In short: A null pointer deliberately points nowhere and can be tested; a wild pointer was never initialised; a dangling pointer points to memory whose lifetime has ended.

    A null pointer, NULL, is a known 'no object' value: it compares unequal to every valid address, so if (p) is a meaningful test, but dereferencing it is undefined behaviour and in practice a crash. A wild pointer is a local pointer declared without an initialiser, so it holds whatever bits were in that stack slot, and not even a test can tell whether it is usable. A dangling pointer once pointed to a real object that has since ended, memory that was freed or a local of a function that has returned; it still holds the old address and may even seem to work until the memory is reused. The defences are to initialise every pointer, to set a pointer to NULL after freeing it, as below, and never to return the address of a local.

    int *p = malloc(sizeof *p);
    *p = 7;
    free(p);
    p = NULL;
    if (p == NULL)
        puts("safe to test");
    // safe to test
  4. 4.What is a void pointer, and why can't you dereference it?

    In short: void * is a generic pointer that can hold the address of any object, but it carries no type, so it must be converted to a typed pointer before use.

    void * is C's generic object pointer: any object pointer converts to it and back without a cast and without losing the address, which is why malloc returns void * and why qsort and memcpy take it. Because void has no size, the compiler cannot know how many bytes *v would read or how far v + 1 should move, so dereferencing a void pointer and doing arithmetic on one are not allowed in standard C; gcc accepts the arithmetic as an extension, treating the size as 1. To use the object, convert the pointer back to its real type, as in *(int *)v. Reading an object through a pointer of the wrong type breaks the aliasing rules, and a function pointer is not guaranteed to fit in a void * at all.

    int n = 5;
    double d = 2.5;
    void *v = &n;
    printf("%d\n", *(int *)v);
    // 5
    v = &d;
    printf("%.1f\n", *(double *)v);
    // 2.5
  5. 5.What is the difference between const int *p, int *const p and const int *const p?

    In short: const before the * protects the int, so p can move but *p cannot change; const after the * protects the pointer, so *p can change but p cannot move; both together freeze both.

    Read the declaration from the name outwards. In const int *p, which can also be written int const *p, p is a pointer to a const int: p may be pointed at another int, but nothing may be assigned through it. In int *const p, p is a const pointer to an int: it must be initialised and can never point elsewhere, but the int it points to can be changed through it. const int *const p combines the two. The distinction matters in function parameters: const char *s promises that a function such as strlen will not modify your string, which is why passing it a string literal is safe. Casting const away and then writing to an object that was defined const is undefined behaviour.

    int a = 1, b = 2;
    const int *p = &a;
    p = &b;
    int *const q = &a;
    *q = 9;
    *p = 5;
    // does not compile
    printf("%d %d\n", a, *p);
    // 9 2
  6. 6.When do you need a pointer to a pointer in C?

    In short: When a function must change which object the caller's pointer points to, such as allocating memory for it, and for arrays of pointers like argv.

    A function receives a copy of every argument, a pointer included, so assigning a new address to a pointer parameter changes only the function's copy. To let the function re-aim the caller's pointer, pass the pointer's own address, a pointer to a pointer, and assign through it: *out = malloc(...). That is the pattern below, and it is how C APIs hand back a newly allocated buffer while keeping the return value free for an error code. The other common use is an array of pointers, such as main's char **argv or a table of strings, where each element is a pointer, so a pointer to the first element has type char **. Each extra * is one more hop: **out is the object at the end of the chain.

    int make(int **out, int v) {
        *out = malloc(sizeof(int));
        if (*out == NULL)
            return -1;
        **out = v;
        return 0;
    }
    
    int main(void) {
        int *p = NULL;
        if (make(&p, 7) == 0)
            printf("%d\n", *p);
        free(p);
    }
    // 7
  7. 7.What is the size of a pointer in C, and does it depend on the type it points to?

    In short: On a given platform object pointers are normally all the same size, 8 bytes on 64-bit systems and 4 on 32-bit ones, whatever type they point to.

    A pointer holds an address, so its size follows the platform's address width, not the pointee: in a 64-bit build char *, int * and double * are all 8 bytes, as the code shows, and in a 32-bit build they are 4. The standard itself only requires that void * can hold any object pointer, and some segmented and word-addressed machines really did use pointers of different sizes, which is why portable code never stores a pointer in an int; uintptr_t from <stdint.h> is the integer type made for that. Function pointers may even differ in size from object pointers on some platforms, one reason C does not let you convert freely between the two kinds.

    printf("%zu %zu %zu\n",
        sizeof(char *),
        sizeof(int *),
        sizeof(double *));
    // 8 8 8

How the diagnostic asks it

One question from the C bank, exactly as a sitting would show it. The bank has 5 on pointers and 30 across C.

Pointers · mediumCL-013

What does this C code print?

int a[] = {10, 20, 30};
int *p = a + 1;
printf("%d %d\n", *p + 1, *(p + 1));
  1. 130 30
  2. 221 21
  3. 311 20
  4. 421 30correct

p = a + 1 points at a[1], which holds 20. Unary * binds tighter than binary +, so *p + 1 reads 20 and adds 1, giving 21, while *(p + 1) first moves the pointer one element on, to a[2], and reads 30. 30 30 reads *p + 1 as *(p + 1). 21 21 reads *(p + 1) as adding 1 to the value. 11 20 assumes p starts at a[0]; it starts at a + 1. Note that p + 1 moves by one element, sizeof(int) bytes, not by one byte.

Measure it

Reading answers tells you what’s true. A diagnostic tells you what you get wrong.

10 C questions across its topics, easy to hard, about fifteen minutes. You get a readiness figure with the arithmetic shown, the topics you missed named, and a practice set sized for today. Free: 1 diagnostic a month and 15 problems a day. No card.

What the readiness test measures · how the score is computed

By Harshit · updated