& in C
- Alessandro Salvato
- 15 mar
- Tempo di lettura: 4 min
In the C programming language, few symbols are as small yet as conceptually dense as &.
At first glance it looks like a simple operator, but the ampersand appears in several completely different contexts:
as the address-of operator to obtain the memory address of a variable;
as the bitwise AND operator between integer values;
as part of &&, the logical AND used in boolean expressions;
inside common patterns involving pointers, simulated pass-by-reference, memory‑mapped registers, and bit masking.
Understanding & properly means understanding a core part of C's mental model: memory, bits, and expression semantics.
In this article we perform a complete deep dive into every variant of & in C, with practical examples, common pitfalls, and real-world embedded programming patterns.
Why & Matters So Much in C
C is a language very close to the machine model. Because of that, a single symbol can represent operations that exist at very different abstraction levels.
For &, the three primary meanings are:
obtain a memory address
combine values at the bit level
evaluate logical conjunction (with &&)
These correspond to three conceptual layers of C:
Level | Operator usage |
Memory | &x |
Bits | a & b |
Logic | a && b |
This layering makes the ampersand an excellent topic for anyone who wants to deepen their understanding of C.
1. & as the Address-of Operator
The most well-known use of & is obtaining the memory address of an object.
int x = 42;
int *p = &x;Here:
x is an int
&x means "the address of x"
p is a pointer to int
If x resides at address 0x20000010, then &x literally evaluates to that address.
Example
#include <stdio.h>
int main(void)
{
int x = 42;
int *p = &x;
printf("x = %d\n", x);
printf("&x = %p\n", (void *)&x);
printf("p = %p\n", (void *)p);
printf("*p = %d\n", *p);
return 0;
}Key observations:
p contains the same address as &x
*p dereferences the pointer
Simulating Pass-by-Reference
C uses pass-by-value only, but passing an address allows functions to modify caller variables.
void increment(int *value)
{
(*value)++;
}
int main(void)
{
int counter = 10;
increment(&counter);
printf("%d\n", counter);
}his pattern is extremely common in:
library APIs
embedded drivers
buffer management
Example with multiple outputs
void min_max(int a, int b, int *min, int *max)
{
if (a < b)
{
*min = a;
*max = b;
}
else
{
*min = b;
*max = a;
}
}
min_max(12, 7, &low, &high);& and Arrays
Arrays introduce subtle but important distinctions.
int arr[5] = {1,2,3,4,5};Consider the following:
Expression | Type |
arr | int* (decays to pointer) |
&arr[0] | int* |
&arr | int(*)[5] |
Even if the printed addresses appear identical, the types are different, and that affects pointer arithmetic.
int *p1 = arr;
int (*p2)[5] = &arr;p1 + 1 moves by sizeof(int)
p2 + 1 moves by sizeof(arr)
Understanding this difference is critical when dealing with pointer arithmetic or multidimensional arrays.
& with Structures
Taking the address of structures is very common.
struct Point
{
int x;
int y;
};
struct Point p = {10,20};
struct Point *ptr = &p;You can then use the arrow operator:
ptr->x = 15;
ptr->y = 25;which is equivalent to:
(*ptr).x = 15;& and scanf
Many programmers encounter & early with scanf.
int value;
scanf("%d", &value);scanf must write into the variable, therefore it requires its address.
Incorrect usage:
scanf("%d", value);This passes an integer instead of a pointer and causes undefined behavior.
Exception with character arrays
char name[20];
scanf("%19s", name);Here name already decays to char *.
2. & as Bitwise AND
The second meaning of & is bitwise AND.
unsigned a = 0b1101;
unsigned b = 0b1011;
unsigned c = a & b;Result (bit by bit):
1101
1011
----
1001Bitwise AND is fundamental in:
bit masking
flag checking
hardware register manipulation
protocol parsing
packed binary formats
Checking Flags
#define RX_READY (1u << 0)
#define TX_READY (1u << 1)
#define OVERRUN (1u << 2)
if (status & OVERRUN)
{
// error detected
}Embedded Example: Hardware Register
#define UART_SR (*(volatile unsigned int *)0x40001000u)
#define UART_RXNE (1u << 5)
if (UART_SR & UART_RXNE)
{
// data available
}Clearing bit
value &= ~(1u << 3);Extracting Bit Fields
unsigned reg = 0xAB;
unsigned low = reg & 0x0F;
/*
10101011
00001111
--------
00001011 (0x0B)
*/3. && Logical AND
if ((x > 0) && (y > 0))
{
// both positive
}Short-Circuit Evaluation
if ((ptr != NULL) && (ptr->value > 0))The second expression executes only if the first is true.
Using & instead would be dangerous.
Operator Precedence Trap
if (flags & MASK == 0)Actually interpreted as:
flags & (MASK == 0)Correct form:
if ((flags & MASK) == 0)Rule of thumb:
always use parentheses when checking bit masks.
4. Compound Operator &=
x &= mask;Equivalent to:
x = x & mask;Example:
unsigned config = 0xFF;
config &= 0x0F;5. Real Embedded Patterns
Checking a GPIO input
if ((GPIO_PORT->IDR & PIN3_MASK) != 0)
{
// pin high
}Checking multiple error flags
#define ERRORS_MASK (CRC_ERR | FRAME_ERR | OVERRUN_ERR)
if ((status & ERRORS_MASK) != 0)
{
// error detected
}Verifying all flags
if ((status & REQUIRED_FLAGS) == REQUIRED_FLAGS)
{
// all flags active
}Address-of and Bitwise in the Same Code
typedef struct
{
volatile unsigned int CR;
volatile unsigned int SR;
volatile unsigned int DR;
} UartRegs;
#define UART0 ((UartRegs *)0x40001000u)
UartRegs *uart = UART0;
volatile unsigned int *status_reg = &uart->SR;Later:
if (uart->SR & UART_RXNE)Same symbol, two completely different meanings.
Common Mistakes
Forgetting &
foo(x);when function expects:
foo(&x);Confusing value vs address
int x = 10;
int *p = &x;x value
&x address
p pointer
*p dereferenced value
Conclusion
The ampersand in C is far more than a symbol. It connects several fundamental layers of the language:
memory through the address-of operator
bit manipulation through bitwise AND
control flow logic through &&
Mastering the differences between these meanings is essential for writing robust C code, especially in areas such as:
embedded systems
firmware
device drivers
low-level protocols
systems programming
The key takeaway is simple:
In C, & is never just a character. Its meaning depends entirely on context.
And learning to interpret that context precisely is what separates someone who uses C from someone who truly masters it.



Commenti