Data Types, Variables
& Operators
From raw bytes in memory to the full algebra of C expressions — everything the compiler needs to know about your data and how to manipulate it.
Introduction & What is Data?
Programs exist to solve problems by processing data. Every number you calculate, every name you look up, every sensor reading you sample — all of it is data. Before you can write a useful program, you must understand how data is represented and stored in memory.
These different kinds of data are stored and processed differently. An age needs 4 bytes; a single grade needs only 1. A string needs a whole array. This is precisely why programming languages provide data types — to give the compiler all the information it needs to handle each value correctly.
What is a Data Type?
A data type tells the compiler three critical things about a value:
What kind of value is stored
Is this a whole number? A decimal? A single character? Text?
How much memory to allocate
Should the compiler reserve 1 byte, 4 bytes, or 8 bytes?
What operations are permitted
Can you multiply two characters? Add a float to an int?
int age = 20;When the compiler reads int, it knows: “Reserve 4 bytes of memory, treat the contents as a signed whole number, and allow arithmetic operations.”
The Container Analogy
Think of data types as different containers. Each is designed to hold a specific kind of content:
Four containers, four sizes — each built for a specific kind of data. double is twice as wide as float, reflecting its 8-byte storage.
Why Data Types Exist
The most important reason for data types in C is memory efficiency. This is especially critical in embedded systems, where a microcontroller may have as little as 2 KB of RAM.
int instead of a 1-byte char, you waste 3 bytes per student. For 100 students, that’s 300 wasted bytes — 15% of total RAM gone.Why Not Just Use int for Everything?
Data types also prevent invalid operations. The compiler can catch type mismatches at compile time before they cause hardware bugs at runtime in your embedded system.
Classification of Data Types
C data types fall into two broad categories: built-in types (provided by the language) and user-defined types (created by programmers to model complex structures).
C data types: four primitive built-in types and user-defined composite types. This chapter focuses on the primitive types.
struct, union, and enum are covered in dedicated later chapters.char — The Character Type
The char data type stores a single character. Internally, characters are stored as integers using the ASCII encoding standard (covered in §16). A char is the smallest standard data type in C, occupying exactly 1 byte (8 bits) of memory.
#include <stdio.h>
int main()
{
char grade = 'A';
printf("Grade: %c\n", grade);
printf("ASCII: %d\n", grade); /* 'A' = 65 in ASCII */
return 0;
}'A'. Double quotes create a string: "A" is a different beast (a char array). Writing char ch = "A"; is a type error.0100 0001 (binary for 65)
int — The Integer Type
The int data type stores whole numbers (no decimal point). It is the most commonly used type in C for counting, indexing, and general-purpose integer arithmetic.
#include <stdio.h>
int main()
{
int age = 21;
int marks = 95;
int temp = -10; /* Can be negative */
int big = 2000000;
printf("Age: %d\n", age);
printf("Marks: %d\n", marks);
printf("Temp: %d\n", temp);
printf("Big: %d\n", big);
return 0;
}int is 4 bytes (32 bits). However, the C standard only guarantees it is at least 16 bits. Always use sizeof(int) to verify, or use int32_t from <stdint.h> when you need exactly 32 bits.float — Single-Precision Decimal
The float type stores decimal (real) numbers using single-precision floating-point format. It uses 4 bytes and provides about 6–7 significant decimal digits of precision.
#include <stdio.h>
int main()
{
float pi = 3.14f; /* 'f' suffix marks float literal */
float temp = 27.5f;
float price = 99.99f;
printf("Pi: %f\n", pi);
printf("Temp: %.1f\n", temp); /* .1 = 1 decimal place */
printf("Price: %.2f\n", price); /* .2 = 2 decimal places */
return 0;
}printf("%f") always prints 6 decimal places. Use the .N format specifier (e.g., %.2f) to control precision.double — Double-Precision Decimal
The double type also stores decimal numbers, but with double precision: 8 bytes and about 15–16 significant digits. Use double when precision matters more than memory.
#include <stdio.h>
int main()
{
double precise_pi = 3.14159265358979;
printf("Pi (float): %.7f\n", (float)3.14159265358979f);
printf("Pi (double): %.14f\n", precise_pi);
return 0;
}scanf() to read a double, always use %lf (“long float”), not %f. For printf(), both %f and %lf work for double.Type Comparison & Selection Guide
Quick Decision Guide
| You want to store | Best Type | Why |
|---|---|---|
A letter: 'A' | char | 1 byte, exact fit |
A count or index: 100 | int | Standard integer, 4 bytes |
A temperature: 37.5 | float | Decimal, ~7 digit precision |
| GPS coordinates | double | 15+ digit precision needed |
| A bit flag (0 or 1) | unsigned char | Smallest type, no sign bit wasted |
| Port register on MCU | volatile uint8_t | Exact 8-bit, from <stdint.h> |
Type Modifiers
Type modifiers let you adjust the range, sign, or size of a basic type without changing its fundamental nature. C provides four modifiers: short, long, signed, and unsigned.
The Four Modifiers
short
Reduces the integer size. Commonly 2 bytes. Useful when you know values stay small and RAM is tight (embedded systems).
long
Increases the integer size. On most systems, long int is 4–8 bytes. long long is at least 8 bytes.
signed
Allows both negative and positive values. Uses one bit for the sign. Default for int.
unsigned
Positive values only (zero included). The sign bit becomes a data bit, doubling the positive range.
Signed vs Unsigned Visualized
Signed char uses bit 7 as a sign flag, limiting positive range to 127. Unsigned char uses all 8 bits for data, reaching 255.
Common Modified Types
| Type | Typical Size | Typical Range | Use Case |
|---|---|---|---|
short int | 2 bytes | −32,768 to 32,767 | Small integers, saves RAM |
unsigned short | 2 bytes | 0 to 65,535 | Port numbers, small counters |
long int | 4–8 bytes | Platform-dependent | Large integers |
long long int | 8 bytes | ±9.2 × 10¹⁹ | Very large numbers |
unsigned int | 4 bytes | 0 to 4,294,967,295 | Counts, sizes, addresses |
unsigned char | 1 byte | 0 to 255 | 8-bit registers, byte buffers |
short int small = 120;
long int bignum = 800000000L;
unsigned int counter = 4294967295U;
unsigned char reg_val = 0xFF; /* Hardware register: 0-255 */
signed int balance = -500; /* Bank balance */
unsigned char sensor = 200; /* ADC reading: 0-255 */Memory Representation
Computers store everything in binary. When you write char ch = 'A';, the computer doesn’t store the letter “A” — it stores the number 65 (the ASCII code for A) in binary.
01000001 should be interpreted as the integer 65, the character ‘A’, or something else. The bits themselves carry no inherent meaning.Multi-Byte Representation (int = 4 bytes)
The sizeof Operator
The sizeof operator returns the size in bytes of a data type or variable. It is evaluated at compile time and returns a value of type size_t (printed with %zu).
#include <stdio.h>
int main()
{
printf("char: %zu bytes\n", sizeof(char));
printf("int: %zu bytes\n", sizeof(int));
printf("float: %zu bytes\n", sizeof(float));
printf("double: %zu bytes\n", sizeof(double));
printf("short int: %zu bytes\n", sizeof(short int));
printf("long int: %zu bytes\n", sizeof(long int));
printf("long long: %zu bytes\n", sizeof(long long));
return 0;
}Why sizeof() Is Critical in Embedded Systems
sizeof also works on variables: sizeof(age) returns the same as sizeof(int) if age is an int. On arrays: sizeof(arr) returns total array size (e.g., sizeof(int[10]) = 40 bytes).Variable Declaration & Initialization
You must declare a variable before using it. Declaration tells the compiler to set aside memory and gives that memory a name and type.
datatype variableName; /* Declaration only */
datatype variableName = value; /* Declaration + init *//* 1. Declaration only */
int age;
/* 2. Initialize after declaration */
age = 20;
/* 3. Declaration + initialization in one line */
int score = 95;
/* 4. Multiple variables of the same type */
int a = 10, b = 20, c = 30;Declaration vs Initialization vs Assignment
| Term | Code | Meaning |
|---|---|---|
| Declaration | int age; | Creates the variable in memory. Value is undefined (garbage). |
| Initialization | int age = 20; | Gives the variable its first value at the point of declaration. |
| Assignment | age = 25; | Changes the value of an existing variable. |
Variables in Depth
A variable is a named memory location. The name labels a specific address in RAM. When you change the variable, the old value at that address is overwritten with the new one — the address itself never changes during the variable’s lifetime.
| Memory Address | Variable Name | Value |
|---|---|---|
| 0x1000 | age | 20 |
age = 25; /* same address, new value */| Memory Address | Variable Name | Value |
|---|---|---|
| 0x1000 | age | 25 |
Notice: the address 0x1000 is the same. Only the value changed.
Updating Variables
int age = 20;
age = 21; /* now age = 21 */
age = 22; /* now age = 22 */
printf("%d\n", age); /* prints: 22 */Variable Naming Best Practices
Literals
A literal is a fixed value written directly in the source code. It is not stored in a named variable — it is the value itself embedded in the program text.
int age = 20; /* 20 is an integer literal */
float pi = 3.14f; /* 3.14f is a float literal */
char ch = 'A'; /* 'A' is a character literal */
char s[] = "Hello"; /* "Hello" is a string literal */ASCII Character Set
Computers don’t store characters — they store integers. The ASCII (American Standard Code for Information Interchange) standard maps every printable character to a unique integer value from 0 to 127.
#include <stdio.h>
int main()
{
char ch = 'A';
printf("Character: %c\n", ch); /* Prints: A */
printf("ASCII value: %d\n", ch); /* Prints: 65 */
/* Can also go the other way */
char letter = 66;
printf("Letter: %c\n", letter); /* Prints: B */
return 0;
}Essential ASCII Values
'A' + 32 = 'a' (lowercase = uppercase + 32). This is how toupper() and tolower() work internally.char upper = 'A';
char lower = upper + 32; /* 65 + 32 = 97 = 'a' */
printf("%c\n", lower); /* Prints: a */Type Conversion (Implicit)
When you assign a value of one data type to a variable of a different type, C may automatically convert the value. This is called implicit type conversion (or type coercion) — the compiler handles it silently.
Implicit promotion always goes from narrow to wide. Mixing types in an expression promotes the narrower operand.
int x = 10;
float y = x; /* int promoted to float: 10 → 10.000000 */
char ch = 'A';
int val = ch; /* char promoted to int: 'A' → 65 */
int a = 10;
float b = 2.5f;
float c = a + b; /* a promoted to 10.0f before adding */
/* result: 12.5 */int x = 3.9; silently truncates to 3. The compiler may warn but will not error. Always check your intent.Type Casting (Explicit)
When you want a specific conversion — and don’t want to rely on the compiler’s implicit behavior — you use a cast. Casting tells the compiler exactly how to interpret the value.
(datatype) expression#include <stdio.h>
int main()
{
int a = 10, b = 3;
/* Without cast: integer division */
float result1 = a / b;
printf("Without cast: %.4f\n", result1); /* 3.0000 */
/* With cast: forces floating-point division */
float result2 = (float)a / b;
printf("With cast: %.4f\n", result2); /* 3.3333 */
return 0;
}Implicit vs Explicit Comparison
| Aspect | Implicit Conversion | Explicit Cast |
|---|---|---|
| Who decides? | Compiler | Programmer |
| Syntax | None (automatic) | (type)value |
| Can lose data? | Yes (narrowing) | Yes (truncation) |
| Intent visible? | No | Yes |
| Best used when? | Safe promotions | Intentional conversion |
Integer Overflow & Numeric Ranges
Every integer type has a maximum and minimum value determined by its bit width. When a value exceeds the maximum, it wraps around — this is called integer overflow.
Numeric Ranges Reference
| Type | Size | Minimum | Maximum |
|---|---|---|---|
char | 1 byte | −128 | 127 |
unsigned char | 1 byte | 0 | 255 |
short int | 2 bytes | −32,768 | 32,767 |
unsigned short | 2 bytes | 0 | 65,535 |
int | 4 bytes | −2,147,483,648 | 2,147,483,647 |
unsigned int | 4 bytes | 0 | 4,294,967,295 |
long long | 8 bytes | −9.2×10¹⁹ | 9.2×10¹⁹ |
int8_t, uint8_t, int16_t, uint32_t etc. from <stdint.h>. These guarantee exact bit widths regardless of the compiler and platform.What is an Operator?
An operator is a symbol that instructs the compiler to perform a specific operation on one or more values (called operands). Operators are the verbs of the C language — they make things happen.
Classification of C Operators
Seven categories of C operators, each covered in detail in the sections that follow
Arithmetic Operators
Arithmetic operators perform mathematical calculations on numeric operands.
Integer Division vs Floating Division
int a = 10, b = 3;
printf("%d\n", a / b); /* Integer division: 3 */
printf("%f\n", 10.0 / b); /* Float division: 3.333333 */
printf("%f\n", (float)a / b); /* Cast to float: 3.333333 */10/3 = 3, not 3.33. Cast at least one operand to float when you need the fractional result.Modulus (%) Operator
The modulus operator returns the remainder after integer division. It only works with integer operands.
| Expression | Result | Why |
|---|---|---|
10 % 3 | 1 | 10 = 3×3 + 1 |
8 % 2 | 0 | 8 = 4×2 + 0 (even number) |
15 % 4 | 3 | 15 = 3×4 + 3 |
7 % 7 | 0 | 7 = 1×7 + 0 (divisible) |
/* Check if even */
if(number % 2 == 0)
printf("Even\n");
/* Check if divisible by 5 */
if(number % 5 == 0)
printf("Divisible by 5\n");
/* Wrap a counter: 0,1,2,3,0,1,2,3,... */
counter = (counter + 1) % 4;Relational Operators
Relational (comparison) operators compare two values. The result is always 1 (true) or 0 (false) — an integer in C, since C89/C90 has no dedicated boolean type (though stdbool.h adds bool in C99+).
#include <stdio.h>
int main()
{
int a = 10, b = 20;
printf("%d\n", a == b); /* 0 (false) */
printf("%d\n", a != b); /* 1 (true) */
printf("%d\n", a < b); /* 1 (true) */
printf("%d\n", b >= a); /* 1 (true) */
/* Common use: eligibility check */
int age = 18;
if(age >= 18)
printf("Eligible to vote\n");
return 0;
}if(a = 5) assigns 5 to a (always true, since 5 is non-zero). if(a == 5) compares a to 5. The single-equals version is legal C but almost certainly a logic bug. Some compilers warn; always double-check.Logical Operators
Logical operators combine or negate boolean (true/false) conditions. They are essential for complex decision-making.
Truth Tables
| A | B | A && B |
|---|---|---|
| 0 | 0 | 0 |
| 0 | 1 | 0 |
| 1 | 0 | 0 |
| 1 | 1 | 1 |
| A | B | A || B |
|---|---|---|
| 0 | 0 | 0 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 | 1 |
| A | !A |
|---|---|
| 0 | 1 |
| 1 | 0 |
int age = 25, income = 50000;
/* AND: both conditions must hold */
if(age >= 18 && income > 30000)
printf("Eligible for loan\n");
/* OR: weekend detection */
int day = 6; /* 0=Mon ... 5=Sat, 6=Sun */
if(day == 5 || day == 6)
printf("Weekend!\n");
/* NOT: toggle logic */
int motor_on = 0;
if(!motor_on)
printf("Motor is OFF\n");Assignment Operators
Assignment operators store values into variables. The basic assignment = copies the right-hand value into the left-hand variable. Compound assignment operators combine an arithmetic operation with assignment in a single step.
| Operator | Example | Equivalent to | Result (if x=10) |
|---|---|---|---|
= | x = 5 | Assign 5 to x | x = 5 |
+= | x += 5 | x = x + 5 | x = 15 |
-= | x -= 3 | x = x - 3 | x = 7 |
*= | x *= 2 | x = x * 2 | x = 20 |
/= | x /= 4 | x = x / 4 | x = 2 |
%= | x %= 3 | x = x % 3 | x = 1 |
int score = 100;
score += 10; /* score = 110 */
score -= 5; /* score = 105 */
score *= 2; /* score = 210 */
score /= 3; /* score = 70 */
score %= 30; /* score = 10 */
printf("Final score: %d\n", score); /* 10 */Increment & Decrement Operators
These unary operators add or subtract 1 from a variable. They come in two forms: prefix (operate first, then use) and postfix (use first, then operate).
then use value
then increment
then use value
then decrement
Pre vs Post — The Critical Difference
#include <stdio.h>
int main()
{
int x = 5;
/* Post-increment: print THEN increment */
printf("x++ = %d\n", x++); /* Prints 5, then x becomes 6 */
printf("x now = %d\n", x); /* Prints 6 */
x = 5; /* reset */
/* Pre-increment: increment THEN print */
printf("++x = %d\n", ++x); /* x becomes 6, then prints 6 */
printf("x now = %d\n", x); /* Prints 6 */
return 0;
}Bitwise Operators
Bitwise operators work directly on the binary representation of integers, manipulating individual bits. They are the most powerful tool in embedded systems programming for hardware register control.
| Operator | Name | Effect | Example |
|---|---|---|---|
& | Bitwise AND | 1 only if both bits are 1 | 12 & 10 = 8 |
| | Bitwise OR | 1 if at least one bit is 1 | 12 | 10 = 14 |
^ | Bitwise XOR | 1 if bits are different | 12 ^ 10 = 6 |
~ | Bitwise NOT | Flips every bit | ~0x0F = 0xF0 |
<< | Left Shift | Shift bits left (×2) | 5 << 1 = 10 |
>> | Right Shift | Shift bits right (÷2) | 20 >> 1 = 10 |
Bitwise AND, OR, XOR Visualized
Bit Manipulation Patterns for Embedded Systems
/* Assume a GPIO port register for an LED on bit 5 */
unsigned char reg = 0b00000000;
/* Set bit 5 (turn LED ON) — use OR with a mask */
reg |= (1 << 5); /* reg = 0b00100000 = 0x20 */
/* Clear bit 5 (turn LED OFF) — AND with inverted mask */
reg &= ~(1 << 5); /* reg = 0b00000000 = 0x00 */
/* Toggle bit 5 — XOR with mask */
reg ^= (1 << 5); /* 0 → 1, 1 → 0 each call */
/* Check if bit 5 is set */
if(reg & (1 << 5))
{
/* Bit 5 is 1: LED is on */
}
/* Read only bits 3:0 (mask upper nibble) */
unsigned char lower = reg & 0x0F;Left and Right Shift
unsigned int x = 5; /* binary: 0101 */
unsigned int left = x << 1; /* 1010 = 10 (×2) */
unsigned int right = x >> 1; /* 0010 = 2 (÷2) */
printf("%u\n", left); /* 10 */
printf("%u\n", right); /* 2 */Conditional Operator (?:)
The conditional operator (also called the ternary operator) is a concise way to write a simple if else expression. It evaluates a condition and returns one of two values.
condition ? expression_if_true : expression_if_falseint a = 10, b = 20;
/* Using if-else */
int max;
if(a > b)
max = a;
else
max = b;
printf("Max: %d\n", max); /* 20 */
/* Same logic with ternary (one line!) */
int max2 = (a > b) ? a : b;
printf("Max2: %d\n", max2); /* 20 */?: for simple, single-line conditionals where both branches are values. For complex logic with side effects or multiple statements, prefer if else for readability.sizeof (Advanced) & Comma Operator
sizeof with Variables and Arrays
#include <stdio.h>
int main()
{
int age = 20;
int arr[10];
double scores[5];
/* sizeof on a variable */
printf("sizeof(age): %zu\n", sizeof(age)); /* 4 */
/* sizeof on an array: returns total bytes */
printf("sizeof(arr): %zu\n", sizeof(arr)); /* 40 */
printf("sizeof(scores): %zu\n", sizeof(scores)); /* 40 */
/* Number of elements in an array */
int n = sizeof(arr) / sizeof(arr[0]);
printf("Array elements: %d\n", n); /* 10 */
return 0;
}The Comma Operator
The comma operator evaluates its left expression, discards the result, then evaluates and returns its right expression. Most commonly seen in for loop initializations.
int x = (2, 4, 6); /* x = 6 (rightmost value) */
int a, b;
a = 10;
b = (a++, a + 5); /* a becomes 11, b = 11+5 = 16 */
/* Most common use: for loops with multiple init variables */
for(int i = 0, j = 10; i < 5; i++, j--)
{
printf("i=%d j=%d\n", i, j);
}Operator Precedence
When an expression contains multiple operators, precedence determines which operation is performed first. Operators with higher precedence bind more tightly to their operands.
int result;
result = 2 + 3 * 4; /* 3*4=12 first, then 2+12=14 */
printf("%d\n", result); /* 14 — NOT 20 */
result = (2 + 3) * 4; /* Parens first: 5*4=20 */
printf("%d\n", result); /* 20 */
result = 10 + 2 * 5; /* 2*5=10 first, then 10+10=20 */
printf("%d\n", result); /* 20 */Step-by-Step Expression Evaluation
| Expression | Step 1 | Step 2 | Result |
|---|---|---|---|
5 + 3 * 2 | 3×2 = 6 | 5+6 | 11 |
(5+3) * 2 | 5+3 = 8 | 8×2 | 16 |
10 / 2 + 3 | 10÷2 = 5 | 5+3 | 8 |
10 > 5 && 3 < 7 | 10>5=1, 3<7=1 | 1&&1 | 1 (true) |
(a + b) * c instead of hoping readers remember the rules.Associativity & Complete Precedence Table
When two operators have the same precedence, associativity determines the direction of evaluation — left-to-right or right-to-left.
/* Left-to-right: *, / have same precedence */
20 / 5 * 2 /* (20/5)*2 = 4*2 = 8 (NOT 20/10) */
/* Right-to-left: assignment */
int a, b, c;
a = b = c = 10; /* c=10 first, then b=10, then a=10 */
/* All three get value 10 */Complete Precedence & Associativity Table
| ↑ | Operators | Associativity |
|---|---|---|
| 1 | ( ) [ ] -> . | Left → Right |
| 2 | ++ -- (prefix) ! ~ + - (unary) (type) sizeof | Right → Left |
| 3 | * / % | Left → Right |
| 4 | + - | Left → Right |
| 5 | << >> | Left → Right |
| 6 | < <= > >= | Left → Right |
| 7 | == != | Left → Right |
| 8 | & (bitwise AND) | Left → Right |
| 9 | ^ (XOR) | Left → Right |
| 10 | | (bitwise OR) | Left → Right |
| 11 | && | Left → Right |
| 12 | || | Left → Right |
| 13 | ?: | Right → Left |
| 14 | = += -= *= /= %= &= |= ^= | Right → Left |
| 15 | , | Left → Right |
The key groups to memorize. Parentheses always win; assignment always loses.
Short-Circuit Evaluation
Logical operators && and || use short-circuit evaluation — they stop evaluating as soon as the final result is determined. This is not just an optimization; it is a language-guaranteed behavior you can rely on.
#include <stdio.h>
int main()
{
int x = 0;
int y = 10;
/* Safe: if x==0, second condition is never evaluated */
if(x != 0 && y / x > 2)
{
printf("Greater than 2\n");
}
else
{
printf("x is zero — avoided division by zero!\n");
}
return 0;
}if(ready && send_data()) — if ready is false, send_data() never executes.Common Mistakes
These are the errors that trip up nearly every C beginner. Recognising them early will save hours of debugging.
Mistake 1 — Using = instead of ==
Mistake 2 — Expecting float result from integer division
Mistake 3 — Wrong quotes for character literals
Mistake 4 — Using % with floating-point numbers
Mistake 5 — Confusing & with && and | with ||
| Operator | Category | Operates on | Use case |
|---|---|---|---|
& | Bitwise AND | Individual bits | Hardware register masking |
&& | Logical AND | True/false values | Combining conditions in if |
| | Bitwise OR | Individual bits | Setting bits in a register |
|| | Logical OR | True/false values | Alternative conditions in if |
Mistake 6 — Using uninitialized variables
Mistake 7 — Assuming int is always 4 bytes
Best Practices
Always initialise variables
Give every variable a sensible starting value at the point of declaration. In embedded systems, garbage values cause hardware bugs that are extremely hard to trace.
Use sizeof, not magic numbers
Write sizeof(int) rather than 4. Your code will compile and run correctly on 16-bit microcontrollers, 32-bit ARM, and 64-bit servers without changes.
Choose the right type
A GPIO pin state needs a uint8_t, not a double. A salary needs a float or double, not an int. Matching type to data saves memory and prevents bugs.
Use meaningful names
studentAge is instantly understood. x requires a comment. Good names are free documentation that can never go out of date.
Cast explicitly when needed
When you intend a conversion, write the cast. (float)a / b makes your intent visible to the compiler, to static analysers, and to future readers.
Use parentheses for clarity
Don’t make readers memorise the full precedence table. (a + b) * c is unambiguous. Precedence rules exist as a fallback, not as a writing style.
Use <stdint.h> in embedded code
Prefer uint8_t, uint16_t, int32_t etc. over int and long when exact bit widths matter — which in hardware control they almost always do.
Never write = inside if unintentionally
If you mean to compare, use ==. Some teams adopt the “Yoda condition” style if(5 == x) to make accidental = a compile error.
Interview Questions
int to float). Type casting is an explicit instruction from the programmer using the (type) operator. Implicit conversion is silent and can hide bugs; explicit casting documents intent and is the preferred approach when conversion is intentional.= is the assignment operator — it stores a value into a variable. == is the equality comparison operator — it tests whether two values are equal and returns 1 or 0. Writing if(a = 5) instead of if(a == 5) is one of the most common C bugs: it assigns 5 to a and the condition is always true (5 is non-zero)./ are integers, C performs integer division, which discards the fractional part (truncates toward zero). To get a decimal result, at least one operand must be a floating-point value: 10.0/3 or (float)10/3 both yield 3.333....++x(prefix): increments first, then the new value is used in the expression.x++(postfix): the current value is used in the expression, then the variable is incremented.- When used as a standalone statement (
x++;), both are identical — the difference only matters when used as part of a larger expression.
&is bitwise AND — it operates on individual bits of integers. Used for hardware register masking.&&is logical AND — it compares boolean conditions and short-circuits.|is bitwise OR — sets bits in a register.||is logical OR — combines conditions and short-circuits.
&& is false, the second is never evaluated (result is already false). When the first operand of || is true, the second is never evaluated (result is already true). Practical use: if(x != 0 && y / x > 2) — if x is zero, the division never executes, preventing a divide-by-zero crash.Frequently Asked Questions
unsigned char or uint8_t instead of int (a) matches the hardware semantics, (b) avoids the overhead of sign-extension on 8-bit CPUs, and (c) makes overflow behaviour well-defined (unsigned overflow wraps; signed overflow is undefined in C).float when memory or speed matters and ~7 digits of precision is enough (sensor readings, graphics, embedded audio). Use double for scientific calculations, financial computations, or when rounding errors would compound over many iterations. On most embedded MCUs without an FPU, both are emulated in software, so double costs exactly twice as much RAM and often more CPU cycles than float.int (0 or 1). C99 added _Bool and the header <stdbool.h> which provides bool, true, and false as convenient aliases. For portable embedded code, include <stdbool.h> and write bool flag = true; freely.Practice Programs
- Print the size (in bytes) of all four basic data types using
sizeof(). - Declare a
charvariable, assign a letter, then print both the character and its ASCII integer value. - Write a program that takes two integers and prints their sum, difference, product, quotient, and remainder.
- Demonstrate that
int x = 3.9;stores3(truncation, not rounding). - Use the modulus operator to check whether a number entered by the user is even or odd.
- Write a program that computes the average of three float marks and prints it to two decimal places.
- Show the difference between pre-increment and post-increment: print the value of
x++and++xin separate statements and observe the outputs. - Write a program that converts a float temperature in Celsius to Fahrenheit. Use explicit casting to confirm the formula works with integers too.
- Using only bitwise operators, set bit 3, clear bit 1, and toggle bit 5 of an
unsigned charvariable. Print the result after each operation in binary (use a loop). - Use the ternary operator to print the maximum of three integers in a single expression.
- Write a program that simulates an 8-bit timer. Start at 0, increment using
unsigned char, and print every 10 values. The counter should wrap from 255 to 0 automatically. Count exactly three full cycles. - Predict the output of
printf("%d", 5++ + ++5);before running it. Then explain why this expression has undefined behaviour in C (hint: sequence points) and why you should never write code like this. - Create a simple bit-field manipulation program: given an 8-bit register value read from the user, print which bits are set (1) and which are clear (0), from bit 7 down to bit 0.
Chapter Summary
- Data types tell the compiler what kind of value is stored, how much memory to allocate, and what operations are permitted.
- The four primitive types are
char(1B),int(4B),float(4B),double(8B). - Modifiers
short,long,signed,unsignedadjust range and size. - Internally, every value is stored as binary bits. ASCII maps characters to integers.
- Always use
sizeof()instead of hard-coded sizes to write portable code. - Variables are named memory locations. Always initialise them to avoid garbage values.
- Literals are fixed values written directly in source code: integer, float, char, and string.
- Implicit type conversion is automatic (narrower → wider); type casting is explicit with
(type). - Integer overflow wraps unsigned values; it is undefined behaviour for signed types.
- Seven categories of operators: Arithmetic, Relational, Logical, Assignment, Increment/Decrement, Bitwise, Conditional and Others.
- Integer division truncates; cast at least one operand to get a decimal result.
- Bitwise operators (
& | ^ ~ << >>) manipulate individual bits and are essential for embedded hardware control. - Operator precedence determines evaluation order; parentheses always override it.
- Short-circuit evaluation:
&&stops on first false;||stops on first true. - Use
<stdint.h>types (uint8_t,int32_tetc.) in embedded systems for exact bit widths.
“Cute Intelligent Fish Dive” — Remember the four basic types