A C function receives a copy of each argument, so how can it change an object owned by its caller? The answer is that a copied address can still reach the original object. That model explains pointer parameters, arrays, returned pointers, and function pointers. Every address in the diagrams is symbolic because real addresses vary between runs; readers who need the broader subject map can start with Programming Languages.
Pointers and functions in C start with two different values
Consider int score = 18; int *score_ptr = &score;. The object score stores the integer 18. The object score_ptr stores the address of score, and *score_ptr reads the value at that address, which is 18.
After *score_ptr = 24;, both score and *score_ptr read 24. The pointer still contains the same address. Only the pointed-to value changed.
The * symbol has two roles here. In int *score_ptr, it forms a pointer declaration. In *score_ptr = 24, it dereferences the pointer. The expression &score means "address of score". No numeric address is needed to reason about the program.
If this syntax is new, Coding & DSA Courses for Placements provides a structured route from C foundations towards data structures and problem solving.
Passing a pointer to a function: a complete swap trace
Functions in C: Call by Value vs Pointers, Swap Traced develops parameter passing through swap and increment questions. The same address-copy rule connects this swap trace to array parameters, returned pointers, and callbacks: the pointer value is copied, while the pointee may still be changed.
This program passes the addresses of two caller-owned integers:
#include <stdio.h>
void swap(int *left, int *right) {
int temp = *left;
*left = *right;
*right = temp;
}
int main(void) {
int first = 14, second = 29;
swap(&first, &second);
printf("first = %d, second = %d\n", first, second);
return 0;
}The output is first = 29, second = 14. Initially, left points to first, whose value is 14, and right points to second, whose value is 29. Then:
temp = *leftstores 14 intemp.*left = *rightstores 29 infirst.*right = tempstores 14 insecond.
C still passes left and right by value. The function receives copies of the two addresses, but those copied addresses reach the caller's integers. By contrast, void failed_swap(int left, int right) can exchange only its local integer copies. The caller's 14 and 29 remain unchanged.

Arrays as pointer parameters: sum four values
An array expression converts to a pointer when passed to a function. The function therefore needs the element count separately.
#include <stdio.h>
int sum(const int *values, size_t count) {
int total = 0;
for (size_t i = 0; i < count; i++) {
total += values[i];
}
return total;
}
int main(void) {
int marks[] = {4, 7, 9, 10};
size_t count = sizeof marks / sizeof marks[0];
printf("sum = %d\n", sum(marks, count));
return 0;
}The accumulator moves from 0 to 4 to 11 to 20 to 30, so the output is sum = 30. Also, values[2] and *(values + 2) both read 9. Subscript syntax is clearer in this loop, but the two expressions show the array-pointer relationship. C Programming & Data Structures places that relationship inside the wider subject map.
Inside sum, sizeof values gives the size of a pointer, not the size of the original array. Compute the count where the array still exists as an array, then pass it explicitly.
Returning a pointer safely: find and update the maximum
A function may return a pointer into a caller-owned array:
#include <stdio.h>
int *find_max(int *values, size_t count) {
if (count == 0) return NULL;
int *best = values;
for (size_t i = 1; i < count; i++) {
if (values[i] > *best) best = &values[i];
}
return best;
}
int main(void) {
int values[] = {12, 7, 31, 19, 25};
int *max_ptr = find_max(values, 5);
if (max_ptr != NULL) {
printf("max = %d\n", *max_ptr);
*max_ptr = 40;
}
printf("array = {%d, %d, %d, %d, %d}\n",
values[0], values[1], values[2], values[3], values[4]);
return 0;
}For {12, 7, 31, 19, 25}, best begins at index 0 with 12. It stays there after 7, moves to index 2 when the scan reaches 31, and stays there after 19 and 25. The returned pointer is &values[2], so *max_ptr is 31. Assigning *max_ptr = 40 changes the caller's array to {12, 7, 40, 19, 25}.
This is valid because the caller's array is still alive. Returning &local from int *bad(void) { int local = 5; return &local; } is invalid: local ceases to exist when the function returns. Treat that line only as a warning, not as code to copy.
Function pointers in C: choose a callback at run time
A function pointer lets the caller supply the calculation:
#include <stdio.h>
int add(int a, int b) { return a + b; }
int multiply(int a, int b) { return a * b; }
int apply(int a, int b, int (*operation)(int, int)) {
return operation(a, b);
}
int main(void) {
printf("add = %d\n", apply(6, 4, add));
printf("multiply = %d\n", apply(6, 4, multiply));
return 0;
}apply(6, 4, add) returns 10, while apply(6, 4, multiply) returns 24. Read int (*operation)(int, int) from the identifier outwards: operation is a pointer to a function that takes two int arguments and returns an int. Both operation(6, 4) and (*operation)(6, 4) call the selected function.
Here, apply uses a callback. It does not know whether it will add or multiply until the caller supplies the function pointer.

Pointer and function traps to catch early
Compare these declarations carefully:
int *make_value(void)declares a function returningint *.int (*make_value)(void)declares a pointer to a function returningint.
Parentheses change what binds to the identifier. Safety rules matter just as much: check a data pointer before evaluating *ptr, check a function pointer before calling operation(a, b), and never retain or return the address of an expired local object. A modern compiler should warn about a returned local address, but the lifetime rule is why the code is wrong.
Trace pointer-function questions with three columns
For an exam or interview trace, write three columns: each object and its current value, each pointer and its pointee, and each object's lifetime.
Apply that method here: void bump(int *p) { *p += 3; p++; }, followed by int values[] = {5, 11}; int *cursor = values; bump(cursor);. The copied parameter p initially points to values[0]. The first statement changes 5 to 8. The second advances only the local pointer copy.
The final array is {8, 11}. The caller's cursor still points to values[0], so *cursor is 8. This method also handles output prediction after dereferencing, function-pointer declarations, returned-pointer lifetimes, and the crucial distinction between changing a pointee and changing a pointer copy.
Pointers and functions in C: the short version
Keep four rules together: pointer arguments are passed by value, dereferencing can reach a caller-owned object, a returned pointer works only while its pointee lives, and a function pointer stores the address of a function with a matching type.
For a five-minute revision, predict the results for score, swap, sum, find_max, apply, and bump. Then run the programs with compiler warnings enabled and explain every prediction that differed. For a structured sequence that combines these ideas with practice, continue to C Language: Concepts, MCQs, Coding Questions.




