Reputation: 444
I have seen that NULL is the equivalent of (void*)0
. But I don't understand why zero needs to be typecasted to void*
. And what is really happening under the hood when we do something like this
int *p = (int*)10;
Is (int*)
extending the address space of integer 10(which take 4 bytes) to 8 bytes in the above statement?
Upvotes: 0
Views: 1561
Reputation: 386706
A pointer is a short arrangement of bytes. Via a cast, C allows to pretend these bytes represent an integer.
Type* p = ...;
intptr_t i = (intptr_t)p;
This is occasionally (but rarely) useful when you need to pass a pointer to an interface expecting an integer. To recover the pointer, one just reverses the cast.
Type* recovered_p = (Type*)i;
This does not allocate any memory. You can only deference recovered_p
if i
contain bytes that, if treated as a Type*
, references a previously allocated Type
value. This means that the following doesn't produce a usable pointer:
int *p = (int*)10;
An example that uses an integer to store a pointer.
typedef void (*Visitor)(intptr_t, ListNode*);
void List_visit(List* list, Visitor visitor, intptr_t arg) {
for (ListNode* node = list->head; node; node=node->next) {
visitor(arg, node);
}
}
void printer(intptr_t arg, ListNode* node) {
State* state = (intptr_t)arg;
printf("%*s%s\n", ( state->count++ )*2, "", node->value);
}
int main(void) {
List* list = ...;
State* state = ...;
List_visit(list, printer, (intptr_t)state);
List_free(list);
State_free(state);
return 0;
}
Upvotes: -1
Reputation: 48083
There are a couple of ways of answering this.
We say that a pointer value is the address of a memory location. But different computers have used different addressing schemes for memory. C is a higher-level language, portable across many kinds of computers. C does not mandate a particular memory architecture. As far as the C programming language is concerned, memory addresses could literally be things like "123 Fourth Ave.", and it's hard to imagine converting back and forth between an integer and an address like that.
Now, for any machine you're likely to use, memory is actually linearly addressed in a reasonably straightforward and unsurprising way. If your program has 1,000 bytes of memory available to it, the addresses of those bytes might range from 0 up to 999. So if you say
char *cp = (char *)10;
you're just setting up a pointer to the byte located at address 10 (or, that is, the 11th byte in your program's address space).
Now, in C, a pointer is not just the raw address of some location in memory. In C, a pointer is also declared to specify what type of data it points to. So if we say
int *ip = (int *)10;
we're setting up a pointer to one int's worth of data located at address 10. It's the same point in memory as cp
pointed to, but since it's an int pointer, it's going to access an int's worth of bytes, not one byte like cp
did. If we're on an old 16-bit machine, and int is two bytes, we could think of ip
as pointing at the fifth int in our address space.
A cast in C can actually do two things: (1) convert a value ("change the bits"), or (2) change the interpretation of a value. If we say float f = (float)3;
, we're converting between the integer representation of 3 and a floating-point representation of 3, which is likely to be quite different. If we go in the other direction, with something like int i = (int)3.14;
, we're also throwing away the fractional part, so there's even more conversion going on. But if we say int *ip = (int *)10;
, we're not really doing anything with the value 10, we're just reinterpreting it as a pointer. And if we say char *cp = (char *)ip
, we're again not changing anything, we're just reinterpreting to a different kind of pointer.
I hasten to add, though, that everything I've said here about pointer conversions is (a) very low-level and machine-dependent, and (b) not the sort of thing that ordinary C programmers are supposed to have to think about during ordinary programming tasks, and (c) not guaranteed by the C language.
In particular, even when programming for a computer with a conventional, linearly-addressed memory model, it's likely that your program doesn't have access to address 10, so these pointers (cp
and ip
) might be pretty useless, might generate exceptions if you try to use them. (Also, when we have a pointer like ip
that points at more than 1 byte, there's the question of which bytes it points to. If ip
is 10, it probably points at bytes 10 and 11 on a 16-bit, byte-addressed machine, but which of those two bytes is the low-order half of the int and which is the high-order half? It depends on whether it's a "big endian" or "little endian" machine.)
But then we come to null pointers. When you use a constant "0" as a pointer value, things are a little different. If you say
void *p = (void *)0;
you are not, strictly speaking, saying "make p
point to address 0
". Instead, you are saying "make p
be a null pointer". But it turns out this has nothing to do with the cast, it's because of a special case in the language: in a pointer context, the constant 0 represents a null pointer constant.
A null pointer is a special pointer value that's defined to point nowhere. It might be represented internally as a pointer to address 0, or it might be represented some other way. (If it is in fact represented as a pointer to address 0, your compiler will be careful to arrange that there's never any actual data at address 0, so that it's still true that the pointer "points nowhere" even though it points to address 0. This is sort of confusing, sorry about that.)
Although pointers to raw addresses like 10
are low-level and dangerous and machine-dependent, null pointers are well-defined and perfectly fine. For example, when you call malloc
and it can't give you the memory you asked for, it returns a null pointer to tell you so. When you test malloc
's return value to see if it succeeded or failed, you just check to see if it gave you a null pointer or not, and there's nothing low-level or nonportable or discouraged about doing so.
See http://c-faq.com/null/index.html for much more on all this.
Upvotes: 4