Operators
SafeC supports the full set of C operators plus SafeC-specific extensions for overflow control. This page is a complete reference for all operators, their precedence, and associativity.
Arithmetic Operators
Binary Arithmetic
| Operator | Description | Example |
|---|---|---|
+ |
Addition | a + b |
- |
Subtraction | a - b |
* |
Multiplication | a * b |
/ |
Division | a / b |
% |
Modulo (remainder) | a % b |
Unary Arithmetic
| Operator | Description | Example |
|---|---|---|
+x |
Unary plus (identity) | +a |
-x |
Unary negation | -a |
Increment and Decrement
| Operator | Description | Example |
|---|---|---|
++x |
Pre-increment (increment, then return) | ++counter |
x++ |
Post-increment (return, then increment) | counter++ |
--x |
Pre-decrement (decrement, then return) | --index |
x-- |
Post-decrement (return, then decrement) | index-- |
int x = 5;
int a = ++x; // x is 6, a is 6
int b = x++; // b is 6, x is 7
int c = --x; // x is 6, c is 6
int d = x--; // d is 6, x is 5
Overflow-Aware Arithmetic
SafeC adds operator variants for explicit overflow control. See Overflow Operators for detailed usage.
| Operator | Description | Behavior |
|---|---|---|
+| |
Wrapping addition | Two's complement wrap |
-| |
Wrapping subtraction | Two's complement wrap |
*| |
Wrapping multiplication | Two's complement wrap |
+% |
Saturating addition | Clamp to min/max |
-% |
Saturating subtraction | Clamp to min/max |
*% |
Saturating multiplication | Clamp to min/max |
Assignment Operators
Simple Assignment
int x = 42;
x = 100;
Compound Assignment
Compound assignment operators combine an arithmetic or bitwise operation with assignment. a op= b is equivalent to a = a op b.
| Operator | Description | Equivalent |
|---|---|---|
+= |
Add and assign | a = a + b |
-= |
Subtract and assign | a = a - b |
*= |
Multiply and assign | a = a * b |
/= |
Divide and assign | a = a / b |
%= |
Modulo and assign | a = a % b |
&= |
Bitwise AND and assign | a = a & b |
|= |
Bitwise OR and assign | a = a | b |
^= |
Bitwise XOR and assign | a = a ^ b |
<<= |
Left shift and assign | a = a << b |
>>= |
Right shift and assign | a = a >> b |
int x = 10;
x += 5; // x is 15
x -= 3; // x is 12
x *= 2; // x is 24
x /= 4; // x is 6
x %= 5; // x is 1
uint32_t flags = 0xFF00;
flags &= 0x0F0F; // flags is 0x0F00
flags |= 0x00F0; // flags is 0x0FF0
flags ^= 0x0FF0; // flags is 0x0000
Comparison Operators
| Operator | Description | Example |
|---|---|---|
== |
Equal | a == b |
!= |
Not equal | a != b |
< |
Less than | a < b |
> |
Greater than | a > b |
<= |
Less than or equal | a <= b |
>= |
Greater than or equal | a >= b |
All comparison operators return bool.
Logical Operators
| Operator | Description | Example |
|---|---|---|
&& |
Logical AND (short-circuit) | a && b |
|| |
Logical OR (short-circuit) | a || b |
! |
Logical NOT | !a |
Short-circuit evaluation: && does not evaluate the right operand if the left is false; || does not evaluate the right operand if the left is true.
// Short-circuiting affects *evaluation order*, not the unsafe-dereference
// rule: *ptr still needs 'unsafe' even though && guarantees it only runs
// when ptr is non-null — the compiler doesn't do flow-sensitive narrowing.
unsafe {
if (ptr != (int*)0 && *ptr > 0) {
// *ptr is only evaluated when ptr is non-null, thanks to &&
}
}
Bitwise Operators
| Operator | Description | Example |
|---|---|---|
& |
Bitwise AND | a & b |
| |
Bitwise OR | a | b |
^ |
Bitwise XOR | a ^ b |
~ |
Bitwise NOT (complement) | ~a |
<< |
Left shift | a << n |
>> |
Right shift | a >> n |
uint32_t flags = 0b1100; // binary literal (see Literals & Qualifiers); = 0xC
uint32_t mask = 0b1010; // = 0xA
uint32_t and_result = flags & mask; // 0x8 (0b1000)
uint32_t or_result = flags | mask; // 0xE (0b1110)
uint32_t xor_result = flags ^ mask; // 0x6 (0b0110)
uint32_t not_result = ~flags; // all bits flipped
uint32_t shifted = 1 << 4; // 16 (bit 4 set)
Bit-Manipulation Built-ins
The compiler also knows GCC/Clang's bit built-ins, each one instruction on most targets:
| Built-in | Result |
|---|---|
__builtin_popcount(x) / __builtin_popcountll(x) |
number of set bits (int) |
__builtin_clz(x) / __builtin_clzll(x) |
leading zero bits (int; undefined for 0) |
__builtin_ctz(x) / __builtin_ctzll(x) |
trailing zero bits (int; undefined for 0) |
__builtin_bswap32(x) / __builtin_bswap64(x) |
x with its bytes reversed (same type as x) |
std/bit.h wraps them in C23 <stdbit.h>-style functions that are defined for 0 too.
Member Access Operators
| Operator | Description | Example |
|---|---|---|
. |
Value member access | point.x |
-> |
Pointer member access | ptr->x |
:: |
Scope resolution / method definition | Point::length() |
The . operator accesses fields on values and references. The -> operator dereferences a pointer and accesses a field in one step.
struct Point { double x; double y; };
struct Point p = {3.0, 4.0};
double x1 = p.x; // value access
// Getting a raw pointer and dereferencing it through -> both require
// 'unsafe' — there's no implicit '&stack Point' -> 'Point*' conversion.
struct Point *ptr;
unsafe { ptr = (struct Point*)&p; }
double x2;
unsafe { x2 = ptr->x; } // pointer dereference + access
// equivalent to: (*ptr).x
The :: operator is used for method definitions and static scope resolution:
double Point::length() const {
return sqrt_d(self.x * self.x + self.y * self.y);
}
Subscript Operator
Array and slice indexing uses []. Accesses are bounds-checked in safe contexts.
int arr[5] = {10, 20, 30, 40, 50};
int x = arr[2]; // 30 (bounds-checked)
[]int s = arr[1..4];
int y = s[0]; // 20 (bounds-checked via slice length)
Ternary Operator
The conditional (ternary) operator evaluates one of two expressions based on a condition:
condition ? then_expr : else_expr
int x = 10;
int y = 20;
int max = (x > y) ? x : y; // max is 20
const &char label = (count == 1) ? "item" : "items";
The ternary operator has very low precedence — use parentheses around complex conditions for clarity.
Comma Operator
Works like C: (e1, e2, ..., eN) evaluates every operand left to right
for its side effects, and the whole expression's type and value are the
last operand's — the earlier operands' values are discarded.
int a; int b;
int x = (a = 1, b = 2, a + b); // x is 3
int i = 0;
int j = 10;
for (; i < j; i++, j--) {
// i counts up, j counts down
}
C-style comma-separated multi-variable declarations also work, including
as a for loop's init clause:
for (int i = 0, j = 10; i < j; i++, j--) {
// i counts up, j counts down
}
int a, b = 5, c; // a and c uninitialized, b == 5
Address-of and Dereference
| Operator | Description | Example |
|---|---|---|
& |
Address-of (creates a region-qualified reference) | &x |
* |
Dereference (access value through pointer/reference) | *ptr |
int x = 42;
&stack int ref = &x; // region-qualified reference
int y = *ref; // dereference: y is 42
See Memory & Regions for region-qualified reference details.
Type-Related Operators
| Operator | Description | Example |
|---|---|---|
(T)expr |
Explicit cast | (double)x |
sizeof(T) |
Size of a type in bytes | sizeof(int) |
sizeof(expr) |
Size of an expression's type in bytes | sizeof(x) |
alignof(T) |
Alignment requirement of a type | alignof(double) |
int x = 42;
double d = (double)x; // explicit cast
long s1 = sizeof(int); // 4
long s2 = sizeof(x); // 4
long a = alignof(double); // 8 (platform-dependent)
See Compile-Time Introspection for typeof, fieldcount, and sizeof....
Operator Precedence
From highest to lowest precedence:
| Precedence | Operators | Associativity |
|---|---|---|
| 1 (highest) | () [] . -> x++ x-- |
Left-to-right |
| 2 | ++x --x +x -x ! ~ * & (T) sizeof alignof |
Right-to-left |
| 3 | * / % *| *% |
Left-to-right |
| 4 | + - +| -| +% -% |
Left-to-right |
| 5 | << >> |
Left-to-right |
| 6 | < <= > >= |
Left-to-right |
| 7 | == != |
Left-to-right |
| 8 | & |
Left-to-right |
| 9 | ^ |
Left-to-right |
| 10 | | |
Left-to-right |
| 11 | && |
Left-to-right |
| 12 | || |
Left-to-right |
| 13 | ? : |
Right-to-left |
| 14 | = += -= *= /= %= &= ^= |= <<= >>= |
Right-to-left |
| 15 (lowest) | , |
Left-to-right |
Operator Overloading
Struct types can overload binary operators by defining methods named operator+, operator-, etc. See Functions for details.
Supported overloadable operators: +, -, *, /, %, ==, !=, <, >, <=, >=.