Home > Net >  Assigning variable address to pointer. Dereferencing pointer causes segmentation fault. How?
Assigning variable address to pointer. Dereferencing pointer causes segmentation fault. How?

Time:05-19

As you can see I'm just taking a variable address and taking it back and forth through other variables and pointers. Through printf I can see that address1, value2 and address 2 all hold the same value. But when I try to dereference address2 (that holds the same value as address1 and &value, i get error. Why?

#include <stdio.h>
#include <stdint.h>

int main()
{
    uint32_t value = 0;

    uint32_t* address1 = NULL;

    uint32_t* address2 = NULL;

    address1 = &value;

    printf("address1: %x\n", address1);

    uint32_t value2 = (uint32_t)address1;

    printf("value2: %x\n", value2);

    address2 = (uint32_t*)value2;

    printf("address2: %x\n", address2);

    uint32_t value3 = *address2; //fault here

    printf("*value2: %u\n", value3);

    return 0;
}

CodePudding user response:

On 64-bit systems, a uint32_t isn't big enough to hold a pointer. By putting one in it anyway, the top half of the address gets truncated, so it's pointing at the wrong place when you go to subsequently use it. Change to a uintptr_t to fix it.

Also, as Ingo Leonhardt commented, %x is the wrong format specifier for pointers, so the printed forms of them were truncated too, which is why you thought the values were all equal even though they actually aren't.

CodePudding user response:

Joseph's answer explains why this program fails on your particular setup (32bit vs. 64bit). I'll try to give an answer that is based only on the C standard (C99 mostly) and platform-independent.

The problem: Your program has undefined behaviour because it violates the C standard.


Specifically, the problem is the combination of these lines:

uint32_t* address1 = NULL; // 1
uint32_t value2 = (uint32_t)address1; // 2
address2 = (uint32_t*)value2; // 3

In line 2, you are casting a "pointer to uint32_t" (uint32_t*) to a uint32_t. That by itself is ok - you will get a numeric representation of the pointer, possibly truncated to fit into unit32_t.

However, in line 3 you then proceed to cast value2 back to uint32_t*, and dereference it. This is the undefined behavior, because (according to the C standard) there is no guarantee that the resulting address2 is a valid pointer.

You may in principle cast a valid pointer to an integral type and back, and get the same (valid) pointer back. However, this only works if the integral type you use is sufficiently large for a pointer - to ensure this, you need to use uintptr_t, which is meant specifically for this purpose. If your platform does not have uintptr_t (only introduced in C99), you will need a platform-specific type.

See e.g. When is casting between pointer types not undefined behavior in C? for more details, both on casting between pointer types and between integers and pointers.


As to how to avoid these problems: There is no general solution, but modern compilers will try to warn you in many cases. For example, on my system (64bit, GCC 7.5.0), gcc (even without any warning options) will warn about the problematic pointer cast in line 3 above:

st.c: In function ‘main’:
tst.c:13:23: warning: cast from pointer to integer of different size
[-Wpointer-to-int-cast]
     uint32_t value2 = (uint32_t)address1;
                       ^

It will also warn about the second cast:

tst.c:16:16: warning: cast to pointer from integer of different size
 [-Wint-to-pointer-cast]
     address2 = (uint32_t*)value2;
                ^

And finally, it will even notice the wrong printf format string mentioned in Joseph's answer:

tst.c:17:24: warning: format ‘%x’ expects argument of type ‘unsigned int’,
 but argument 2 has type ‘uint32_t * {aka unsigned int *}’ [-Wformat=]
     printf("address2: %x\n", address2);
                       ~^
                       %ls

Lesson: Always compile with -Wall -Wextra (or whatever switches on warning in your compiler), and heed the warnings.

  • Related