Let me C : Pointers And Arrays
A pointer is just a variable whose value is the address of another variable.
Let’s introduce two variables:
&(address of): give me the address of this variable*(dereference, in a declaration or expression context): give me the value stored at this address
1
2
3
4
int x=1;
int *ip; //ip is declared as pointer to int
ip=&x; //ip now holds the address of x
printf("%d\n", *ip); //deference, prints 1
Note the dual role of
*here - in the declarationint *ip;, it means “ip is a pointer to int.” In the expression*ip, it means “dereference ip.” Same symbol, different job depending on context.
Example:
1
2
3
4
5
6
7
int y = 42;
int *p = &y;
*p=100;
printf("%d\n", y);
// Output
100 //Why?
Why? - That’s the core insight of pointers: *p = 100 doesn’t change what p points to, it changes the value at that location. Since p points to y, you’re editing y “through the back door.”
Pointers and Function Arguments
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>
void swap(int *pa, int *pb)
{
int temp;
temp=*pa;
*pa=*pb;
*pb=temp;
}
int main()
{
int a=5,b=10;
printf("The value of a is %d and b is %d before the swap\n", a,b);
swap(&a,&b);
printf("The value of a is %d and b is %d after the swap\n", a,b);
return 0;
}
Pointers and Arrays
1
2
3
int a[10];
int *pa;
pa=a; //equivalent to pa=&a[0];
Core Fact: The name of an array, when used in an expression, “decays” into a pointer to its first element.
So that means, these two statements are equal:
1
2
a[i] == *(a + i)
pa[i] == *(pa + i)
Example:
pa[2] → indexing syntax, reads as “element 2 of pa”
(pa + 2) → pointer arithmetic, reads as “go 2 ints past where pa points, then dereference”
Now lets look at this example and see how we can use pointers instead:
1
2
3
4
5
6
//Regular way
int a[5] = {10, 20, 30, 40, 50};
int sum = 0;
int i;
for (i = 0; i < 5; i++)
sum += a[i];
1
2
3
4
5
6
//Using the pointer
int a[5] = {10, 20, 30, 40, 50};
int sum = 0;
int *p;
for (p = a; p < a + 5; p++)
sum += *p;
One Important difference
1
2
3
4
5
int a[5] = {10, 20, 30, 40, 50};
int *pa = a;
pa++; // legal - pa is a variable, can be reassigned
a++; // ILLEGAL - a is not a variable, it's a fixed label for the array's address
Address Arithmetic
Important concept :
i * sizeof(the pointed-to type)
Suppose int is 4 bytes on your system, and a starts at memory address 1000.
1
int a[5] = {10, 20, 30, 40, 50};
Memory layout will look like this:
1
2
3
address: 1000 1004 1008 1012 1016
value: a[0] a[1] a[2] a[3] a[4]
10 20 30 40 50
Each int takes 4 bytes, so consecutive elements are 4 bytes apart, not 1 byte apart.
Now, a + 2 doesn’t mean “address 1002” (that would land you inside a[0], garbage). It means:
1
a + 2 = 1000 + (2 * sizeof(int)) = 1000 + 8 = 1008
…which correctly lands you at a[2]. The compiler knows a is int *, so it automatically multiplies your offset by sizeof(int) before adding it to the raw address.
Legal Pointer Operations
- Add or subtract an integer to/from a pointer:
p + n,p - n- Subtract one pointer from another (only if both point into the same array):
p2 - p1- Compare two pointers with
<,<=,>,>=,==,!=(only meaningful if both point into the same array)
1
2
3
4
5
int a[10];
int *p1 = &a[2];
int *p2 = &a[7];
int n = p2 - p1; // n == 5
Character Pointers and Functions
1
2
char *pmessage;
pmessage = "now is the time";
Note: C has no built-in string type. A “string” is just a convention - an array of
charsterminated by a special sentinel value,'\0'(the null character, value0).
Here, "now is the time" is a string constant - stored somewhere in memory as a sequence of characters plus a trailing '\0'. The name pmessage doesn’t hold the characters themselves; it holds the address of the first character ('n'). This is exactly the array-decay behavior from pointer and array topic above.
1
2
char amessage[] = "now is the time"; // an array - stores the actual characters
char *pmessage = "now is the time"; // a pointer - points to the string constant
amessageis an array; the characters live insideamessage’s own memory. You can modify individual characters:amessage[0] = 'N';is legal.pmessageis a pointer; it points at a string literal, which many compilers place in read-only memory. Attemptingpmessage[0] = 'N';is undefined behavior - it might crash, or silently corrupt something, depending on the platform.
Example: strlen function
1
2
3
4
5
6
7
int strlen(char *s)
{
int n;
for (n = 0; *s != '\0'; s++)
n++;
return n;
}
Pointer Arrays: Pointers to Pointers
I tied to follow the example in book but it flew over my head. So I am relying on this w3schools note to get acquainted with the pointer concept.
1
2
3
4
5
6
7
int myNum = 5;
int *ptr = &myNum;
int **pptr = &ptr;
**pptr = 20; // changes myNum
printf("myNum = %d\n", myNum); // prints 20
- A pointer to pointer stores the address of another pointer.
*ptrgives the value of a variable.**pptrgives the same value by following two levels of indirection.
Multi-dimensional Arrays
Again, I didn’t want to spend much time on it. I rather choose to go through this Multi-dimensional note to get the gist of it.
From K&R book:
int m[4][3]is one contiguous block of 12 ints in memory - not separate rows, not pointers to anything. It’s a flat sequence that we interpret as rows and columns.- Indexing formula to locate any element within in the block
1
offset = row * (columns per row) + column
Example
1 2
int a[10][20]; // a true 2D array: ONE block of 200 ints, contiguous int *b[10]; // an array of 10 pointers: EACH pointer can point anywhere separately
Initialization of Pointer Arrays
1
2
3
4
5
6
7
8
9
10
11
12
char *month_name(int n)
{
static char *name[] = {
"Illegal month",
"January", "February", "March",
"April", "May", "June",
"July", "August", "September",
"October", "November", "December"
};
return (n < 1 || n > 12) ? name[0] : name[n];
}
Let’s break it down:
The function name
char *month_name(int n), how to read it? - month_name is a function that takes integer n input and returnschar *meaning - returns a pointer to character aka address of the character.the
static char *name[]={}, how to read it? -nameis an array[]ofchar *(array of pointers to char)static- This meansnameis created once and keeps its values between function calls, rather than being rebuilt from scratch every timemonth_name()runs. Withoutstatic, a local array like this would normally be re-initialized on every single call - wasteful for a fixed table that never changes.
Pointers to Functions
Just like variables live at addresses, so does compiled code. A function has an address in memory too - and C lets you store that address in a pointer, then call the function through that pointer.
1
int (*comp)(void *, void *);
- This says:
compis a pointer to a function that takes twovoid *arguments and returns anint - Here
void *means a pointer to some type, unspecified
Note:
1
int *comp(void *, void *); // DIFFERENT: a function named comp, returning int*
Important Concept
Let’s recall
1
2
int a[5];
int *pa = a; // no & needed - 'a' already means "address of a[0]"
Similarly
1
2
3
4
5
int numcmp(int a, int b) { ... }
int (*p)(int, int) = numcmp; // 'numcmp' by itself already means its own address
int (*p)(int, int) = &numcmp; // also legal, & is optional here, means the same thing
Let’s look at the below example and its output to better understand the above concept.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <stdio.h>
int numcmp(int a, int b)
{
return a - b;
}
int main(void)
{
// 'numcmp' by itself (no parentheses) means "the address of this function"
// just like an array name 'a' by itself means "the address of a[0]"
printf("Address of numcmp: %p\n", (void *)numcmp);
int (*p)(int, int);
p = numcmp; // no & needed - numcmp already IS the address
printf("Address stored in p: %p\n", (void *)p);
// both addresses printed above will be IDENTICAL
int result = p(10, 3); // call the function through the pointer
printf("p(10, 3) = %d\n", result);
return 0;
}
Output
1
2
3
Address of numcmp: 0x5578a1b3e169
Address stored in p: 0x5578a1b3e169
p(10, 3) = 7
Example
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <stdio.h>
// Two candidate functions with matching signatures
int add(int a, int b)
{
return a + b;
}
int multiply(int a, int b)
{
return a * b;
}
// A function that takes a "pointer to function" as a parameter
int compute(int x, int y, int (*op)(int, int))
{
return op(x, y); // call whichever function op points to
}
int main(void)
{
int (*fp)(int, int); // fp: pointer to a function taking (int,int), returning int
fp = add; // fp now points at add
printf("add: %d\n", fp(3, 4)); // calls add(3,4) -> 7
fp = multiply; // fp now points at multiply
printf("multiply: %d\n", fp(3, 4)); // calls multiply(3,4) -> 12
// Passing function pointers directly into another function
printf("compute(add): %d\n", compute(5, 6, add)); // 11
printf("compute(multiply): %d\n", compute(5, 6, multiply)); // 30
return 0;
}
Output
1
2
3
4
add: 7
multiply: 12
compute(add): 11
compute(multiply): 30
Complicated Declarations
As the name suggests, it is complicated and my brain couldn’t grasp the idea completely for now. My brain hurts when I try to comprehend the concepts presented in this section. So, I am leaving this part empty for now. And, I will revisit this later.
Basically this part sums up the different ways the pointers are bind using the () and how to interpret it. More on this later.
