0

I've learned pointer to pointer. I am curious about dereferencing after casting "integer to pointer" (char*)(int) and "void pointer to pointer to char pointer." (char**)(void*)

Now there are some issues when I want to dereference to void pointer to pointer to char pointer.(char**)(void*)

I tried 2 cases.

integer to pointer (char*)(int)

#include <stdio.h>
int main()

{
    
    int a = 10;
    int b;
    void* ptr = &a;

    b = ptr;
    printf("%d\n", *(unsigned int*)b);
    printf("%d\n", *(void**)(b));
    printf("%p\n", *(char**)(b));

    return 0;
}

*(unsigned int*)b 
*(void**)(b)
*(char**)(b)**

These three read 10 which is in address.

But the code below is problem. (Please watch ptr_speacial) "void pointer to pointer to char pointer." (char**)(void*)

#include <stdio.h>
int main()
{
    
    int arr[5] = { 1,2,3,4,5 };
    char arr2[5] = { 1,2,3,4,5 };
    

    void* ptr_arr;
    void* ptr_arr2;

    void* ptr_special;

    ptr_arr = arr;
    ptr_arr2 = arr2;

    ptr_special = (char*)ptr_arr2 + 4;


    printf("address : %p: value : %d\n", ptr_special, *(char**)ptr_special);
    printf("%d\n", *(char**)ptr_arr);

    return 0;
}

*(char**)ptr_special did not read 5 which is in address but -858993659

I'm not sure but I think this is overflow problem. What happend in this case? Please help me!

Alan Birtles
  • 32,622
  • 4
  • 31
  • 60
w00sung
  • 11
  • 2
  • 3
    Most of this code is a problem. Firstly, you cannot rely on `int` being large enough to store a pointer type. This is why `intptr_t` exists. Secondly, you cannot interpret an array type as a `char**`. It simply is _not_ unless the underlying array actually stores `char*` values. What you've done with `*(char**)ptr_special` is forced the compiler to treat the memory at `arr2+4` as if it contains a `char*` value, and then you tell `printf` to output that pointer value as a `int` -- also problematic, and is why the `%p` format specifier exists. Yes, it also overflows, which doesn't help. – paddy Jan 20 '21 at 08:01
  • Thanks for your comment! I knew this casting would be problematic but I wanted to know how it works and what makes this problem. Thanks to your comment, I realize how compiler treat memory. – w00sung Jan 20 '21 at 10:16

3 Answers3

2

First of all, C has fairly lax rules when it comes to conversions between various pointer and integer types. If you add an explicit cast and thereby force a conversion, the code will compile in most cases. However, that doesn't necessarily mean that the code is correct. There are multiple forms of undefined behavior asociated with such wild conversions between different types. Alignment, type/address sizes, compatible types, pointer aliasing (What is the strict aliasing rule?) and so on. Lots of potential for bugs to consider.

There are many problems with this code. Specifically:

  • b = ptr; This is invalid C. You cannot assign an integer to a pointer without an explicit cast. "Pointer from integer/integer from pointer without a cast" issues. Yes "it compiles" but not without warnings. Code can compile with warnings and still be invalid C, see What must a C compiler do when it finds an error?.

  • b = (int)ptr; would still be problematic because the pointer type may not fit inside an int. You should be using uintptr_t rather than int for such cases. As a consequence of using the wrong type, your examples bug out and do not print the address when I run them on my computer, but rather some gibberish like 0022FE400000000A where it has managed to mess up pointer address + a value from the stack in the same 64 bit access. Which isn't guaranteed - it is undefined behavior - this was just what happened on one particular computer.

  • printf("%d\n", *(unsigned int*)b); is valid but questionable since it changes signedness of the actual data from int to unsigned int, then immediately tells printf to convert back to int by using the %d specifier. Doesn't make any sense.

  • Generally, you should printf pointers with %p or otherwise anything can happen.

  • *(void**)(b) is needlessly complicated, why not write (void*)b.

  • *(char**)(b) is questionable since it re-interprets the first byte of a into char which is potentially signed. You should be using uint8_t instead. More problematic yet is that the code states that the contents of b (that is: supposedly &a) is a char**, which you then convert to char*. It's an invalid pointer conversion since neither char* not char** are compatible types with int*. The compiler is free to do crazy things here.

  • *(char**)ptr_special is simply using the wrong pointer type. You convert from array to char* to void*. Then claim that the void* is actually a char** and try to de-reference it, so you get gibberish. Correct code is *(char*)ptr_special.

Summary:

Do not do wild pointer conversions between unrelated types unless you are absolutely sure what you are doing. The compiler may let all manner of crap through but that doesn't mean that the code is correct.

Lundin
  • 195,001
  • 40
  • 254
  • 396
  • I really appreciate your every point. I realized that there are many caution points when it comes to conversion. Actually, I have these curiosity while doing implementation malloc lab which has some weird (my opinion) conversions. By the way, thanks again. I have to be more careful and check again whenever the compiler works. – w00sung Jan 20 '21 at 11:01
1
printf("address : %p: value : %d\n", ptr_special, *(char**)ptr_special);

You're passing char* as an argument so the program will take 8 bytes(or 4, depending on compiler setting) from ptr_special.

The memory beginning at arr2 will be something like this:

01 02 03 04 05 cc cc cc cc cc cc cc cc cc cc cc

It might be different from many reasons(optimization setting, etc) but important thing is that there are some values after arr2. When you use expression *(char**)ptr_special, the program will interpret that as 0xcccccccccccccc05(or 0xcccccc05) and pass to printf function.

Because you used %d as format specifier, function will print the value as 4 byte integer 0xcccccc05, which is -858993659.

MarkSouls
  • 982
  • 4
  • 13
  • Thanks for your answer! I find it out after checking memory in visual studio debug mode. Thanks a lot !!!! – w00sung Jan 20 '21 at 10:22
1

Take a look at this:

#include <stdio.h>
int main()
{
    char arr2[] = { 1,2,3,4,5 };
    char arr3[] = { 1,2,3,4,5 };
    
    void* ptr_special2 = arr2 + 4;
    void* ptr_special3 = arr3 + 4;

    printf("address : %p: value : %d\n", ptr_special2, *(char**)ptr_special2);
    printf("address : %p: value : %d\n", ptr_special2, *(char**)ptr_special3);

    return 0;
}

output (may vary):

address : 0x7ffc5841755a: value : 50462981
address : 0x7ffc5841755a: value : 1074892805

Now try this:

    char arr2[] = { 1,2,3,4,5,0,0,0 };
    char arr3[] = { 1,2,3,4,5,1,0,0 };

output:

address : 0x7fffecb291fc: value : 5
address : 0x7fffecb291fc: value : 261

Try to work out what's happening before reading the ...

Explanation:

On my system, a "pointer to a pointer to a char" is 4 bytes long. So - when you take the address of the 5 in arr2, cast it to a char**, and dereference it - the system reads 4 bytes, starting at the 5.

In arr2, those 4 bytes are 5 0 0 0, which in a little-endian system is interpreted as 5.

In arr3, those 4 bytes are 5 1 0 0, which in a little-endian system is interpreted as 5 + 256 = 261.

In your code, the three bytes after the 5 are unknown. You don't know what the compiler has placed there (if anything). You are reading beyond the end of the array because you are trying to read 4 bytes (or whatever sizeof(char**) is on your system) from an array that only has 1 valid byte at that address.

This is undefined behaviour, which means the standard allows literally anything to happen - but typically, you could expect:

  1. "random garbage" bytes
  2. The beginning of the next local variable
  3. An access violation
Allison Lock
  • 2,375
  • 15
  • 17
  • I really appreciate your answer. I do understand how it works. The casting before dereferencing tell the compiler how many bytes trying to read. It is really adorable example!! – w00sung Jan 20 '21 at 10:48