"Magically" avoiding segfault

73 Views Asked by At

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?

2

There are 2 best solutions below

0
MikeCAT On BEST ANSWER

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.

0
gulpr On

(*position)->prev->prev->prev is NULL dereferencing, which must cause a segfault error.

As it is an Undefined Behavior it must not anything. UB does not have to express itself any special way. So the claim:

In my opinion the program must crash at the node **ptr = &(*position)->prev->prev->prev;

is invalid.

But this time it is not an UB

In your code the last prev is not being dereferenced only the compiler is adding the offset of the prev member to the NULL pointer as you want its REFERENCE but not the actual value.