C / POINTERS
Memory addresses and the address-of operator
Take the address of any object with &, store it in a pointer, and reason about which addresses are equal, distinct, or impossible to take.
What you will learn
- Apply & to a named object, array element or struct member to get its address
- Store an address in a matching pointer and print it with %p after a (void *) cast
- Explain why equal values still have different addresses, and use == as an identity test
- Spot why &42, &(a + b) and & on a register variable cannot compile
Understanding Memory addresses and the address-of operator
Every byte your program can touch has a number, and that number is its address. When you write int count = 42; the compiler reserves a run of adjacent bytes (four on a typical desktop compiler) and binds the name count to them: the name is a compile-time label, the address is the runtime location. &count evaluates to that location, specifically the number of the first byte of the object, and its type is int * so the compiler still knows the run is four bytes wide and holds an integer.
The & operator only works on something that designates storage, which C calls an lvalue. 42 is a value rather than a place, and a + b is a computation whose result may never leave a CPU register, so &42 and &(a + b) are rejected at compile time instead of failing at runtime. The flip side is that every object alive at the same moment has its own distinct address, so comparing two addresses with == asks "are these the same object?", which is a different question from comparing the values stored there.
To display an address, use %p and cast the argument to void *, because void * is the only pointer type %p is defined for. The digits you see are chosen by the loader and operating system when the process starts, so they change between runs and between machines; on Linux address-space randomisation guarantees it. Treat them as opaque labels: never hard-code one, and never assume that two variables declared next to each other sit next to each other in memory.
<stdio.h>
int main(void)
{
int count = 42;
char tag = 'A';
int *count_address = &count; /* & yields the address; int * stores it */
printf("value in count: %d\n", count);
printf("count_address == &count? %d\n", count_address == &count);
printf("count spans %zu bytes from its address\n", sizeof count);
printf("tag spans %zu byte from its address\n", sizeof tag);
printf("&count == &tag? %d\n", (void *)&count == (void *)&tag);
/* printf("%p\n", (void *)&count); would print something like 0x7ffc9a3b1c4c */
return 0;
}
&x yields the location where x lives rather than a copy of what x holds, so it identifies an object instead of a value.
Worked examples
Same value, different address
Shows that an address identifies storage, so two variables holding 5 are still two different objects.
<stdio.h>
int main(void)
{
int a = 5;
int b = 5;
printf("same value: %d\n", a == b);
printf("same address: %d\n", &a == &b);
return 0;
}
Example explained
Line 1int a = 5; int b = 5; defines two separate objects, each given its own bytes.
Line 2a == b reads the stored values and finds them equal, so it prints 1.
Line 3&a == &b compares locations, and two objects alive at once can never share one, so it prints 0.
Line 4This is why address comparison is an identity check: it answers "which object", not "which value".
A struct begins where its first member begins
Demonstrates that & works on struct members and that a struct's own address coincides with its first member's.
<stdio.h>
struct point { int x; int y; };
int main(void)
{
struct point p = {3, 4};
printf("p = {%d, %d}\n", p.x, p.y);
printf("&p and &p.x are the same address: %d\n", (void *)&p == (void *)&p.x);
printf("&p and &p.y are the same address: %d\n", (void *)&p == (void *)&p.y);
return 0;
}
Example explained
Line 1&p.x applies & to a member, which is a real object inside p and therefore has an address of its own.
Line 2C guarantees that a pointer to a struct, suitably converted, points at its first member, so line 2 prints 1.
Line 3p.y starts further along the struct's bytes, so its address differs from the struct's own address.
Line 4The (void *) casts are needed because struct point * and int * are different types and cannot be compared directly.
A parameter has its own address
Proves that a by-value parameter is a fresh object, not another name for the caller's variable.
<stdio.h>
static int shares_address(int copy, int *original)
{
return © == original;
}
int main(void)
{
int n = 10;
printf("parameter shares n's address: %d\n", shares_address(n, &n));
printf("n is still %d\n", n);
return 0;
}
Example explained
Line 1shares_address(n, &n) passes two independent things: the value 10, and the address of n.
Line 2copy is a new object initialised from that value, so © names different bytes than original does.
Line 3The printed 0 is the reason a plain by-value parameter cannot change the caller's variable: the function only holds its own storage.
Important notes
%p is defined only for void *, so cast the argument; the digits it prints are opaque labels that differ between runs and machines and say nothing about how much memory you have.
& requires an object with storage: &42, &(a + b) and & applied to a register-qualified variable are compile errors, while array elements and struct members are fine because they are objects.
Common mistakes
Printing an address with %d, as in printf("%d", &count): the format and the argument type disagree, which is undefined behaviour and typically shows a truncated number on 64-bit systems.
Writing int *p = count; when you meant int *p = &count;: the value 42 is now treated as an address, the compiler warns about an int-to-pointer conversion, and the first use of p touches address 42 and crashes.
Assuming declaration order implies address order and deriving one variable's address from another's: the compiler may place, pad or reorder separate locals freely, so the arithmetic happens to work on one build and reads unrelated bytes on the next.
Try it yourself
Change, predict, then run
Declare an int, a char and a double in main, print all three addresses with %p and (void *) casts, then add lines printing whether any two of those addresses are equal. Run the program twice and note which parts of the output stay the same.
Open the C workspaceCheck your understanding
Two int variables a and b are both set to 7. What does a program that compares them and compares their addresses report?
- Both a == b and &a == &b are 1, because identical values are stored only once.
- a == b is 1, but &a == &b is undefined behaviour, since pointers into different objects cannot be compared.
- a == b is 1 and &a == &b is 0, because an address identifies the object, not the value it holds.
- &a == &b is 1 whenever the compiler can prove that neither variable is modified.
Show answer
Two objects alive at the same time occupy different storage, so their addresses differ no matter what values they hold, and == on addresses reports object identity, giving 0. Option 1 is tempting because relational comparisons (< and >) between pointers into unrelated objects really are not portable, but == and != are always well defined and simply report "not the same object".