2

Lately, while working in C, I was developing a generic function. Here is the layout of structure:

typedef struct student_{
    char name[32];
    int roll_no;
    unsigned int height;
} student_t;

and below is my function. What all the function does is, it compares the given instance of a structure with one of the keys ( and hence generic). Here the key being roll_no

static int
check_student_by_roll_number(void *data,void *key){
    student_t * student = (student_t*) data;
    unsigned int roll_no = *(unsigned int*)(key); // Getting segmentation fault here
    if(student->roll_no == roll_no)return 0;
    return -1;
}

But the same fault code, when I write as

unsigned int roll_no = *(unsigned int*)(&key);

I am calling the function as

check_student_by_roll_number(john,(void*)40); // john is a pointer to valid structure 

The segmentation fault is gone and code works fine.So what is difference between *(unsigned int*)(key) and *(unsigned int*)(&key)

stardep
  • 129
  • 1
  • 13
  • 2
    You're passing an `int` (`40`) hard-cast to `void*`. That doesn't mean the function gets the address of some memory location where a `40` resides. It means you literally manufacturing a pointer with the scalar value `40` . That pointer clearly doesn't point to anything valid, so the dereference invokes undefined behavior. Regarding the non-"crash" code, all `*(unsigned int*)(&key)` is doing is casting a `void**` to an `unsigned int*`, then dereferencing it, which isn't really what you want either. – WhozCraig Jul 26 '23 at 05:24
  • @WhozCraig can you please tell me, what is the behaviour of that manufactured pointer ? – stardep Jul 26 '23 at 05:28

1 Answers1

2

In this scenario, key is treated as some kind of integer type. key doesn't contain a pointer, so dereferencing it is clearly wrong.

To get back 40 from (void*)40, you need (int)(void*)40.

You could also cast straight to unsigned int which is what you want here.

unsigned int roll_no = (unsigned int)key;

Generally speaking, storing an integer value in a variable expected to hold a pointer isn't safe. At least not on machines with trap representations. But there's no issue on a x86 or x86-64 (as long as the pointer is large enough to store the integer).

*(unsigned int*)&key is worse. It assumes the memory layout of a void* is identical to that of an unsigned int. This won't be the case on the big-endian machine if the types have different sizes. It will effectively work on a little-endian machine (as long as the integer type is no larger than the pointer type). Might as well use the inverse of what you used outside the function ((unsigned int)key) and avoid using a second hack in addition to your first.

ikegami
  • 367,544
  • 15
  • 269
  • 518
  • In general, can you explain me the difference between int* p = (int*) x and int* p = (int*) &x ?? What does that additional & sign does ? Here, you can consider x as a void pointer which points to a valid integer data. – stardep Jul 27 '23 at 05:12
  • `x` gets the value of `x`. `&x` creates a pointer to `x`. – ikegami Jul 27 '23 at 05:13
  • But to get x value of x, there must be a de referencing first right? as x is itself a pointer ? – stardep Jul 27 '23 at 05:15
  • There's no dereferencing (`*E`, `E->`, `E[]`, `E()`) here. Yes, the value of `x` is a pointer. – ikegami Jul 27 '23 at 05:16
  • Is there any thumb rule for typecasting of void pointers ? – stardep Jul 27 '23 at 05:17
  • Only do so to recuperate a pointer that was previously casted to a `void*`. Or for the result of an `malloc`/`realloc`/`calloc` – ikegami Jul 27 '23 at 05:18
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/254685/discussion-between-stardep-and-ikegami). – stardep Jul 27 '23 at 05:19