C
Variables, Data Types & Operators
A variable is C's basic unit of storage: a named, fixed-size chunk of memory holding one value of one specific type. This chapter covers how to declare one, what types are actually available, how big each one is, and the operators you use to combine and compare them — the vocabulary every single line of C code after this chapter depends on.
- What it is
- Fixed-size, typed storage locations, plus the operators (arithmetic, relational, logical, bitwise) used to compute with them.
- Mental model
- A variable is a labeled box of a fixed size; its type is what's printed on the box telling you how to interpret the bits inside — the same 4 bytes mean something completely different labeled
intvs.float. - Key numbers
char= 1 byte,intcommonly 4 bytes,float= 4 bytes,double= 8 bytes — always platform-dependent;sizeofis the only source of truth.- Reach for it when
- Any time a value needs to be stored or computed — the very first design decision inside any function.
Declaring a variable
Declaring a variable in C always states its type first, then its name, optionally followed by an initial value:
int age = 25; // type: int, name: age, value: 25
char grade = 'B'; // type: char, name: grade, value: 'B'
float price = 9.99f; // type: float, name: price, value: 9.99
double pi = 3.14159265358979; // type: double, name: pi
Once declared, a variable's type never changes — this is what makes C statically typed. You can change the value stored inside the box, but not what kind of box it is.
The basic data types
| Type | Stores | Typical size | Example |
|---|---|---|---|
char | A single character (or small integer) | 1 byte | char c = 'A'; |
int | A whole number | 4 bytes | int count = 42; |
float | A decimal number, single precision | 4 bytes | float f = 3.14f; |
double | A decimal number, double precision | 8 bytes | double d = 3.14159; |
short | A smaller whole number | 2 bytes | short s = 100; |
long | A larger whole number | 8 bytes (platform-dependent) | long l = 100000L; |
Any of the integer types (char, short, int, long) can
be prefixed with unsigned to store only non-negative numbers, doubling the positive range in
exchange for losing negative values entirely — unsigned int instead of int. The
exact bit-level consequences of that trade-off, and the fixed-width types (uint8_t,
int32_t, …) that remove the "typical size" guesswork entirely, are covered properly in
Embedded C's Data Types & Integer Overflow — worth
reading once you're comfortable here, since exact sizes matter far more on a microcontroller than on a
desktop.
sizeof — and why it matters
"Typical size" above is deliberately vague, because C never actually guarantees exact sizes — only
minimums. The one way to know for certain, on your specific compiler and target, is the built-in
sizeof operator:
#include <stdio.h>
int main(void) {
printf("int is %zu bytes\n", sizeof(int));
printf("double is %zu bytes\n", sizeof(double));
return 0;
}
// int is 4 bytes
// double is 8 bytes
This isn't a pedantic detail — code that assumes int is always 4 bytes has genuinely broken
on real, shipped hardware where it wasn't. sizeof is how you stop guessing.
Type conversion & casting
C will sometimes convert one type to another automatically (implicit conversion), and you can always force one explicitly (casting):
int a = 7;
int b = 2;
float result = a / b; // 3.0 — integer division happened BEFORE the assignment
float fixed = (float)a / b; // 3.5 — casting a to float first forces floating-point division
(float)a is an explicit cast: "treat this int as a float from this
point forward." Without it, C evaluates a / b using integer division first — both operands
are integers, so the result is truncated to an integer before it's ever assigned to
result.
Operators
| Category | Operators | Example | Meaning |
|---|---|---|---|
| Arithmetic | + - * / % | a % b | Remainder after dividing a by b |
| Relational | == != > < >= <= | a == b | True if a and b are equal |
| Logical | && || ! | a > 0 && b > 0 | True only if both conditions are true |
| Bitwise | & | ^ ~ << >> | flags & 0x04 | Tests bit 2 of flags directly |
| Assignment | = += -= *= /= | total += 5 | Shorthand for total = total + 5 |
Bitwise operators work on the individual bits of a value rather than its numeric meaning — they look unfamiliar at first, but they're the exact tool used to set, clear, and test individual bits in a hardware register, which is why Embedded C's Registers & volatile chapter leans on them constantly.
flags.Operator precedence
When an expression mixes several operators, C evaluates them in a fixed order — not strictly left to right. The rules that matter most day to day:
*,/,%bind tighter than+,-— exactly like ordinary arithmetic (2 + 3 * 4is14, not20).- Relational (
>,==) bind looser than arithmetic, soa + b > cmeans(a + b) > c. - Logical
&&binds tighter than logical||. - Bitwise
&,^,|bind looser than relational operators — the single most common precedence mistake in real C code (see Gotchas). - When genuinely unsure, add parentheses — they cost nothing at runtime and remove all ambiguity for the next reader, including future you.
Real-world applications
- Sensor reading pipelines — a raw ADC value read as an
intis almost always cast tofloatbefore being scaled into a real-world unit like volts or degrees. - Status/flag registers — a single
uint8_toruint32_tvariable where each individual bit represents one on/off condition, tested and set with bitwise operators. - Loop counters and array indices — almost universally plain
int, the most common variable type written in any C program.
Gotchas
int a = 7 / 2;gives3, not3.5— integer division always truncates toward zero, it never rounds. Getting a fractional result requires at least one operand to already be a floating-point type.=(assignment) and==(equality test) are easy to type wrong for each other —if (x = 5)compiles, assigns5tox, and is always true, which is a famously hard-to-spot bug.- Bitwise
&binding looser than==meansif (flags & 0x04 == 0x04)does not do what it looks like —==binds first, so this actually evaluates asflags & (0x04 == 0x04), i.e.flags & 1. Always parenthesize:if ((flags & 0x04) == 0x04). - Signed/unsigned comparisons can silently convert a negative signed value into a huge unsigned one before comparing — a full treatment of exactly how and why is in Embedded C's Data Types & Integer Overflow.