📚 Chapter 2 🟢 Beginner → Intermediate ⏱ 45–60 min

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.

🎯 Learning Objectives
Select correct data type for any value
Understand memory size and ranges
Use sizeof() for portable code
Explain implicit vs explicit conversion
Use all seven categories of operators
Apply bitwise ops to hardware registers
1Foundations

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.

👤
Student Age
A whole number like 20
💵
Salary
A decimal like 45000.50
🌡
Temperature
A float like 37.5℃
🄯
Grade
A character like ‘A’
🏫
Student Name
A string like “Satwik”
🔌
Register Value
Binary like 0b00001111

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.

💡
Data is Everything
At the hardware level, everything a computer stores is binary — sequences of 0s and 1s. Data types are the agreement between you and the compiler about how to interpret those bits.
2Foundations

What is a Data Type?

A data type tells the compiler three critical things about a value:

1
What kind of value is stored

Is this a whole number? A decimal? A single character? Text?

2
How much memory to allocate

Should the compiler reserve 1 byte, 4 bytes, or 8 bytes?

3
What operations are permitted

Can you multiply two characters? Add a float to an int?

C — Data type in action
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:

char 1 Byte Holds: ‘A’ ‘Z’ ‘9’ 8 bits Single character int 4 Bytes Holds: 10, −20, 5000 32 bits Whole numbers float 4 Bytes Holds: 3.14, −0.5 32 bits Decimal numbers double 8 Bytes Holds: 3.14159265358... 64 bits High precision decimals

Four containers, four sizes — each built for a specific kind of data. double is twice as wide as float, reflecting its 8-byte storage.

3Foundations

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.

Embedded Reality
An Arduino Uno has only 2 KB of SRAM. If you store a single-byte grade as a 4-byte 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?

✗ Wasteful (int for a grade)
int grade = 'A'; /* 4 bytes used */ /* Uses 4× more RAM than needed for 100 grades = 400 bytes wasted */
✓ Efficient (char for a grade)
char grade = 'A'; /* 1 byte used */ /* For 100 grades = only 100 bytes Right tool for the right job */

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.

4Foundations

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).

Data Types in C Built-in (Primitive) provided by the language User-Defined created by programmer char 1 byte int 4 bytes float 4 bytes double 8 bytes struct union enum This chapter covers the built-in (primitive) types in depth. User-defined types are covered in later chapters.

C data types: four primitive built-in types and user-defined composite types. This chapter focuses on the primitive types.

💡
Focus of This Chapter
We focus entirely on the four primary built-in types in Part 1. struct, union, and enum are covered in dedicated later chapters.
5Built-in Data Types

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.

C — Declaring and using char
#include <stdio.h>

int main()
{
    char grade = 'A';

    printf("Grade: %c\n", grade);
    printf("ASCII: %d\n", grade);   /* 'A' = 65 in ASCII */

    return 0;
}
Output
Grade: A ASCII: 65
⚠️
Single Quotes Only
Character literals use single quotes: 'A'. Double quotes create a string: "A" is a different beast (a char array). Writing char ch = "A"; is a type error.
char grade = 'A';
grade
'A'
char • 1 byte • ASCII 65
Memory at addr 0x1000:
0100 0001 (binary for 65)
6Built-in Data Types

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.

C — Integer examples
#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;
}
Output
Age: 21 Marks: 95 Temp: -10 Big: 2000000
💡
Implementation-Defined Size
On most modern 32-bit and 64-bit systems, 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.
7Built-in Data Types

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.

C — float examples
#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;
}
Output
Pi: 3.140000 Temp: 27.5 Price: 99.99
💡
Default printf Precision
By default, printf("%f") always prints 6 decimal places. Use the .N format specifier (e.g., %.2f) to control precision.
8Built-in Data Types

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.

C — double example
#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;
}
Output
Pi (float): 3.1415927 Pi (double): 3.14159265358979
🔨
scanf() and double
When using scanf() to read a double, always use %lf (“long float”), not %f. For printf(), both %f and %lf work for double.
9Built-in Data Types

Type Comparison & Selection Guide

char
Character type
Size1 byte
Range−128 to 127
Format%c / %d
Use forLetters, ASCII
int
Integer type
Size4 bytes
Range±2.1 billion
Format%d
Use forCounts, index
float
Single precision
Size4 bytes
Precision~7 digits
Format%f
Use forDecimals, sensor
double
Double precision
Size8 bytes
Precision~15 digits
Format%lf
Use forScience, finance

Quick Decision Guide

You want to storeBest TypeWhy
A letter: 'A'char1 byte, exact fit
A count or index: 100intStandard integer, 4 bytes
A temperature: 37.5floatDecimal, ~7 digit precision
GPS coordinatesdouble15+ digit precision needed
A bit flag (0 or 1)unsigned charSmallest type, no sign bit wasted
Port register on MCUvolatile uint8_tExact 8-bit, from <stdint.h>
10Modifiers & Memory

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 (8 bits) S sign 7 data bits Range: −128 to 127 unsigned char (8 bits) D data all 8 bits are data Range: 0 to 255 Key insight: unsigned char doubles the positive range (0–255) by repurposing the sign bit as a data bit

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

TypeTypical SizeTypical RangeUse Case
short int2 bytes−32,768 to 32,767Small integers, saves RAM
unsigned short2 bytes0 to 65,535Port numbers, small counters
long int4–8 bytesPlatform-dependentLarge integers
long long int8 bytes±9.2 × 10¹⁹Very large numbers
unsigned int4 bytes0 to 4,294,967,295Counts, sizes, addresses
unsigned char1 byte0 to 2558-bit registers, byte buffers
C — Modified types in code
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 */
11Modifiers & Memory

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.

decimal
65
= ASCII value of ‘A’
binary
0
1
0
0
0
0
0
1
= stored in memory
hex
0x41
= same value, hex notation
💡
The Computer Only Sees Bits
It is the data type that tells the CPU whether 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)

int x = 20; → stored as 4 bytes (little-endian)
byte 0
0
0
0
1
0
1
0
0
= 20 (0x14)
byte 1
0
0
0
0
0
0
0
0
= 0 (padding)
byte 2
0
0
0
0
0
0
0
0
= 0 (padding)
byte 3
0
0
0
0
0
0
0
0
= 0 (padding)
12Modifiers & Memory

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).

C — sizeof all basic types
#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;
}
Typical Output (64-bit Linux / macOS)
char: 1 bytes int: 4 bytes float: 4 bytes double: 8 bytes short int: 2 bytes long int: 8 bytes long long: 8 bytes

Why sizeof() Is Critical in Embedded Systems

✗ Assumes size (not portable)
/* Breaks on systems where int != 4 bytes */ malloc(10 * 4);
✓ Uses sizeof (portable)
/* Works on any system automatically */ malloc(10 * sizeof(int));
sizeof on Variables Too
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).
13Variables & Literals

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.

C — Syntax
datatype variableName;          /* Declaration only   */
datatype variableName = value;  /* Declaration + init  */
C — All four variations
/* 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

TermCodeMeaning
Declarationint age;Creates the variable in memory. Value is undefined (garbage).
Initializationint age = 20;Gives the variable its first value at the point of declaration.
Assignmentage = 25;Changes the value of an existing variable.
Never Use Uninitialized Variables
An uninitialized variable in C contains whatever bytes were previously at that memory address — garbage. Using it causes unpredictable behavior. In embedded systems, this can trigger hardware malfunctions. Always initialize.
14Variables & Literals

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 AddressVariable NameValue
0x1000age20
C — After age = 25;
age = 25;  /* same address, new value */
Memory AddressVariable NameValue
0x1000age25

Notice: the address 0x1000 is the same. Only the value changed.

Updating Variables

C
int age = 20;

age = 21;      /* now age = 21 */
age = 22;      /* now age = 22 */

printf("%d\n", age);   /* prints: 22 */

Variable Naming Best Practices

✗ Bad names (cryptic)
int x; float a1; char q; double zz;
✓ Good names (descriptive)
int studentAge; float examScore; char letterGrade; double preciseTemp;
15Variables & Literals

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.

C — Literals highlighted
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    */
Integer
Literal
Examples10, −25, 0
Decimal100
Octal0144
Hex0x64
Floating
Literal
float3.14f
double3.14
Scientific3.14e2
Character
Literal
Examples'A', 'Z', '9'
Escape'\n', '\t'
QuotesSingle only
String
Literal
Examples"Hello"
Empty""
Terminator\0 (auto)
16ASCII & Conversion

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.

C — ASCII in action
#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;
}
Output
Character: A ASCII value: 65 Letter: B

Essential ASCII Values

A–Z
65 to 90
a–z
97 to 122
0–9
48 to 57
'A'
65
'a'
97
'0'
48
Space
32
'Z'
90
'\n'
10
'\t'
9
💡
Practical Insight
Because characters are integers, you can do arithmetic on them! 'A' + 32 = 'a' (lowercase = uppercase + 32). This is how toupper() and tolower() work internally.
C — Char arithmetic
char upper = 'A';
char lower = upper + 32;     /* 65 + 32 = 97 = 'a' */
printf("%c\n", lower);       /* Prints: a           */
17ASCII & Conversion

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: smaller → larger (safe, automatic) char 1 byte short 2 bytes int 4 bytes float 4 bytes double 8 bytes widest When mixing types, C promotes the narrower type to match the wider — no precision is lost in this direction

Implicit promotion always goes from narrow to wide. Mixing types in an expression promotes the narrower operand.

C — Implicit conversion examples
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 */
⚠️
Narrowing Conversion (Danger Zone)
Assigning a wider type to a narrower one is implicit too, but can lose data: int x = 3.9; silently truncates to 3. The compiler may warn but will not error. Always check your intent.
18ASCII & Conversion

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.

C — Cast syntax
(datatype) expression
C — Common cast use case: integer division fix
#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;
}
Output
Without cast: 3.0000 With cast: 3.3333

Implicit vs Explicit Comparison

🔄 Implicit (Automatic)
float y = 10; /* compiler converts */ /* No cast operator used */ /* Compiler handles silently */
✍ Explicit (Type Cast)
float y = (float)10 / 3; /* Programmer explicitly casts */ /* Forces float division */
AspectImplicit ConversionExplicit Cast
Who decides?CompilerProgrammer
SyntaxNone (automatic)(type)value
Can lose data?Yes (narrowing)Yes (truncation)
Intent visible?NoYes
Best used when?Safe promotionsIntentional conversion
19ASCII & 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.

unsigned char wraps at 255+1:
253
1
1
1
1
1
1
0
1
254
1
1
1
1
1
1
1
0
255
1
1
1
1
1
1
1
1
← maximum
0 !
0
0
0
0
0
0
0
0
← wraps to 0 (overflow!)
🔌
Overflow in Embedded Systems
Embedded timers and counters use overflow intentionally. An 8-bit timer counting 0–255 wraps to 0 and triggers an interrupt — this is the heartbeat of timing in microcontrollers.

Numeric Ranges Reference

TypeSizeMinimumMaximum
char1 byte−128127
unsigned char1 byte0255
short int2 bytes−32,76832,767
unsigned short2 bytes065,535
int4 bytes−2,147,483,6482,147,483,647
unsigned int4 bytes04,294,967,295
long long8 bytes−9.2×10¹⁹9.2×10¹⁹
Use <stdint.h> for Exact Sizes
In embedded systems, use int8_t, uint8_t, int16_t, uint32_t etc. from <stdint.h>. These guarantee exact bit widths regardless of the compiler and platform.
20Operators

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.

result = a + b;
a
10
operand 1
+
b
20
operand 2
=
result
30
operand 3 (destination)

Classification of C Operators

Operators in C 7 major categories Arithmetic + − * / % Relational == != > < Logical && || ! Assignment = += -= Incr/Decr ++ -- Bitwise & | ^ ~ << >> Others ?: sizeof , 21 22 23 24 25 26 27–28

Seven categories of C operators, each covered in detail in the sections that follow

21Operators

Arithmetic Operators

Arithmetic operators perform mathematical calculations on numeric operands.

+
Addition
a + b
10 + 3 = 13
Subtraction
a − b
10 − 3 = 7
*
Multiplication
a * b
5 * 4 = 20
/
Division
a / b
10 / 3 = 3
%
Modulus
a % b
10 % 3 = 1

Integer Division vs Floating Division

C — Division pitfall
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 */
⚠️
Integer Division Truncates
When both operands are integers, division in C discards the fractional part — it does NOT round. 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.

ExpressionResultWhy
10 % 3110 = 3×3 + 1
8 % 208 = 4×2 + 0 (even number)
15 % 4315 = 3×4 + 3
7 % 707 = 1×7 + 0 (divisible)
C — Common modulus uses
/* 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;
22Operators

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+).

==
Equal to
a == b
10==10 → 1
!=
Not equal
a != b
10!=20 → 1
>
Greater than
a > b
10>5 → 1
<
Less than
a < b
5<10 → 1
>=
Greater or equal
a >= b
10>=10 → 1
<=
Less or equal
a <= b
9<=10 → 1
C — Relational operators example
#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;
}
Most Common C Bug: = vs ==
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.
23Operators

Logical Operators

Logical operators combine or negate boolean (true/false) conditions. They are essential for complex decision-making.

&&
Logical AND
a && b
Both must be true
||
Logical OR
a || b
At least one true
!
Logical NOT
!a
Reverses result

Truth Tables

ABA && B
000
010
100
111
ABA || B
000
011
101
111
A!A
01
10
C — Logical operators in practice
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");
24Operators

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.

OperatorExampleEquivalent toResult (if x=10)
=x = 5Assign 5 to xx = 5
+=x += 5x = x + 5x = 15
-=x -= 3x = x - 3x = 7
*=x *= 2x = x * 2x = 20
/=x /= 4x = x / 4x = 2
%=x %= 3x = x % 3x = 1
C — Compound assignments
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 */
25Operators

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).

++x
Pre-increment
Increment first,
then use value
x++
Post-increment
Use value first,
then increment
--x
Pre-decrement
Decrement first,
then use value
x--
Post-decrement
Use value first,
then decrement

Pre vs Post — The Critical Difference

C — Prefix vs Postfix
#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;
}
Output
x++ = 5 x now = 6 ++x = 6 x now = 6
Pre-increment (++x)
Step 1: x = x + 1 Step 2: use x in expression Think: "increment BEFORE use"
Post-increment (x++)
Step 1: use x in expression Step 2: x = x + 1 Think: "increment AFTER use"
26Advanced Operators

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.

🔌
Why Embedded Programmers Love Bitwise Ops
A hardware register is just a collection of bits. Each bit controls a specific hardware function (LED on/off, pin direction, interrupt enable, etc.). Arithmetic operators can’t target individual bits. Bitwise operators can.
OperatorNameEffectExample
&Bitwise AND1 only if both bits are 112 & 10 = 8
|Bitwise OR1 if at least one bit is 112 | 10 = 14
^Bitwise XOR1 if bits are different12 ^ 10 = 6
~Bitwise NOTFlips every bit~0x0F = 0xF0
<<Left ShiftShift bits left (×2)5 << 1 = 10
>>Right ShiftShift bits right (÷2)20 >> 1 = 10

Bitwise AND, OR, XOR Visualized

12 & 10 | 12 ^ 10 (using 4 bits for clarity)
12 =
1
1
0
0
0b1100
10 =
1
0
1
0
0b1010
AND
1
0
0
0
= 8 (both bits must be 1)
OR
1
1
1
0
= 14 (at least one bit is 1)
XOR
0
1
1
0
= 6 (bits differ)

Bit Manipulation Patterns for Embedded Systems

C — Hardware register control (essential embedded patterns)
/* 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

C — Shifts as multiply/divide by 2
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  */
27Advanced Operators

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.

C — Syntax
condition ? expression_if_true : expression_if_false
C — Ternary vs if-else
int 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 */
💡
When to Use Ternary
Use ?: for simple, single-line conditionals where both branches are values. For complex logic with side effects or multiple statements, prefer if  else for readability.
28Advanced Operators

sizeof (Advanced) & Comma Operator

sizeof with Variables and Arrays

C — sizeof advanced usage
#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.

C — Comma operator
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);
}
29Expressions

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.

💡
Just Like Math
In mathematics, multiplication before addition is a rule you already know. C formalizes this with a complete precedence table covering all operators.
C — Precedence in action
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

ExpressionStep 1Step 2Result
5 + 3 * 23×2 = 65+611
(5+3) * 25+3 = 88×216
10 / 2 + 310÷2 = 55+38
10 > 5 && 3 < 710>5=1, 3<7=11&&11 (true)
Best Practice: Use Parentheses Liberally
Even when you know the precedence rules, parentheses make your intent explicit and your code self-documenting. Write (a + b) * c instead of hoping readers remember the rules.
30Expressions

Associativity & Complete Precedence Table

When two operators have the same precedence, associativity determines the direction of evaluation — left-to-right or right-to-left.

C — Associativity examples
/* 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

OperatorsAssociativity
1( ) [ ] -> .Left → Right
2++ -- (prefix) ! ~ + - (unary) (type) sizeofRight → 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
() → Unary → * / % → + - → Shifts → Compare → Logic → Assignment

The key groups to memorize. Parentheses always win; assignment always loses.

31Expressions

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.

&& Short-circuit
if(A && B) If A is FALSE → B is NEVER evaluated. Result must be false anyway. Safe divide-by-zero guard:
|| Short-circuit
if(A || B) If A is TRUE → B is NEVER evaluated. Result must be true anyway. Use to skip expensive checks:
C — Short-circuit prevents division by zero
#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;
}
Output
x is zero — avoided division by zero!
⚠️
Don’t Rely on Side Effects in Short-Circuits
Never write code where a function call in the right operand has necessary side effects that must execute. If the left operand short-circuits the expression, the function won’t run: if(ready && send_data()) — if ready is false, send_data() never executes.
32Review

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 ==

✗ Assignment in a condition
if(a = 5) /* assigns 5; always true */ printf("yes");
✓ Comparison
if(a == 5) /* tests equality */ printf("yes");

Mistake 2 — Expecting float result from integer division

✗ Result is 2.000000, not 2.5
int a = 5, b = 2; float c = a / b; /* integer division first */ printf("%f", c); /* 2.000000 */
✓ Cast first, divide second
int a = 5, b = 2; float c = (float)a / b; printf("%f", c); /* 2.500000 */

Mistake 3 — Wrong quotes for character literals

✗ Double quotes = string pointer
char ch = "A"; /* compiler error */
✓ Single quotes = char literal
char ch = 'A'; /* correct */

Mistake 4 — Using % with floating-point numbers

✗ % only works with integers
float x = 5.5 % 2; /* compiler error */
✓ Use fmod() for floats
#include <math.h> double r = fmod(5.5, 2.0); /* 1.5 */

Mistake 5 — Confusing & with && and | with ||

OperatorCategoryOperates onUse case
&Bitwise ANDIndividual bitsHardware register masking
&&Logical ANDTrue/false valuesCombining conditions in if
|Bitwise ORIndividual bitsSetting bits in a register
||Logical ORTrue/false valuesAlternative conditions in if

Mistake 6 — Using uninitialized variables

✗ Garbage value — undefined behaviour
int x; printf("%d", x); /* unpredictable output */
✓ Always initialise
int x = 0; printf("%d", x); /* prints 0 */

Mistake 7 — Assuming int is always 4 bytes

✗ Hard-coded size breaks portability
malloc(10 * 4); /* breaks if int is 2 bytes */
✓ sizeof makes it portable
malloc(10 * sizeof(int));
33Review

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.

34Review

Interview Questions

Q1
What is a data type and why do data types exist in C?
A data type tells the compiler three things: what kind of value is stored, how much memory to allocate, and what operations are permitted. Data types exist because different values require different amounts of memory and different hardware operations. Without them, the compiler would not know whether a sequence of bytes should be interpreted as an integer, a decimal, a character, or a hardware address.
Q2
What is the difference between implicit type conversion and type casting?
Implicit conversion is performed automatically by the compiler when types are mixed (e.g., assigning 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.
Q3
What is integer overflow? Why does it matter in embedded systems?
Integer overflow occurs when a computation produces a value outside the representable range of the type. For unsigned integers, the value wraps around from the maximum back to zero. In embedded systems, this is used intentionally (hardware timers count 0–255 and wrap). Accidental overflow, however, causes subtle bugs: a safety counter that should stop at 255 silently resets to 0 and keeps running.
Q4
What is the difference between = and ==?
= 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).
Q5
Why does 10/3 return 3 in C, not 3.333?
When both operands of / 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....
Q6
Explain the difference between ++x and x++.
  • ++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.
Q7
What is the difference between & and &&? Between | and ||?
  • & 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.
Q8
What is short-circuit evaluation? Give a practical use case.
When the first operand of && 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

Why use unsigned types on microcontrollers?
Microcontroller hardware registers always hold non-negative values (0–255 for 8-bit registers). Using 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).
When should I use float vs double?
Use 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.
Is there a boolean type in C?
C89/C90 has no native boolean type — logical operators return 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.
35Review

Practice Programs

🟢 Easy
  • Print the size (in bytes) of all four basic data types using sizeof().
  • Declare a char variable, 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; stores 3 (truncation, not rounding).
  • Use the modulus operator to check whether a number entered by the user is even or odd.
🔵 Medium
  • 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 ++x in 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 char variable. 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.
🔴 Challenge
  • 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.
36Review

Chapter Summary

✅ What you mastered in Chapter 2
  • 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, unsigned adjust 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_t etc.) in embedded systems for exact bit widths.
C I F D
Cchar
Iint
Ffloat
Ddouble

“Cute Intelligent Fish Dive” — Remember the four basic types

📚 Chapter 3 — Control Statements
Building decision-making and repetition into your programs
if / if  else
else if Ladder
Nested if
switch  case
while Loop
do  while
for Loop
Nested Loops
break & continue
goto
Flowcharts
Interview Questions