Unit 1: Introduction to C Programming Language and Basics [5 Hours]
This unit lays the foundation for everything else in C. It covers how programming languages evolved, why C still matters, how a C program is structured and turned into a running executable, and the basic building blocks (tokens, variables, data types, type casting, and operators) that every later program depends on. Expect direct theory questions on structure, compilation stages, storage classes, and operator precedence, plus short-answer questions on tokens and data types.
1.1 Evolution of Programming Languages
Core Definition
The evolution of programming languages refers to the historical progression of languages from raw binary machine instructions to high-level, human-readable languages, developed to make programming faster, easier, and more portable across machines.
Computers only understand 0s and 1s. Writing programs directly in 0s and 1s was painfully slow and error-prone, so over decades programmers built layers of easier-to-use languages on top of machine code, each generation moving further away from hardware and closer to human thinking.
Detailed Explanation
Programming languages are grouped into five generations, each solving a limitation of the one before it.
First Generation Language (1GL) – Machine Language: raw binary (0s and 1s) understood directly by the CPU. Fast to execute but extremely hard to write, debug, and impossible to reuse across different processor types.
Second Generation Language (2GL) – Assembly Language: replaces binary with mnemonic codes (e.g., MOV, ADD). An assembler converts it to machine code. Easier than 1GL but still hardware-specific.
Third Generation Language (3GL) – High-Level Languages: languages like C, C++, and Java use English-like syntax and are portable across machines through compilers. C belongs to this generation.
Fourth Generation Language (4GL): closer to natural language, focused on what result is needed rather than how to get it. SQL is the classic example: you state the data you want, not the retrieval steps.
Fifth Generation Language (5GL): uses constraints, logic, and AI-based reasoning so the system decides the steps itself. Prolog and AI-driven languages fall here.
Moving up generations increases readability and developer speed but does not automatically increase execution speed. Low-level languages remain faster for hardware-critical tasks like operating system kernels or device drivers.
Let's Break It Down
Think of giving directions to a friend. 1GL is drawing the exact muscle movements needed to walk there. 2GL is writing short coded instructions like "L2, R1, R3." 3GL is simply saying "turn left at the temple, then right." 4GL is just saying "take me to the temple" and letting GPS figure out the route. 5GL is the GPS predicting where you want to go before you even ask.
Common Mistake
Students assume a higher generation language always runs faster than a lower one. In reality, higher generations trade raw execution speed for programmer productivity and portability; assembly and machine code remain faster for time-critical, hardware-level operations.
Test Yourself
- Why is assembly language considered easier than machine language but still hardware-dependent?
- Give one reason SQL is classified as a 4GL rather than a 3GL.
Answers: 1) Assembly uses mnemonic codes instead of binary, but each mnemonic set is specific to a particular processor's instruction set.
2) SQL describes the desired result (what data to fetch) instead of step-by-step retrieval logic (how to fetch it), which is the defining trait of 4GLs.
Summary
- Languages evolved from binary (1GL) to logic-based systems (5GL) to reduce programming difficulty.
- C is a Third Generation Language: portable, structured, and high-level relative to assembly.
- Higher generation does not mean faster execution, only easier and faster development.
One-Liner Revision
Programming languages evolved through five generations, from raw binary to natural-language-like systems, trading closeness to hardware for ease of human use.
1.2.1 History of C
Core Definition
C is a general-purpose, procedural programming language developed by Dennis Ritchie at Bell Labs between 1969 and 1973, derived from the earlier B and BCPL languages.
C was built so the Unix operating system could be rewritten in a language more powerful than assembly but still close enough to hardware to be efficient. That dual nature, powerful yet close to the machine, is why C is still taught first.
Detailed Explanation
C's lineage: ALGOL → BCPL (Basic Combined Programming Language) → B (simplified by Ken Thompson) → C (Dennis Ritchie added data types and structure). C was first used to rewrite the Unix kernel, proving that an operating system did not need to be written entirely in assembly. It was standardized to keep implementations consistent across vendors: ANSI C (1989, also called C89), ISO C90 (1990, essentially the same standard adopted internationally), C99 (added features like inline functions and variable-length arrays), and C11 (added multithreading and improved Unicode support) [verify exact feature lists per standard].
Summary
- Created by Dennis Ritchie at Bell Labs (1969–1973).
- Evolved from BCPL and B; built to rewrite Unix.
- Standardized as ANSI C (1989) and later C99, C11.
One-Liner Revision
C was created by Dennis Ritchie at Bell Labs to rewrite Unix, evolving from BCPL and B before being standardized as ANSI C.
1.2.2 Characteristics of C
Core Definition
The characteristics of C are the defining properties, procedural, structured, portable, efficient, and close to hardware, that explain why it remains a foundation for system and application programming.
These are simply the reasons C is still relevant: it runs fast, works on almost any machine, lets you organize code into reusable blocks, and lets you talk directly to memory when needed.
Detailed Explanation
Procedural language: a C program is a sequence of instructions executed step by step inside functions, following a clear procedure.
Structured language: code is organized into functions and blocks, making large programs easier to read, debug, and maintain instead of one long unbroken script.
Middle-level language: C combines low-level features (direct memory access through pointers, bit manipulation) with high-level features (functions, loops, structured syntax), sitting between assembly and languages like Python.
Portability: a C program written on one machine can run on another with little or no change, because the compiler, not the program, handles hardware-specific translation.
Efficient and fast: C compiles to very compact machine code with minimal runtime overhead, which is why operating systems and embedded systems still use it.
Rich library support: the Standard Library (stdio.h, stdlib.h, string.h, math.h, etc.) provides ready-made functions for input/output, memory management, and string handling.
Case-sensitive:Total and total are treated as two different identifiers.
Extensible: programmers can add their own functions and data types (via structures, typedef) to extend the language's built-in capability.
Pointer and dynamic memory support: C allows direct access to memory addresses and manual allocation/deallocation, giving fine-grained control unavailable in many higher-level languages.
Test Yourself
- Why is C called a "middle-level" language?
- How does case sensitivity affect identifier naming in C?
Answers: 1) It offers low-level features like pointers and direct memory access while also supporting high-level structured constructs like functions and loops.
2) Identifiers that differ only in letter case (e.g., sum and Sum) are treated as completely different names.
Summary
- C is procedural, structured, and middle-level (low-level power with high-level structure).
- Portable, efficient, case-sensitive, and extensible through user-defined types.
- Pointer support gives direct memory control, which is rare in higher-level languages.
One-Liner Revision
C is a fast, portable, structured middle-level language that is case-sensitive and gives direct control over memory.
1.2.3 Applications of C
Core Definition
The applications of C are the real-world domains where its speed and hardware closeness make it the preferred or foundational choice.
Wherever software needs to run fast and close to hardware, C is usually somewhere underneath it.
Detailed Explanation
Operating systems: Unix, Linux kernel, and parts of Windows are written in C because it can manage hardware resources directly.
Embedded systems: microcontrollers in appliances, automotive systems, and IoT devices run C due to its small memory footprint.
Compilers and interpreters: many language compilers (including early Python implementations) are themselves written in C.
Database engines: core engines of systems like MySQL and parts of PostgreSQL use C for performance-critical operations.
Device drivers: hardware drivers require direct register and memory access, which C provides.
Game development and graphics engines: performance-critical engine cores are often written in C or C-derived languages.
Summary
- Used in operating systems, embedded systems, compilers, database engines, drivers, and game engines.
- Chosen wherever speed and direct hardware access matter most.
One-Liner Revision
C powers operating systems, embedded devices, compilers, and drivers wherever speed and hardware control are critical.
1.3 Structure of a C Program
Core Definition
The structure of a C program is the fixed arrangement of sections, documentation, preprocessor directives, global declarations, the main() function, and user-defined functions, in which every C program must be organized for the compiler to process it correctly.
Every C program follows the same skeleton, like a form with fixed sections you fill in: a place for comments, a place to bring in libraries, a place for global data, the main starting point, and optional extra functions.
Detailed Explanation
A typical C program is built from these sections, in order:
1. Documentation section: comments describing the program's purpose, author, and date. Ignored by the compiler, used by humans.
2. Preprocessor directive section: lines starting with #, such as #include <stdio.h>, that bring in header files before compilation begins.
3. Global declaration section: variables and function prototypes declared outside any function, accessible throughout the file.
4. main() function section: every executable C program must have exactly one main() function; execution always begins here, regardless of where other functions are placed in the file.
5. User-defined function section: additional functions, defined either before or after main(), that main() can call.
| Section | Purpose | Compiler-visible? |
|---|---|---|
| Documentation | Explains program intent to readers | No (ignored) |
| Preprocessor directives | Includes headers, defines macros | Yes (processed first) |
| Global declarations | Shared variables/prototypes across functions | Yes |
| main() function | Program's entry point | Yes (mandatory) |
| User-defined functions | Reusable blocks of logic | Yes (optional) |
#include <stdio.h> int total = 0; int add(int a, int b); int main() { total = add(5, 3); printf("%d", total); return 0;}int add(int a, int b) { return a + b;}Let's Break It Down
Picture building a house. The documentation section is the architect's notes on the blueprint. Preprocessor directives are the imported materials list. Global declarations are the shared utility lines (electricity, water) running through the whole house. main() is the front door, the only entry point everyone uses first. User-defined functions are the individual rooms, built separately but reachable from the entrance.
Common Mistake
Students think the compiler executes code top-to-bottom regardless of function placement. In reality, execution always starts at main() no matter where it appears in the file; other functions only run when called from main() or from a function it calls.
Test Yourself
- Why must every executable C program contain a
main()function? - What happens if a function is defined after
main()but never declared before it?
Answers: 1) main() is the fixed entry point; the operating system/runtime always begins execution there.
2) Without a prior prototype, the compiler may not know the function's signature when it is first called inside main(), which can cause a compilation warning or error depending on the compiler.
Summary
- A C program has five sections: documentation, preprocessor directives, global declarations, main(), and user-defined functions.
- Execution always begins at
main(), regardless of physical placement of other functions. - Global declarations and function prototypes make names visible across the whole file.
One-Liner Revision
A C program is organized into documentation, preprocessor directives, global declarations, a mandatory main() entry point, and optional user-defined functions.
1.4 Compilation and Execution Process of a C Program
Core Definition
The compilation and execution process is the sequence of stages, preprocessing, compilation, assembly, linking, and loading, through which human-readable C source code is converted into a running machine-executable program.
You write code in English-like syntax, but the CPU only understands binary. Several tools work one after another to translate your code step by step until it becomes something the machine can actually run.
Detailed Explanation
The process happens in five ordered stages:
Step 1 → Preprocessing: the preprocessor scans the source file (.c) and resolves all lines starting with #. It expands #include by copying header file contents in, replaces #define macros with their values, and removes comments. Output: an expanded source file with no directives left.
Step 2 → Compilation: the compiler takes the preprocessed code and translates it into low-level assembly language for the target CPU, while also performing syntax and semantic checking. If there are syntax errors, the process stops here. Output: an assembly file (.s).
Step 3 → Assembly: the assembler converts the assembly code into machine-readable object code (binary, but not yet a complete program). Output: an object file (.o or .obj), containing machine instructions plus unresolved references to library functions like printf.
Step 4 → Linking: the linker combines the object file with the object code of library functions (such as the C standard library) and resolves all unresolved references, producing one self-contained executable file (.exe on Windows, no extension on Unix/Linux).
Step 5 → Loading and Execution: the loader copies the executable into main memory (RAM), sets up the program's runtime environment, and hands control to the operating system, which begins executing instructions starting from main().
| Stage | Tool | Input | Output |
|---|---|---|---|
| Preprocessing | Preprocessor | source.c | Expanded source |
| Compilation | Compiler | Expanded source | Assembly code (.s) |
| Assembly | Assembler | Assembly code | Object code (.o) |
| Linking | Linker | Object code + libraries | Executable file |
| Loading/Execution | Loader/OS | Executable | Running process |
Let's Break It Down
It is like preparing a meal kit for shipping. Preprocessing is gathering all the ingredients listed in the recipe. Compilation is chopping and prepping them into a usable form. Assembly is sealing each prepped ingredient into a labeled packet. Linking is packing every packet plus the shared spice kit (the library) into one sealed box. Loading is the delivery truck placing that box on your kitchen counter, ready to cook (execute) the moment you open it.
Common Mistake
Students confuse "compilation" with the entire build process. Strictly, compilation is only the single stage that converts preprocessed code into assembly; preprocessing, assembling, linking, and loading are separate stages that happen before the program actually runs.
Test Yourself
- What does the linker do that the compiler cannot do alone?
- At which stage would an undefined reference to a library function like
printftypically be caught?
Answers: 1) The linker resolves references to external library functions and combines multiple object files into one complete executable; the compiler only translates a single source file into assembly/object form.
2) During the linking stage, when the linker tries to resolve the function reference and cannot find its definition.
Summary
- Five stages: preprocessing → compilation → assembly → linking → loading/execution.
- Each stage has a dedicated tool and produces a specific intermediate file.
- The compiler only converts code to assembly; turning that into a runnable program needs the assembler and linker too.
One-Liner Revision
C source code becomes a running program through preprocessing → compilation → assembly → linking → loading, each handled by a dedicated tool.
1.5 C Tokens (Overview)
Core Definition
A token is the smallest individual unit of a C program that the compiler recognizes as meaningful, such as a keyword, identifier, constant, string literal, or operator.
Just like a sentence is built from words, a C program is built from tokens; the compiler reads the source code token by token to understand what you wrote.
Detailed Explanation
C recognizes five categories of tokens, each covered in detail next: Keywords (reserved words with fixed meaning), Identifiers (programmer-defined names), Constants (fixed values that never change), String literals (sequences of characters in double quotes), and Operators (symbols that act on data). The compiler's first internal job, lexical analysis, is to break the raw source text into this stream of tokens before any further checking happens.
Summary
- Tokens are the smallest meaningful units of a C program.
- Five categories: keywords, identifiers, constants, string literals, operators.
- The compiler's lexical analysis stage breaks source code into tokens first.
One-Liner Revision
Tokens are the smallest meaningful pieces of a C program: keywords, identifiers, constants, string literals, and operators.
1.5.1 Keywords
Core Definition
Keywords are reserved words in C that have a predefined meaning to the compiler and cannot be used as identifiers (variable, function, or label names).
These are words the language has already claimed for its own grammar, like "int" or "if," so you are not allowed to reuse them as your own variable names.
Detailed Explanation
Standard C has 32 keywords, always written in lowercase. They fall into functional groups:
| Category | Examples |
|---|---|
| Data type keywords | int, char, float, double, void, short, long, unsigned, signed |
| Control flow keywords | if, else, switch, case, default, for, while, do, break, continue, goto, return |
| Storage class keywords | auto, register, static, extern |
| Structure/derived type keywords | struct, union, enum, typedef |
| Other keywords | sizeof, const, volatile |
Because keywords are fixed, the compiler always interprets them the same way; you cannot redefine what if or int means inside your program.
Common Mistake
Trying to name a variable using a keyword, for example writing int for = 5;, causes a compilation error because for is reserved and cannot be reused as an identifier.
Test Yourself
- Why can't
floatbe used as a variable name?
Answers: 1) float is a reserved keyword with a fixed meaning (a data type), so the compiler will not allow it to be reused as an identifier.
Summary
- 32 reserved, lowercase words with fixed compiler-defined meaning.
- Grouped into data type, control flow, storage class, and structure-related keywords.
- Cannot be reused as identifiers.
One-Liner Revision
Keywords are C's 32 reserved, lowercase words with fixed meanings that can never be used as identifier names.
1.5.2 Identifiers
Core Definition
An identifier is a programmer-chosen name used to identify a variable, function, array, or any other user-defined entity in a C program.
Identifiers are simply the names you invent for things in your program, like naming a variable totalMarks so you can refer to it later.
Detailed Explanation
An identifier must follow strict naming rules:
Rule 1: must begin with a letter (A–Z, a–z) or an underscore (_), never a digit. In practice: _count is valid, 1count is not.
Rule 2: after the first character, it can contain letters, digits, and underscores, but no spaces or special characters like @, $, or -.
Rule 3: it cannot be a reserved keyword.
Rule 4: it is case-sensitive, so Sum and sum are two different identifiers.
Rule 5: some compilers only guarantee uniqueness up to a certain number of significant characters (historically 31), so extremely long names risk being treated as identical [verify exact limit per compiler].
| Identifier | Valid? | Reason |
|---|---|---|
| total_marks | Valid | Starts with letter, uses underscore |
| _temp | Valid | Starts with underscore |
| 2ndValue | Invalid | Starts with a digit |
| my-var | Invalid | Hyphen is not allowed |
| int | Invalid | Reserved keyword |
Common Mistake
Students try to start an identifier with a digit (e.g., 1total) or insert a space (my total), both of which the compiler rejects; only letters, digits (after the first character), and underscores are allowed, with no spaces.
Test Yourself
- Is
Total_1a valid identifier? Why? - Are
sumandSumthe same identifier in C?
Answers: 1) Yes, it starts with a letter and contains only letters, a digit, and an underscore.
2) No, C is case-sensitive, so they are treated as two distinct identifiers.
Summary
- Identifiers must start with a letter or underscore, never a digit.
- Can contain letters, digits, and underscores only; no spaces or special symbols.
- Case-sensitive and cannot match a reserved keyword.
One-Liner Revision
Identifiers are programmer-given names that must start with a letter or underscore and contain only letters, digits, and underscores.
1.5.3 Constants
Core Definition
A constant is a fixed value in a C program that does not change during program execution, such as a number or a single character.
A constant is a value you write directly into the code that stays exactly the same every time the program runs, unlike a variable, which can change.
Detailed Explanation
C supports several categories of constants:
Integer constants: whole numbers without a decimal point, e.g., 10, -25. Suffixes can specify type: U/u for unsigned, L/l for long, UL for unsigned long.
Floating-point (real) constants: numbers with a decimal point or exponent, e.g., 3.14, 2.5e3. Suffix f/F marks a value as float instead of the default double.
Character constants: a single character enclosed in single quotes, e.g., 'A', '5'; internally stored as the character's ASCII integer code.
(String constants, sequences of characters in double quotes, are covered separately as String Literals below, since they behave differently from single-value constants: they are stored as an array of characters.)
| Type | Example | Notes |
|---|---|---|
| Integer | 100, -7, 50000UL | No decimal point; suffixes change type |
| Floating-point | 3.14, 2.0e-3, 9.5f | Decimal point or exponent; default type double |
| Character | 'A', '7', '\n' | Single character in single quotes; stored as ASCII code |
Common Mistake
Writing a character constant with double quotes ("A") instead of single quotes ('A'). "A" is actually a string literal (a two-element char array including the null terminator), not a single character constant.
Test Yourself
- What value does the character constant
'A'actually store internally?
Answers: 1) It stores the ASCII integer code for 'A', which is 65.
Summary
- Constants are fixed values: integer, floating-point, and character constants.
- Suffixes (U, L, f) can specify a constant's exact type.
- Character constants use single quotes and are stored as ASCII codes.
One-Liner Revision
Constants are fixed integer, floating-point, or character values that never change during execution.
1.5.4 String Literals
Core Definition
A string literal is a sequence of characters enclosed in double quotes, automatically terminated by a null character (\0) and stored internally as a character array.
A string literal is just text in double quotes, like "Hello", and C secretly adds an invisible marker at the end so it knows where the text stops.
Detailed Explanation
Writing "Hello" creates an array of 6 characters in memory: H, e, l, l, o, and the hidden null terminator \0, even though only 5 visible characters were typed. String literals often include escape sequences, special two-character codes representing characters that cannot be typed directly.
| Escape Sequence | Meaning |
|---|---|
| \n | New line |
| \t | Horizontal tab |
| \\ | Backslash character |
| \' | Single quote character |
| \" | Double quote character |
| \0 | Null character (string terminator) |
Common Mistake
Students forget the hidden null terminator when calculating memory size; "Hi" looks like 2 characters but actually occupies 3 bytes in memory because of the automatically added \0.
Summary
- String literals are double-quoted text, stored as a character array.
- A hidden null terminator (
\0) is always added at the end. - Escape sequences represent special characters inside string literals.
One-Liner Revision
A string literal is double-quoted text stored as a character array, automatically ending with a hidden null terminator.
1.5.5 Operators (as a Token Category)
Core Definition
An operator is a symbol that instructs the compiler to perform a specific mathematical, relational, or logical action on one or more operands.
Operators are just the action symbols of the language, like + or >, that tell the computer what to do with the values around them.
Detailed Explanation
As a token type, an operator simply marks where an operation happens in an expression, such as + in a + b. C groups operators into several functional families (arithmetic, relational, logical, bitwise, assignment, and special operators), each covered in full depth, with examples and worked behaviour, in Section 1.9.
Summary
- Operators are symbols that act on operands to perform operations.
- Full classification (arithmetic, relational, logical, bitwise, etc.) is detailed in Section 1.9.
One-Liner Revision
Operators are symbols that act on data; their full classification is covered separately under Operators and Expressions.
1.6.1 Declaration and Initialization of Variables
Core Definition
Declaration tells the compiler a variable's name and data type so it can reserve memory for it; initialization assigns it a starting value at the moment it is declared.
Declaring a variable is like reserving a labeled box of a certain size; initializing it is putting something into that box right when you create it.
Detailed Explanation
Syntax for declaration: data_type variable_name;, e.g., int age; reserves memory of the size required for an int and labels it age. At this point its value is undefined (garbage) until assigned. Combined declaration and initialization: data_type variable_name = value;, e.g., int age = 20; reserves the memory and immediately stores 20 in it. Multiple variables of the same type can be declared together: int a = 5, b, c = 10;, where b remains uninitialized.
int age; int age = 20; int x = 1, y = 2, z; Common Mistake
Using a variable before initializing it, for example printing age right after int age; without assigning it a value, reads unpredictable garbage memory content rather than a safe default like 0.
Summary
- Declaration reserves memory and a type for a variable; it does not give it a meaningful value.
- Initialization assigns a starting value at the point of declaration.
- Uninitialized variables hold unpredictable garbage values until assigned.
One-Liner Revision
Declaration reserves a variable's memory and type; initialization gives it a starting value at the same time.
1.6.2 Scope of Variables
Core Definition
The scope of a variable is the region of the program within which that variable's name can be legally accessed and used.
Scope answers the question: "From where in my code can I actually use this variable's name?"
Detailed Explanation
Local (block) scope: a variable declared inside a function or inside {} braces (a block, like inside an if or for) is only accessible within that block; it disappears once the block ends.
Global (file) scope: a variable declared outside all functions is accessible from any function in the same file, from the point of declaration onward.
Function scope: applies specifically to labels used with goto; a label is visible anywhere within the function it is defined in, regardless of block nesting.
When a local variable has the same name as a global variable, the local one takes priority inside its block, temporarily "hiding" the global one; this is called variable shadowing.
| Scope Type | Where Declared | Accessible From |
|---|---|---|
| Local/Block | Inside a function or {} block | Only within that block |
| Global/File | Outside all functions | Any function in the file, after declaration |
| Function | As a goto label | Anywhere in the same function |
int x = 10; void show() { int x = 5; printf("%d", x); }Common Mistake
Assuming a local variable with the same name as a global variable overwrites the global one. It does not; it only shadows (temporarily hides) it within its own block. The global variable's value is untouched outside that block.
Test Yourself
- What happens to a local variable once its enclosing block finishes execution?
- If a local variable shares its name with a global variable, which one is used inside that block?
Answers: 1) It goes out of scope and its memory is released; it can no longer be accessed.
2) The local variable takes priority and is used inside that block; the global variable is temporarily shadowed, not destroyed.
Summary
- Scope determines where a variable's name can be legally used.
- Local/block scope is limited to the enclosing block; global scope spans the whole file.
- A local variable can shadow a global one with the same name within its block.
One-Liner Revision
Scope defines where a variable's name is visible: local to a block, or global across the whole file.
1.6.3 Lifetime (Storage Duration) of Variables
Core Definition
The lifetime of a variable is the duration for which it occupies memory during program execution, from the moment it is created until the moment that memory is released, and this is controlled by its storage class.
Lifetime answers: "How long does this variable actually exist in memory, even if its name later goes out of scope?"
Detailed Explanation
Lifetime in C is governed by four storage class keywords:
auto (Automatic): the default storage class for variables declared inside a function/block. Memory is created when the block is entered and destroyed when the block exits. Most ordinary local variables are auto by default, so the keyword is rarely written explicitly.
register: requests that the compiler store the variable in a CPU register instead of regular memory for faster access; it is a hint, not a guarantee, since registers are limited. Cannot have its address taken with &.
static: a variable retains its value between function calls instead of being recreated each time. It is initialized only once, the first time the block is entered, and persists for the entire program's run even though its scope may still be local to the function.
extern: declares that a variable is defined in another file or elsewhere in the same file; it has global lifetime (exists for the whole program) but does not allocate new memory at the point of declaration, it just refers to existing memory.
| Storage Class | Default Initial Value | Scope | Lifetime |
|---|---|---|---|
| auto | Garbage (undefined) | Local (block) | Until block exits |
| register | Garbage (undefined) | Local (block) | Until block exits |
| static | 0 (by default) | Local or global | Entire program run |
| extern | 0 (by default, set in original file) | Global | Entire program run |
void counter() { static int count = 0; count++; printf("%d\n", count);}Let's Break It Down
Think of a hostel room. An auto variable is like a guest who checks in and checks out within the same day, the room resets completely after they leave. A static variable is like a permanent resident, the room and their belongings stay exactly as they left them, ready the next time they return, even though other guests can use the lobby (scope) only while they are physically present.
Common Mistake
Confusing scope with lifetime. A static local variable still has local scope (only that function can use its name) but its lifetime spans the whole program, unlike a normal local (auto) variable whose memory is destroyed once its block ends.
Test Yourself
- Why does a
staticlocal variable retain its value across multiple function calls? - What is the key difference between a variable's scope and its lifetime?
Answers: 1) Because static allocates its memory once for the entire program duration and initializes it only on the first call; subsequent calls reuse the same memory without re-initializing it.
2) Scope is about where the variable's name can be accessed in the code; lifetime is about how long the variable's memory actually exists during execution. A variable can have local scope but program-long lifetime, as with static.
Summary
- Lifetime is controlled by storage class: auto, register, static, extern.
- auto and register exist only while their block is active; static and extern persist for the whole program.
- Scope and lifetime are independent concepts; static variables prove this clearly.
One-Liner Revision
A variable's lifetime, how long it exists in memory, is set by its storage class: auto/register last only within their block, while static/extern last the whole program.
1.7.1 Basic (Primary) Data Types
Core Definition
Basic data types are the fundamental, built-in types in C, int, char, float, double, and void, that directly represent the simplest kinds of data without being built from other types.
These are the five core "boxes" the language gives you out of the box: whole numbers, single characters, decimal numbers (two precisions), and a special "nothing" type.
Detailed Explanation
int: stores whole numbers (no decimal point). Typically 2 or 4 bytes depending on the system/compiler.
char: stores a single character, internally as a 1-byte integer (its ASCII code).
float: stores single-precision decimal numbers, typically 4 bytes, with about 6–7 significant digits of precision.
double: stores double-precision decimal numbers, typically 8 bytes, with about 15–16 significant digits of precision, used when more accuracy is needed than float provides.
void: represents the absence of a type, used for functions that return no value, or for generic pointers that can point to any data type.
Modifiers (signed, unsigned, short, long) can be combined with int and char to adjust their size and range. For an n-bit signed integer, the range is calculated as: Minimum = −2(n−1), Maximum = 2(n−1) − 1. For a 16-bit signed int, this gives a range of −32768 to 32767; for a 32-bit signed int, the range is −2147483648 to 2147483647.
| Type | Typical Size | Typical Range (signed) | Format Specifier |
|---|---|---|---|
| char | 1 byte | −128 to 127 | %c |
| int | 2 or 4 bytes | −32768 to 32767 (2 bytes) [verify per system] | %d |
| float | 4 bytes | ~1.2E−38 to 3.4E+38 [verify] | %f |
| double | 8 bytes | ~2.3E−308 to 1.7E+308 [verify] | %lf |
| void | No size (no value stored) | Not applicable | Not applicable |
Common Mistake
Assuming int is always exactly 2 or always exactly 4 bytes across all systems. The actual size is compiler and platform dependent; the safe way to check it on a given system is the sizeof(int) operator rather than memorizing one fixed number.
Test Yourself
- Why does
doubleoffer more precision thanfloat? - What is the range formula for an n-bit signed integer, and what does it give for 16 bits?
Answers: 1) double typically uses 8 bytes compared to float's 4 bytes, allowing more bits for the significant digits and giving roughly double the decimal precision.
2) Range = −2^(n−1) to 2^(n−1) − 1; for 16 bits this gives −32768 to 32767.
Summary
- Five basic types: int, char, float, double, void.
- Modifiers (signed, unsigned, short, long) adjust size and range.
- Signed n-bit integer range = −2^(n−1) to 2^(n−1) − 1.
One-Liner Revision
The basic data types, int, char, float, double, and void, represent whole numbers, characters, decimals of two precisions, and "no value" respectively.
1.7.2 Derived Data Types
Core Definition
Derived data types are types built from basic data types using the language's own constructs, namely arrays, pointers, and functions.
These are not brand-new kinds of data, they are basic types combined or arranged in a new way, like a row of boxes (array) or a note pointing to where a box is kept (pointer).
Detailed Explanation
Arrays: a fixed-size collection of elements of the same data type stored in contiguous memory, accessed using an index, e.g., int marks[5]; stores 5 integers under one name.
Pointers: a variable that stores the memory address of another variable rather than a direct value, e.g., int *p; can hold the address of an int variable.
Functions: a named, reusable block of code that takes inputs (parameters) and optionally returns a value of a specific type; the function itself is treated as having a derived type based on its return type and parameters.
| Derived Type | Built From | Example |
|---|---|---|
| Array | Multiple elements of one basic type | int marks[5]; |
| Pointer | Address of a basic/derived type | int *p; |
| Function | Basic/derived return type + parameters | int add(int a, int b); |
One-Liner Revision
Derived data types, arrays, pointers, and functions, are built by arranging or referencing basic data types in new ways.
Summary
- Three derived types: arrays, pointers, functions.
- Arrays group same-type elements; pointers store addresses; functions package reusable logic.
1.7.3 User-Defined Data Types
Core Definition
User-defined data types let a programmer create custom data types using structure, union, enumeration, or typedef, rather than relying only on built-in types.
When the built-in types don't naturally describe what you're modeling, like a student with a name, roll number, and marks together, C lets you design your own type to group them.
Detailed Explanation
Structure (struct): groups variables of different data types under one name, each member getting its own separate memory. Useful for modeling real-world records, e.g., a student record with name, roll number, and marks together.
Union: similar syntax to struct, but all members share the same memory location, so only one member holds a valid value at a time; total size equals the size of its largest member.
Enumeration (enum): assigns readable names to a set of integer constants, e.g., enum Day {MON, TUE, WED}; where MON=0, TUE=1, WED=2 by default, improving code readability over plain numbers.
typedef: creates an alias (alternate name) for an existing type, making complex declarations shorter and code more readable, e.g., typedef unsigned int uint; lets you write uint instead of unsigned int.
| Type | Memory Behavior | Use Case |
|---|---|---|
| struct | Each member gets separate memory | Grouping related but different fields (a record) |
| union | All members share the same memory | Memory-efficient storage when only one value is needed at a time |
| enum | Stored as integers internally | Naming a fixed set of related constants |
| typedef | No new memory; just an alias | Simplifying complex or repeated type names |
Common Mistake
Confusing struct and union sizes. A struct's total size is roughly the sum of its members' sizes; a union's size equals only its largest member's size, since members overlap in the same memory.
Test Yourself
- Why is a union's size determined by its largest member rather than the sum of all members?
Answers: 1) Because all members of a union occupy the same shared memory location rather than separate ones, so the union only needs enough space for the biggest member to hold any one of them at a time.
Summary
- Four user-defined types: struct, union, enum, typedef.
- struct gives each member separate memory; union shares memory among members.
- enum names integer constants; typedef just renames an existing type.
One-Liner Revision
User-defined types, struct, union, enum, and typedef, let programmers build or rename types to fit real-world data better than built-in types alone.
1.8 Type Casting: Implicit and Explicit Type Conversion
Core Definition
Type casting is the process of converting a value from one data type to another, either automatically by the compiler (implicit) or manually by the programmer (explicit).
Type casting is just changing what "kind" of value something is treated as, like turning a whole number into a decimal number so a calculation works the way you want.
Detailed Explanation
Implicit type conversion (automatic/coercion): the compiler converts one operand's type to another automatically, usually from a smaller/lower-ranked type to a larger/higher-ranked one, following C's usual arithmetic conversion rules, so no data is lost. Example: in int a = 5; float b = a;, the integer 5 is automatically converted to 5.0 when assigned to b.
Explicit type conversion (casting): the programmer manually forces a conversion using the cast operator (type), often used to convert a larger type to a smaller one, which can lose data (narrowing). Example: int x = (int)3.99; forces the float value to be truncated to 3.
| Aspect | Implicit Conversion | Explicit Conversion |
|---|---|---|
| Who triggers it | Compiler automatically | Programmer manually, using (type) |
| Typical direction | Lower type to higher type (widening) | Often higher type to lower type (narrowing) |
| Data loss risk | Usually safe, no loss | Possible loss of precision or truncation |
| Example | int → float during assignment | (int)3.99 → 3 |
int a = 5;float b = a; float c = 3.99;int d = (int)c; Common Mistake
Assuming explicit casting from float to int rounds the value. It actually truncates (cuts off the decimal part) rather than rounding, so (int)3.99 becomes 3, not 4.
Test Yourself
- Why is converting a float to an int considered a "narrowing" conversion?
- What is the output of
(int)7.8?
Answers: 1) Because it moves from a type that can represent fractional, larger-range values to a type that can only represent whole numbers, risking loss of the decimal portion.
2) 7 (truncation, not rounding).
Summary
- Implicit conversion happens automatically, usually widening with no data loss.
- Explicit conversion is manual, using (type), often narrowing and risking data loss.
- Casting float to int truncates the decimal part rather than rounding.
One-Liner Revision
Implicit casting happens automatically and safely; explicit casting is manual and can lose data through truncation.
1.9.1 Arithmetic Operators
Core Definition
Arithmetic operators are symbols (+, -, *, /, %) that perform basic mathematical operations on numeric operands.
These are the everyday math symbols: add, subtract, multiply, divide, and find the remainder.
Detailed Explanation
All five operators work as expected on numbers, but two behaviours are frequently tested: integer division and the modulo operator.
| Operator | Meaning | Example (a=10, b=3) | Result |
|---|---|---|---|
| + | Addition | a + b | 13 |
| - | Subtraction | a - b | 7 |
| * | Multiplication | a * b | 30 |
| / | Division | a / b | 3 (integer division, decimal dropped) |
| % | Modulus (remainder) | a % b | 1 |
When both operands of / are integers, the result is an integer with the fractional part discarded (not rounded), so 10 / 3 gives 3, not 3.33. The % operator only works with integer operands and gives the remainder after division.
Common Mistake
Expecting 10 / 3 to give 3.33. Since both operands are integers, C performs integer division and discards the decimal part, giving 3. To get a decimal result, at least one operand must be a float, e.g., 10.0 / 3.
One-Liner Revision
Arithmetic operators (+, -, *, /, %) perform basic math; integer division truncates, and % only works on integers.
Summary
- Five operators: +, -, *, /, %.
- Integer division truncates the decimal part instead of rounding.
- % (modulus) requires integer operands and returns the remainder.
1.9.2 Relational Operators
Core Definition
Relational operators compare two values and produce an integer result, 1 for true or 0 for false, since C has no separate boolean type.
These operators just ask a yes/no comparison question, like "is a bigger than b?", and C answers with 1 (yes) or 0 (no).
Detailed Explanation
| Operator | Meaning | Example (a=5, b=8) | Result |
|---|---|---|---|
| < | Less than | a < b | 1 (true) |
| > | Greater than | a > b | 0 (false) |
| <= | Less than or equal to | a <= b | 1 (true) |
| >= | Greater than or equal to | a >= b | 0 (false) |
| == | Equal to | a == b | 0 (false) |
| != | Not equal to | a != b | 1 (true) |
Common Mistake
Writing = (assignment) instead of == (comparison) inside a condition, e.g., if (a = 5) assigns 5 to a and the condition becomes true because 5 is non-zero, instead of comparing whether a equals 5.
One-Liner Revision
Relational operators compare two values and return 1 (true) or 0 (false), since C has no built-in boolean type.
Summary
- Six relational operators:<, >, <=, >=, ==, !=.
- Results are always 1 (true) or 0 (false), C's substitute for booleans.
- Mixing up = and == is a classic and serious bug.
1.9.3 Logical Operators
Core Definition
Logical operators (&&, ||, !) combine or invert multiple conditions, treating any non-zero value as true and zero as false.
These let you ask combined questions, like "is it raining AND is it cold?", and C evaluates the whole combined condition to a single true/false answer.
Detailed Explanation
&& (Logical AND): true only if both conditions are true. || (Logical OR): true if at least one condition is true. ! (Logical NOT): reverses a condition's truth value.
C uses short-circuit evaluation for && and ||: in A && B, if A is false, B is never evaluated at all because the overall result is already guaranteed false. Similarly, in A || B, if A is true, B is skipped. This matters when B has side effects, like a function call or an unsafe memory access, since that code may not run at all.
| Operator | Meaning | Example (a=1, b=0) | Result |
|---|---|---|---|
| && | AND (both true) | a && b | 0 (false) |
| || | OR (at least one true) | a || b | 1 (true) |
| ! | NOT (inverts) | !a | 0 (false, since a was true) |
Let's Break It Down
Picture checking two locks before opening a door: "is the key lock open AND is the chain lock open?" The moment you find the key lock is still locked, you don't even bother checking the chain, you already know the door won't open. That's short-circuit evaluation: C stops checking further conditions the instant the answer is already decided.
Common Mistake
Relying on a side-effect inside the second operand of && or || always running, e.g., if (a != 0 && (b = compute())); if a == 0, compute() never executes due to short-circuiting, so b stays unassigned.
Test Yourself
- In
x > 0 || y++ > 0, ifxis 5, willyget incremented?
Answers: 1) No, because x > 0 is already true, so || short-circuits and the right-hand side y++ > 0 is never evaluated, meaning y is not incremented.
Summary
- Three logical operators: &&, ||, !.
- Any non-zero value is treated as true; zero is false.
- Short-circuit evaluation can skip the second condition entirely, affecting any side effects inside it.
One-Liner Revision
Logical operators (&&, ||, !) combine conditions and use short-circuit evaluation, skipping the second condition when the result is already determined.
1.9.4 Bitwise Operators
Core Definition
Bitwise operators (&, |, ^, ~, <<, >>) operate directly on the individual binary bits of integer operands rather than on their decimal values.
Instead of treating numbers as whole values, bitwise operators look inside the number's binary form, bit by bit, and manipulate each 0 or 1 individually.
Detailed Explanation
& (Bitwise AND): each result bit is 1 only if both corresponding input bits are 1.
| (Bitwise OR): each result bit is 1 if at least one corresponding input bit is 1.
^ (Bitwise XOR): each result bit is 1 only if the corresponding input bits differ.
~ (Bitwise NOT/Complement): flips every bit of its single operand, 0 becomes 1 and 1 becomes 0.
<< (Left shift): shifts all bits left by a given number of positions, filling vacated positions on the right with 0; each left shift by 1 effectively multiplies the value by 2.
>> (Right shift): shifts all bits right by a given number of positions; for unsigned values this fills vacated positions on the left with 0, effectively dividing by 2 per shift.
Worked walkthrough: evaluate 5 & 3 using 4-bit representations.
5 = 01013 = 0011AND (bit by bit): 0101& 0011------ 0001 → result = 15 | 3: 0101| 0011------ 0111 → result = 75 << 1: 0101 shifted left by 1 → 1010 → result = 10 (i.e., 5 × 2)| Operator | Name | Example | Result |
|---|---|---|---|
| & | AND | 5 & 3 | 1 |
| | | OR | 5 | 3 | 7 |
| ^ | XOR | 5 ^ 3 | 6 |
| ~ | NOT | ~5 | -6 [verify, depends on bit width/representation] |
| << | Left shift | 5 << 1 | 10 |
| >> | Right shift | 5 >> 1 | 2 |
Common Mistake
Confusing the bitwise AND/OR (&, |) with the logical AND/OR (&&, ||). Bitwise operators act bit-by-bit on binary representations and can return values other than 0/1, while logical operators always return only 0 or 1 based on overall truth.
Test Yourself
- What is the result of
6 & 5, using binary working? - Why does
n << 1effectively multiplynby 2?
Answers: 1) 6 = 0110, 5 = 0101, AND gives 0100 = 4.
2) Shifting all bits one position to the left doubles the place value each bit represents, which is mathematically equivalent to multiplying the whole number by 2.
Summary
- Bitwise operators act on individual binary bits: &, |, ^, ~, <<, >>.
- Left shift by n multiplies by 2^n; right shift by n divides by 2^n (for unsigned values).
- Bitwise operators are distinct from, and easily confused with, logical operators.
One-Liner Revision
Bitwise operators (&, |, ^, ~, <<, >>) manipulate the individual binary bits of integers rather than their overall decimal value.
1.9.5 Assignment Operators
Core Definition
Assignment operators store a value into a variable, with the simple = operator and compound forms (+=, -=, *=, /=, %=) that combine an operation with assignment in one step.
Assignment puts a value into a variable's box; compound assignment operators let you update that value using its own current content in one shorter step.
Detailed Explanation
a = b simply copies the value of b into a. Compound operators are shorthand: a += b means a = a + b; similarly -=, *=, /=, and %= apply the respective arithmetic operation to a's current value and store the result back in a.
| Operator | Equivalent To | Example (a=10) | Result (a becomes) |
|---|---|---|---|
| = | Direct assignment | a = 5 | 5 |
| += | a = a + b | a += 5 | 15 |
| -= | a = a - b | a -= 5 | 5 |
| *= | a = a * b | a *= 2 | 20 |
| /= | a = a / b | a /= 2 | 5 |
| %= | a = a % b | a %= 3 | 1 |
One-Liner Revision
Compound assignment operators (+=, -=, *=, /=, %=) combine an arithmetic operation with assignment in a single, shorter step.
Summary
- = assigns a value directly; compound operators combine an arithmetic step with assignment.
- a += b is shorthand for a = a + b, and similarly for -=, *=, /=, %=.
1.9.6 Increment and Decrement Operators
Core Definition
Increment (++) and decrement (--) operators increase or decrease a variable's value by exactly 1, and can be applied either before (prefix) or after (postfix) the variable.
These are shortcuts for adding or subtracting 1, but where you place the symbol, before or after the variable, changes exactly when the increase happens relative to the rest of the expression.
Detailed Explanation
Prefix (++a): the variable is incremented first, and the already-updated value is used in the surrounding expression.
Postfix (a++): the current (old) value of the variable is used in the surrounding expression first, and only afterward is the variable incremented.
Worked walkthrough:
int a = 5;int x = ++a; int b = 5;int y = b++; When used as standalone statements (e.g., a++; alone on its own line), prefix and postfix produce the identical end result; the difference only matters when the increment is part of a larger expression that also reads the value, such as an assignment or function argument.
Common Mistake
Assuming a++ and ++a always behave identically. They differ only in what value gets used at the moment of the surrounding expression; the variable's final stored value after the statement completes is the same either way, but the value used inside the expression differs.
Test Yourself
- If
int p = 3; int q = p++ + 2;, what are the final values ofpandq?
Answers: 1) q uses the old value of p (3) before incrementing, so q = 3 + 2 = 5; p is then incremented to 4.
Summary
- ++ and -- adjust a variable's value by exactly 1.
- Prefix updates the variable before using its value; postfix uses the old value first, then updates.
- The difference only matters when the increment is embedded inside a larger expression.
One-Liner Revision
Prefix (++a) increments before using the value; postfix (a++) uses the old value first, then increments.
1.9.7 Conditional (Ternary) Operator
Core Definition
The conditional (ternary) operatorcondition ? expr1 : expr2 evaluates a condition and returns expr1 if it is true, or expr2 if it is false, all within a single expression.
It is a compact one-line replacement for a simple if-else: "if this is true, give me this value, otherwise give me that one."
Detailed Explanation
It is called "ternary" because it is the only operator in C that takes three operands: the condition, the true-result expression, and the false-result expression. It is most useful for short, single-value decisions rather than entire blocks of logic.
int a = 10, b = 20, max;max = (a > b) ? a : b; Common Mistake
Nesting too many ternary operators inside one expression to replace a multi-branch if-else chain. While syntactically valid, this quickly becomes hard to read and debug; a regular if-else or switch is clearer for more than one condition level.
Test Yourself
- Rewrite
result = (x % 2 == 0) ? "Even" : "Odd";as an if-else statement.
Answers: 1) if (x % 2 == 0) { result = "Even"; } else { result = "Odd"; }
Summary
- Ternary operator: condition ? expr1 : expr2, the only three-operand operator in C.
- Best for short, single-value decisions; avoid deep nesting for readability.
One-Liner Revision
The ternary operator (condition ? a : b) is a compact one-line substitute for a simple if-else.
1.9.8 Special Operators: sizeof, Comma, Address-of, and Dereference
Core Definition
Special operators, sizeof, the comma operator (,), address-of (&), and dereference (*), each perform a distinct utility role beyond ordinary arithmetic or logical operations.
These four operators each do one specific job: measure size, chain expressions, find an address, or read the value an address points to.
Detailed Explanation
sizeof: returns the number of bytes occupied in memory by a variable or data type, evaluated at compile time. Example: sizeof(int) typically returns 4 on most modern systems [verify per system].
Comma operator (,): evaluates multiple expressions left to right within a single statement, and the overall expression's value is the value of the rightmost expression. Commonly used inside for loop headers to update multiple variables. Example: for (i = 0, j = 10; i < j; i++, j--) updates both i and j each iteration.
Address-of (&): returns the memory address where a variable is stored. Example: &a gives the address of variable a, commonly used to initialize pointers.
Dereference (*): when applied to a pointer, accesses the value stored at the address the pointer holds. Example: if p holds the address of a, then *p gives the actual value of a.
| Operator | Purpose | Example |
|---|---|---|
| sizeof | Returns size in bytes of a type/variable | sizeof(int) → 4 |
| , (comma) | Evaluates multiple expressions, returns rightmost | i++, j-- |
| & (address-of) | Gets the memory address of a variable | &a |
| * (dereference) | Gets the value at a pointer's address | *p |
int a = 10;int *p = &a; printf("%d", *p); Common Mistake
Confusing the dereference operator * used with pointers (*p, accessing a value at an address) with the multiplication operator * used between two numbers; the same symbol means different things depending on context.
Test Yourself
- If
p = &a, what does*pgive you?
Answers: 1) It gives the actual value stored in the variable a, by following the address stored in p.
Summary
- sizeof measures memory size in bytes; comma chains expressions and returns the rightmost result.
- & returns a variable's address; * dereferences a pointer to get the value at that address.
- * means dereference with pointers but multiplication between numbers, depending on context.
One-Liner Revision
sizeof measures bytes, comma chains expressions, & gets an address, and * dereferences a pointer to its stored value.
1.9.9 Operator Precedence and Associativity
Core Definition
Operator precedence determines which operator is evaluated first when an expression contains multiple operators; associativity determines the evaluation order (left-to-right or right-to-left) among operators of the same precedence level.
Precedence is the rulebook for "which operator goes first," and associativity is the tiebreaker for "which direction to read in" when two operators are equally important.
Detailed Explanation
Without these rules, an expression like 2 + 3 * 4 would be ambiguous. C resolves this with a fixed precedence table; multiplication has higher precedence than addition, so it is evaluated first regardless of left-to-right reading order, giving 2 + 12 = 14, not 5 * 4 = 20.
| Precedence (High→Low) | Operators | Associativity |
|---|---|---|
| 1 (Highest) | () [] -> . (postfix) | Left to right |
| 2 | ! ~ ++ -- (unary) sizeof (type) * (deref) & (address-of) | Right to left |
| 3 | * / % | Left to right |
| 4 | + - (binary) | Left to right |
| 5 | << >> | Left to right |
| 6 | < <= > >= | Left to right |
| 7 | == != | Left to right |
| 8 | & (bitwise AND) | Left to right |
| 9 | ^ | Left to right |
| 10 | | | Left to right |
| 11 | && | Left to right |
| 12 | || | Left to right |
| 13 | ?: (ternary) | Right to left |
| 14 (Lowest, excluding comma) | = += -= *= /= %= | Right to left |
| 15 (Lowest overall) | , (comma) | Left to right |
Worked walkthrough: evaluate 10 + 6 / 2 * 3 - 1.
Step 1 (highest precedence among these): / and * have equal, higher precedence than + and -, and are left-associative, so evaluate left to right: 6 / 2 = 3 3 * 3 = 9Step 2: Now the expression is: 10 + 9 - 1 + and - are equal precedence, left-associative: 10 + 9 = 19 19 - 1 = 18Final result: 18Let's Break It Down
Think of precedence like a queue at a counter sorted by ticket priority: gold tickets (high precedence operators like * and /) get served before silver tickets (+ and -), no matter where they stand in line. Associativity is the rule for two people holding the same priority ticket: left-to-right associativity means whoever is standing more to the left gets served first among equals.
Common Mistake
Assuming assignment (=) happens left to right like most other operators. Assignment is actually right-associative, which is why a chained statement like a = b = 5; correctly assigns 5 to b first, then assigns the result to a, rather than failing or behaving unpredictably.
Test Yourself
- Evaluate
5 + 2 * 3 - 1step by step using precedence rules. - Why is the ternary operator right-associative, and what does that mean for nested ternaries?
Answers: 1) 2 * 3 = 6 first (higher precedence), then 5 + 6 = 11, then 11 - 1 = 10.
2) Right-associativity means that when ternary operators are nested without parentheses, the rightmost condition is grouped and evaluated first, which affects how a chain like a ? b : c ? d : e is interpreted.
Summary
- Precedence decides which operator runs first; associativity decides direction among equal-precedence operators.
- Multiplicative operators (*, /, %) outrank additive operators (+, -); both are left-associative.
- Assignment and the ternary operator are right-associative, unlike most other operators.
One-Liner Revision
Precedence decides which operator runs first; associativity breaks ties between operators of equal precedence, left-to-right for most, right-to-left for assignment and ternary.
Whole Chapter Summary
- 1.1 Evolution of Programming Languages: languages progressed through five generations, from binary machine code to logic-based 5GL systems, trading hardware closeness for human ease.
- 1.2.1 History of C: created by Dennis Ritchie at Bell Labs to rewrite Unix, evolving from BCPL and B, later standardized as ANSI C.
- 1.2.2 Characteristics of C: procedural, structured, middle-level, portable, efficient, case-sensitive, and capable of direct memory access.
- 1.2.3 Applications of C: used in operating systems, embedded systems, compilers, databases, drivers, and game engines.
- 1.3 Structure of a C Program: fixed sections, documentation, preprocessor directives, global declarations, main(), and user-defined functions, with execution always starting at main().
- 1.4 Compilation and Execution Process: source code becomes a running program through preprocessing, compilation, assembly, linking, and loading/execution.
- 1.5 C Tokens: the smallest meaningful units, keywords, identifiers, constants, string literals, and operators.
- 1.5.1 Keywords: 32 reserved, fixed-meaning words that cannot be used as identifiers.
- 1.5.2 Identifiers: programmer-defined names following strict naming rules (start with a letter/underscore, case-sensitive).
- 1.5.3 Constants: fixed integer, floating-point, and character values that never change.
- 1.5.4 String Literals: double-quoted text stored as a char array with a hidden null terminator.
- 1.5.5 Operators (token): symbols acting on operands; full classification covered under 1.9.
- 1.6.1 Declaration and Initialization: declaration reserves memory and type; initialization gives a starting value.
- 1.6.2 Scope of Variables: local/block scope is limited to its block; global scope spans the whole file.
- 1.6.3 Lifetime of Variables: controlled by storage class (auto, register, static, extern); static/extern persist for the whole program.
- 1.7.1 Basic Data Types: int, char, float, double, void, each with defined size, range, and format specifier.
- 1.7.2 Derived Data Types: arrays, pointers, and functions, built from basic types.
- 1.7.3 User-Defined Data Types: struct, union, enum, and typedef for custom data modeling.
- 1.8 Type Casting: implicit conversion is automatic and safe; explicit conversion is manual and can truncate data.
- 1.9.1–1.9.9 Operators and Expressions: arithmetic, relational, logical (with short-circuiting), bitwise, assignment, increment/decrement (pre vs post), ternary, special operators (sizeof, comma, &, *), and the precedence/associativity rules that resolve evaluation order in any expression.
Key Formulas and Points Sheet
| Formula / Rule | Meaning |
|---|---|
| Signed n-bit integer range = −2^(n−1) to 2^(n−1) − 1 | Calculates the minimum and maximum value an n-bit signed integer can hold |
| n << k ≈ n × 2^k | Left-shifting by k positions multiplies the value by 2^k |
| n >> k ≈ n ÷ 2^k (unsigned) | Right-shifting by k positions divides the value by 2^k |
| a += b ⇔ a = a + b | Compound assignment is shorthand for operate-then-assign (same for -=, *=, /=, %=) |
| Integer division (a / b, both int) | Result is truncated toward zero, decimal part discarded |
| Precedence order (high→low, key groups) | () → unary (!, ~, ++, --, sizeof, *, &) → * / % → + - → relational → == != → & → ^ → | → && → || → ?: → = → , |
Last-Minute Revision Sheet
Evolution → 1GL binary, 2GL assembly, 3GL C/high-level, 4GL SQL (what not how), 5GL AI/logic.
C origin → Dennis Ritchie, Bell Labs, derived from B/BCPL, standardized as ANSI C.
C traits → procedural + structured + middle-level + portable + fast + case-sensitive.
Program structure → doc → preprocessor → global decl → main() → functions.
Build pipeline → preprocess → compile (→ assembly) → assemble (→ object) → link (→ exe) → load/run.
Tokens → keywords, identifiers, constants, string literals, operators.
Keywords → 32 reserved words, can't be variable names.
Identifiers → start letter/underscore, no spaces, case-sensitive.
Constants → integer / float / char, fixed value.
String literal → "text" + hidden \0 terminator.
Declaration vs initialization → reserve memory vs give starting value.
Scope → where the name is visible (local/global).
Lifetime → how long memory exists (auto/register = block; static/extern = whole program).
Basic types → int, char, float, double, void.
Derived types → array, pointer, function.
User-defined types → struct (separate memory), union (shared memory), enum (named constants), typedef (alias).
Type casting → implicit = automatic/safe; explicit = manual, can truncate.
Arithmetic → int/int division truncates; % needs integers.
Relational → returns 1/0, no real boolean.
Logical → &&, ||, ! ; short-circuit skips unneeded checks.
Bitwise → &, |, ^, ~, <<, >> operate on binary bits, not decimal value.
Increment/decrement → prefix updates first, postfix uses old value first.
Ternary → condition ? true_val : false_val, right-associative.
Special operators → sizeof (bytes), comma (chain, rightmost wins), & (address), * (dereference).
Precedence/associativity → precedence picks which operator first; associativity breaks ties (most left-to-right, assignment/ternary right-to-left).


