I don't know when you have to use a * when you are working with pointers an when not. Everything that is carrying a hexadecimal is a pointer right? The variable name of an array is carrying it's hexadecimal memory location. So why isn't it a pointer?
Last thing first - the name of an array is not a pointer; it does not store an address anywhere. When you define an array, it will be laid out more or less like the following:
+---+
arr: | | arr[0] Increasing address
+---+ |
| | arr[1] |
+---+ |
... |
+---+ |
| | arr[n-1] V
+---+
There is no storage set aside for an object arr
separate from the array elements arr[0]
through arr[n-1]
. C does not store any metadata such as length or starting address as part of the array object.
Instead, there is a rule that says if an array expression appears in your code and that expression is not the operand of the sizeof
or unary &
operators, it will be converted ("decay") to a pointer expression, and the value of the pointer expression will be the address of the first element of the array.
So given the declaration
T arr[N]; // for any type T
then the following are true:
Expression Type Decays to Value
---------- ---- --------- -----
arr T [N] T * Address of first element
&arr T (*)[N] n/a Address of array (same value
as above
*arr T n/a Value of arr[0]
arr[i] T n/a Value of i'th element
&arr[i] T * n/a Address of i'th element
sizeof arr size_t Number of storage units (bytes)
taken up by arr
The expressions arr
, &arr
, and &arr[0]
all yield the same value (the address of the first element of the array is the same as the address of the array), but their types aren't all the same; arr
and &arr[0]
have type T *
, while &arr
has type T (*)[N]
(pointer to N
-element array of T
).
Everything that is carrying a hexadecimal is a pointer right?
Hexadecimal is just a particular representation of binary data; it's not a type in and of itself. And not everything that can be written or displayed in hex is a pointer. I can assign the value 0xDEADBEEF
to any 32-bit integer type; that doesn't make it a pointer.
The exact representation of a pointer can vary between architectures; it can even vary between different pointer types on the same architecture. For a flat memory model (like any modern desktop architecture) it will be a simple integral value. For a segmented architecture (like the old 8086/DOS days) it could be a pair of values for page # and offset.
A pointer value may not be as wide as the type used to store it. For example, the old Motorola 68000 only had 24 address lines, so any pointer value would only be 24 bits wide. However, to make life easier, most compilers used 32-bit types to represent pointers, leaving the upper 8 bits unused (powers of 2 are convenient).
I don't know when you have to use a * when you are working with pointers an when not.
Pretty simple - when you want to refer to the pointed-to entity, use the *
; when you want to refer to the pointer itself, leave it off.
Another way to look at it - the expression *ptr
is equivalent to the expression var
, so any time you want to refer to the contents of var
you would use *ptr
.
A more concrete example might help. Assume the following:
void bar( T *p )
{
*p = new_value(); // write new value to *p
}
void foo( void )
{
T var;
bar( &var ); // write a new value to var
}
In the example above, the following are true:
p == &var
*p == var
If I write something to *p
, I'm actually updating var
. If I write something to p
, I'm setting it to point to something other than var
.
This code above is actually the primary reason why pointers exist in the first place. In C, all function arguments are passed by value; that is, the formal parameter in the function definition is a separate object from the actual parameter in the function call. Any updates to the formal parameter are not reflected in the actual parameter. If we change the code as follows:
void bar( T p )
{
p = new_value(); // write new value to p
}
void foo( void )
{
T var;
bar( var ); // var is not updated
}
The value of p
is changed, but since p
is a different object in memory from var
, the value in var
remains unchanged. The only way for a function to update the actual parameter is through a pointer.
So, if you want to update the thing p
points to, write to *p
. If you want to set p
to point to a different object, write to p
:
int x = 0, y = 1;
int *p = &x; // p initially points to x
printf( "&x = %p, x = %d, p = %p, *p = %d\n", (void *) &x, x, (void *) p, *p );
*p = 3;
printf( "&x = %p, x = %d, p = %p, *p = %d\n", (void *) &x, x, (void *) p, *p );
p = y; // set p to point to y
printf( "&y = %p, y = %d, p = %p, *p = %d\n", (void *) &y, y, (void *) p, *p );
At this point you're probably asking, "why do I use the asterisk in int *p = &x
and not in p = y
?" In the first case, we're declaring p
as a pointer and initializing it in the same operation, and the *
is required by the declaration syntax. In that case, we're writing to p
, not *p
. It would be equivalent to writing
int *p;
p = &x;
Also note that in a declaration the *
is bound to the variable name, not the type specifier; it's parsed as int (*p);
.
C declarations are based on the types of expressions, not objects. If p
is a pointer to an int
, and we want to refer to the pointed-to value, we use the *
operator to dereference it, like so:
x = *p;
The type of the expression *p
is int
, so the declaration is written as
int *p;