Search code examples
cpointerstreesegmentation-faultpointer-to-pointer

"Magically" avoiding segfault


I wrote a small demo program

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

typedef struct tag_node {
    struct tag_node *left, *right, *prev;
    int value;
} node;

int main()
{
    node *root_node = malloc(sizeof(node));
    root_node->value = 1;
    node **position = &root_node->right;
    *position = malloc(sizeof(node));
    (*position)->value = 2;
    (*position)->prev = root_node;
    printf("root node is %d, right node is %d\n",
        root_node->value, root_node->right->value);
    node **ptr = &(*position)->prev->prev->prev;
    printf("This is fine\n");
    return 0;
}

and compiled it with gcc -Wall -Wextra -g -O0 example.c -o example. In my opinion the program must crash at the node **ptr = &(*position)->prev->prev->prev; line because:

  1. *position is the root->right address;
  2. (*position)->prev is the root address;
  3. (*position)->prev->prev is the root previous address, i.e., NULL;
  4. (*position)->prev->prev->prev is NULL dereferencing, which must cause a segfault error.

Nevertheless the program feels great, prints "This is fine" and returns exit code 0.

Could someone please explain this black magic to me?


Solution

  • The line

    node **ptr = &(*position)->prev->prev->prev;
    

    is meaningless and can be eliminated by the optimization.

    Interestingly, adding a line

    printf("%p\n",(void*)ptr);
    

    after the line to make the line meaningfull also didn't invoke segmentation fault.
    This looks like because we can read (*position)->prev->prev (this means root_node->prev) and can calculate the address &(*position)->prev->prev->prev by just adding the offset of the member prev to the value of (*position)->prev->prev without actually accessing (*position)->prev->prev->prev.

    Also note that malloc doesn't initialize the allocated buffer and the value of (*position)->prev->prev need not be NULL.