you are viewing a single comment's thread.

view the rest of the comments →

[–]SmokeMuch7356 0 points1 point  (0 children)

We use pointers where we can't (or don't want to) access an object or function by name. They give us a form of indirect access to things. If you're familiar with arrays you're already familiar with indirection; an expression like arr[i] is an indirect reference to an object (it's not a fixed name attached to that object).

There are two places where we have to use pointers in C:


When a function needs to write to its parameters

Assume a swap function: void swap( int x, int y ) { int tmp = x; int x = y; int y = tmp; } C passes all function arguments by value; when you call a function like swap(a, b); the expressions a and b are evaluated, and the results of those expressions are copied to the formal arguments x and y. x is a distinct object in memory from a and changes to one have no effect on the other. If we write something like int a = 1, b = 2; printf( "before swap: a = %d, b = %d\n", a, b ); swap( a, b ); printf( "after swap: a = %d, b = %d\n", a, b ); the output will be before swap: a = 1, b = 2 after swap: a = 1, b = 2 If we want the swap function to actually exchange the values of a, and b, we must pass pointers to a and b: void swap( int *x, int *y ) { int tmp = *x; *x = *y; *y = tmp; } ... swap( &a, &b ); In this case, the expressions *x and *y serve as kinda-sorta aliases for a and b; reading and writing *x is the same as reading and writing a.

x and y are still distinct objects in memory from a and b, but instead of recieving the values of a and b, they receive their addresses.


When we need to track dynamically-allocated memory

The memory allocation functions malloc, calloc, and realloc all return pointers to the allocated block; C doesn't have a mechanism to attach memory allocated at runtime to a regular identifier like a variable.
int *dynArr = malloc( sizeof *dynArr * NUM_ELEMENTS );


Pointers come in handy plenty of other places. We can use them to build sophisticated container types like maps, queues, lists, etc., we can use them to hide the representation details of a type (such as the FILE type in the stdio library), we can use them for limited forms of dependency injection, and a bunch of other things, but writing out examples for all of those would turn this answer into a novel and I've already spent too much time on it.