Unit 1: Basics and Introduction to C
1. Evolution, Paradigm, and Compilation Model of C
Developed by Dennis Ritchie at Bell Laboratories between 1969 and 1973 as a successor to Ken Thompson's B language, the C programming language was engineered to rewrite the UNIX operating system. It occupies a unique niche as a "middle-level" procedural language: combining the abstract structured flow, typing systems, and portability of high-level languages with the low-level memory addressing, pointer arithmetic, and bit-manipulation capabilities of assembly language.
1.1 The Multi-Stage Compilation Pipeline
A C source file (.c) undergoes four distinct phases before generating an executable binary machine file:
CODE[source.c] ---> | Preprocessor | ---> [source.i] ---> | Compiler | ---> [source.s] ^ ^ | (Macros, #include) | (Syntactic/Semantic) +--------------------------------------------------------+ v [source.s] ---> | Assembler | ---> [source.o / .obj] ---> | Linker | ---> [executable] ^ ^ | (Machine Code Translation) | (Resolves symbols, links libc)
- Preprocessing (
cpp):- Strips comments (
/* ... */and// ...). - Expands macro definitions (
#define). - Recursively incorporates header file declarations (
#include <stdio.h>). - Evaluates conditional compilation directives (
#ifdef,#ifndef,#if,#endif). - Yields a pure expanded preprocessed source stream (
.i).
- Strips comments (
- Compilation (
cc/gcc):- Performs lexical analysis (tokenization), syntax analysis (parsing into Abstract Syntax Trees - AST), semantic validation, and target-independent code optimizations.
- Translates high-level C logic into architecture-specific assembly instructions (
.s).
- Assembly (
as):- Converts mnemonic assembly instructions into binary object code (relocatable machine instructions with unresolved symbol offsets), saving it as an object module (
.oon Linux,.objon Windows).
- Converts mnemonic assembly instructions into binary object code (relocatable machine instructions with unresolved symbol offsets), saving it as an object module (
- Linking (
ld):- Resolves external references, global variables, and imported library functions (such as
printf()from the C Standard Librarylibc). - Concatenates multiple compiled object modules, assigns concrete runtime memory offsets, and emits an executable binary (
.exeon Windows, ELF binary on Linux/Unix).
- Resolves external references, global variables, and imported library functions (such as
2. The C Character Set and Lexical Tokens
The C compiler reads source code as a continuous stream of characters from the basic source character set, tokenizing them into six fundamental classes of lexical tokens: Keywords, Identifiers, Constants, String Literals, Operators, and Punctuators/Separators.
2.1 The Character Set
- Alphabets: Uppercase Latin (
A–Z) and Lowercase Latin (a–z). C is strictly case-sensitive (Sum,sum, andSUMrepresent distinct identifiers). - Digits: Decimal numerals
0through9. - Special Characters: Arithmetic symbols, punctuation marks, brackets, and delimiters (
+,-,*,/,%,=,<,>,!,&,|,^,~,(,),{,},[,],;,:,,,.,',",?,#,_,\). - White Space Characters: Blank space (
' '), horizontal tab (\t), newline (\n), vertical tab (\v), carriage return (\r), and form feed (\f). Whitespaces serve to delimit adjacent tokens and are discarded during lexical analysis, except when enclosed in string or character literals. - Escape Sequences: Non-graphic or control characters preceded by a backslash (
\):\a: Audible alert (bell)\b: Backspace\f: Form feed\n: Line feed (newline)\r: Carriage return\t: Horizontal tab\v: Vertical tab\\: Literal backslash\': Single quotation mark\": Double quotation mark\?: Question mark literal\0: Null terminator (ASCII value 0)\ooo: Octal byte specification\xhh: Hexadecimal byte specification
3. Keywords and Identifiers
3.1 Keywords (Reserved Words)
Keywords are reserved lexical tokens with fixed, immutable syntactic meanings defined by the C language specification. They cannot be repurposed or redefined as user variable names, function names, or tags.
The 32 Standard ANSI C (C89/C90) Keywords:
c// Fundamental Data Types & Qualifiers
char, int, float, double, void, short, long, signed, unsigned, const, volatile
// Storage Class Specifiers
auto, register, static, extern
// Control Flow & Branching
if, else, switch, case, default
// Looping & Jumps
for, while, do, break, continue, goto, return
// Derived Types & Metaprogramming
struct, union, enum, typedef, sizeof
(Note: C99 added _Bool, _Complex, _Imaginary, inline, restrict; C11 added _Alignas, _Atomic, _Generic, _Static_assert, _Thread_local).
3.2 Identifiers (Naming Rules and Scope)
An identifier is a programmer-defined symbolic name assigned to program entities such as variables, functions, symbolic constants, arrays, and user-defined structures.
Strict Syntactic Rules for Valid Identifiers:
- Must begin strictly with an alphabetic character (
A–Z,a–z) or an underscore (_). - Subsequent characters may consist of any combination of letters, decimal digits (
0–9), or underscores. - No special punctuation or operators (
$,@,#,%,-, space) are permissible. - Cannot clash with any reserved C keyword.
- Case sensitivity applies:
count,Count, andCOUNToccupy separate symbol table entries. - Standard C implementations guarantee uniqueness up to at least 31 characters for internal identifiers and 6 (or 31 in modern revisions) characters for external linkage identifiers.
c// Valid Identifiers:
int student_age;
float _grossSalary99;
double speedOfLight;
// Invalid Identifiers:
int 99balloons; // Error: Begins with decimal numeral
float net-pay; // Error: Contains hyphen/minus operator
double default; // Error: 'default' is a reserved keyword
char user name; // Error: Embedded whitespace
4. Constants and Literals
Constants are immutable data values hardcoded into source code that cannot be altered during program execution.
4.1 Integer Constants
Integer literals represent whole numerical quantities without fractional components. They can be expressed in three distinct numerical radix bases:
- Decimal (Base 10): Non-zero leading digit followed by digits 0–9 (e.g.,
42,-108,1000). - Octal (Base 8): Must begin with a leading zero (
0), followed exclusively by octal digits 0–7 (e.g.,052represents ). - Hexadecimal (Base 16): Must begin with
0xor0X, followed by hexadecimal digits 0–9 andA–F/a–f(e.g.,0x2Arepresents ).
Integer Type Suffixes:
uorU: Unsigned literal (42U)lorL: Long integer (42L)llorLL: Long long integer (42LL)- Combinations:
42ULL(Unsigned long long integer)
4.2 Floating-Point (Real) Constants
Represent fractional numbers and can be written in fractional or scientific notation:
- Fractional Notation: Digits separated by a decimal radix point (e.g.,
3.14159,.75,12.0). - Exponential / Scientific Notation: Mantissa followed by
eorEand an exponent of 10 (e.g.,6.022e23,1.5E-4). - Suffixes: Default floating constants are of type
double. AppendingforFspecifiesfloat(3.14f), whilelorLspecifieslong double(3.14L).
4.3 Character Constants
A single character enclosed within single quotes ('A', '9', '\n').
- In C, character constants have type
int(holding the integer ASCII numerical code of the character). For instance,'A'evaluates to integer value65.
4.4 String Literals
A sequence of characters enclosed within double quotation marks ("Engineering C").
- Automatically appended by the compiler with an implicit null terminator character
'\0'(ASCII 0) at the terminal index. - Memory size = (number of visible characters + 1) bytes.
"Hello"occupies 6 consecutive bytes in the read-only.rodatamemory segment.
4.5 Symbolic Constants (#define vs. const)
- Preprocessor Macros (
#define):c#define MAX_BUFFER 1024- Evaluated during the text-substitution preprocessing stage before semantic compilation.
- Allocates no memory; has no type checking; has global file scope until
#undef.
- Read-Only Qualified Variables (
const):cconst int MAX_BUFFER = 1024;- Evaluated during compilation; strictly enforces data types and compiler warnings.
- Allocates physical memory in the symbol table; subject to standard lexical block scoping.
5. Primary Data Types, Memory Layout, and Type Qualifiers
The type system dictates memory allocation, binary encoding schemas, and valid operational manipulations for declared identifiers.
CODE+---------------------------------------------------------------------------------+ | C DATA TYPES | +----------------------------------------+----------------------------------------+ | +-----------------------------------+-----------------------------------+ | | v v +------------------------+ +------------------------+ | PRIMARY / BASIC | | DERIVED | | char, int, float, | | Arrays, Pointers, | | double, void | | Structures, Unions, | +------------------------+ | Functions | +------------------------+
5.1 Primary (Fundamental) Data Types
| Type Specifier | Typical Size (bytes) | Typical Bit Width | Typical Range (Two's Complement) | Format Specifier |
|---|---|---|---|---|
char / signed char | 1 | 8 | to | %c / %d |
unsigned char | 1 | 8 | to | %c / %u |
short int (short) | 2 | 16 | to | %hd |
unsigned short int | 2 | 16 | to | %hu |
int | 4 (or 2 on 16-bit) | 32 | to | %d or %i |
unsigned int | 4 | 32 | to | %u |
long int (long) | 4 or 8 (platform) | 32 or 64 | to (or to ) | %ld |
unsigned long int | 4 or 8 | 32 or 64 | to (or to ) | %lu |
long long int | 8 | 64 | to | %lld |
float (IEEE 754) | 4 | 32 | to (6 sig. digits) | %f / %e / %g |
double (IEEE 754) | 8 | 64 | to (15 sig. digits) | %lf / %le |
long double | 10, 12, or 16 | 80 to 128 | to (19+ digits) | %Lf |
void | Incomplete | 0 | Represents the absence of type or value | N/A |
5.2 Size and Type Qualifiers
- Sign Qualifiers (
signed,unsigned):signed: Uses the most significant bit (MSB) as a sign bit (, ) using two's complement arithmetic.unsigned: Treats all bits as positive magnitude, doubling the positive numeric ceiling.
- Size Qualifiers (
short,long,long long):- Dictates minimum storage width: .
- Type Qualifiers (
const,volatile):const: Promises that the variable's value will not be modified after initialization. Attempting modification triggers a compile-time error.volatile: Informs the compiler optimizer that the variable's value may be altered asynchronously by external hardware, interrupt service routines (ISRs), or concurrent threads; prevents compiler caching of the variable in processor registers.
6. Variables, Declarations, and Lvalues vs. Rvalues
6.1 Variable Declaration vs. Definition
- Declaration: Informs the compiler of the variable's name and data type without allocating physical memory storage. Example:
extern int global_counter;. - Definition: Informs the compiler of the variable's name and type, and physically reserves memory storage space in the data/BSS/stack segment. Example:
int count;. - Initialization: Assigning an initial value simultaneously with definition. Example:
int score = 100;.
6.2 Lvalues and Rvalues
- Lvalue (Locator Value): An expression designating an identifiable object that occupies a locatable, persistent physical address in memory. An lvalue can appear on the left-hand or right-hand side of an assignment operator. A modifiable lvalue is an lvalue that is not array-typed, not incomplete, and not
const-qualified. - Rvalue (Read Value): A transient data value stored temporarily in registers or calculated on the fly. An rvalue can appear exclusively on the right-hand side of an assignment operator.
cint x = 10; // 'x' is a modifiable lvalue; '10' is an rvalue
x = x + 5; // 'x' on LHS is lvalue; '(x + 5)' evaluates to an rvalue
// 10 = x; // COMPILE ERROR: Constant '10' is an rvalue, cannot be on LHS
// (x + 2) = 5; // COMPILE ERROR: '(x + 2)' is a temporary rvalue
7. Operators and Operational Semantics in C
Operators are syntactic symbols that instruct the processor to carry out specific mathematical, relational, or logical evaluations on operands.
7.1 Arithmetic Operators
Operate on numerical operands to produce computed numerical values:
- Addition (
+), Subtraction (-), Multiplication (*) - Division (
/):- If both operands are integers, integer division is performed, truncating the fractional part towards zero (e.g.,
7 / 2evaluates to integer3). - If either operand is floating-point, floating-point division is performed (e.g.,
7.0 / 2evaluates to3.5).
- If both operands are integers, integer division is performed, truncating the fractional part towards zero (e.g.,
- Modulo (
%):- Yields the remainder of integer division (e.g.,
7 % 3evaluates to1). - Both operands must strictly be integral (
int,char,short,long). Applying%tofloatordoubleoperands triggers a compile-time syntax error (one must usefmod()from<math.h>for real numbers). - In ANSI C99/C11, the sign of the result of
%matches the sign of the numerator (dividend):(-7) % 3yields-1, whereas7 % (-3)yields+1.
- Yields the remainder of integer division (e.g.,
7.2 Unary Operators
Act upon a single operand:
- Unary Plus (
+) and Unary Minus (-): Inverts or maintains algebraic sign (-x). - Increment (
++) and Decrement (--):- Prefix (
++a,--a): Increments or decrements the operand first, then returns the newly updated value. - Postfix (
a++,a--): Returns the current value of the operand first, then increments or decrements the stored variable at the next sequence point.
cint a = 5, b, c; b = ++a; // a becomes 6, b receives 6 c = a++; // c receives 6, a becomes 7 - Prefix (
- The
sizeofOperator:- A compile-time unary operator that returns the memory footprint (in bytes) of a data type or expression.
- Evaluates to an unsigned integer type
size_t(printed using%zu). - Crucial Rule: The expression inside
sizeof(expr)is not evaluated at runtime.
cint i = 5; size_t s = sizeof(i++); // i remains 5 at runtime; i++ is never executed!
7.3 Relational and Equality Operators
Compare values and return an integer boolean result: 1 (representing true) or 0 (representing false).
- Less than (
<), Greater than (>), Less than or equal to (<=), Greater than or equal to (>=) - Equality (
==), Inequality (!=) - Common Pitfall: Accidental assignment inside conditions:
if (x = 5)assigns 5 toxand evaluates to 5 (true), rather than testingif (x == 5).
7.4 Logical Operators
Perform boolean logic over conditional expressions:
- Logical AND (
&&): True if and only if both operands evaluate to non-zero. - Logical OR (
||): True if at least one operand evaluates to non-zero. - Logical NOT (
!): Unary operator; inverts boolean truth value (!0yields1;!5yields0). - Short-Circuit Evaluation:
- In
A && B, ifAevaluates to0(false),Bis never evaluated because the compound expression cannot be true. - In
A || B, ifAevaluates to non-zero (true),Bis never evaluated because the expression is already guaranteed true.
cint x = 0, y = 10; if (x != 0 && (y / x) > 2) { // Safe! (y / x) is never evaluated, preventing division-by-zero crash. } - In
7.5 Bitwise Operators
Directly manipulate the binary bit strings of integer operands:
- Bitwise AND (
&): Sets bit to 1 if both corresponding bits are 1. - Bitwise OR (
|): Sets bit to 1 if either corresponding bit is 1. - Bitwise XOR (
^): Sets bit to 1 if the corresponding bits are distinct (one 0 and one 1). Useful for toggling bits and in-place swapping without temporary variables. - Bitwise NOT / One's Complement (
~): Unary operator; flips all 0s to 1s and 1s to 0s (~x = -(x + 1)in two's complement). - Bitwise Left Shift (
<<): Shifts bits left, filling emptied lower bits with 0s. Shifting left by bits multiplies an unsigned integer by . - Bitwise Right Shift (
>>): Shifts bits right. For unsigned types, vacated higher bits are filled with 0s (logical shift). For signed types, behavior is implementation-defined (usually sign-extended arithmetic shift).
7.6 Assignment and Shorthand Operators
- Simple Assignment (
=): Assigns the evaluated rvalue of the right expression to the modifiable lvalue on the left. - Compound Assignment (
+=,-=,*=,/=,%=,&=,|=,^=,<<=,>>=):a += bis syntactically equivalent toa = a + (b), evaluatingaonly once.
7.7 Conditional (Ternary) Operator (?:)
The sole ternary operator in C, functioning as an inline expression-level alternative to if-else:
- If
Conditionis non-zero,Expression1is evaluated and becomes the result. - If
Conditionis zero,Expression2is evaluated and becomes the result.
cint max = (a > b) ? a : b;
7.8 Comma Operator (,)
Functions as an operator (distinct from comma as a syntactic separator in declarations and parameter lists).
- Evaluates its left operand, discards its result, introduces a sequence point, then evaluates its right operand and returns the right operand's value and type.
- Has the lowest precedence in the entire C language.
cint a = (1, 2, 3); // Evaluates 1, then 2, then 3; 'a' is assigned 3
8. Operator Precedence and Associativity Table
When an expression contains multiple operators, precedence dictates which operator binds first. If operators have equal precedence, associativity dictates whether evaluation groups from Left-to-Right or Right-to-Left.
| Precedence Rank | Operator Group | Operators | Associativity |
|---|---|---|---|
| 1 (Highest) | Postfix / Member Access | (), [], ->, ., ++ (postfix), -- (postfix) | Left to Right |
| 2 | Unary (Prefix) | + (unary), - (unary), ! , ~, ++ (prefix), -- (prefix), * (dereference), & (address-of), sizeof, (type) (cast) | Right to Left |
| 3 | Multiplicative | *, /, % | Left to Right |
| 4 | Additive | +, - | Left to Right |
| 5 | Shift | <<, >> | Left to Right |
| 6 | Relational | <, <=, >, >= | Left to Right |
| 7 | Equality | ==, != | Left to Right |
| 8 | Bitwise AND | & | Left to Right |
| 9 | Bitwise XOR | ^ | Left to Right |
| 10 | Bitwise OR | | | Left to Right |
| 11 | Logical AND | && | Left to Right |
| 12 | Logical OR | || | Left to Right |
| 13 | Conditional (Ternary) | ?: | Right to Left |
| 14 | Assignment | =, +=, -=, *=, /=, %=, &=, ^=, |=, <<=, >>= | Right to Left |
| 15 (Lowest) | Comma | , | Left to Right |
9. Type Conversions: Implicit Promotion vs. Explicit Type Casting
When operands of differing types interact within an expression, C reconciles data discrepancies through strict conversion hierarchies to prevent loss of precision.
9.1 Implicit Type Conversion (Usual Arithmetic Conversions)
Performed automatically by the compiler. Follows a two-step promotion sequence:
- Integer Promotion: Small integer types (
char,signed char,unsigned char,short,unsigned short) are automatically promoted toint(orunsigned intifintcannot represent all values) before any arithmetic operation. - Arithmetic Conversion Hierarchy: The operand with the lower rank is promoted to match the higher-ranking operand:
cchar c = 10;
short s = 20;
float f = 5.5f;
double d = 2.0;
double result = (c * s) + (f / d);
// 1. (c * s): 'c' and 's' promoted to 'int'. Result is 'int' (200).
// 2. (f / d): 'f' is promoted to 'double'. Result is 'double' (2.75).
// 3. 'int' 200 is promoted to 'double' 200.0, resulting in 202.75 of type 'double'.
9.2 Explicit Type Conversion (Type Casting)
The programmer explicitly forces conversion using the unary cast operator: (target_type) expression.
- Used to prevent unintended integer truncation during division or to convert pointers.
cint total_marks = 450;
int count = 5;
double average = (double) total_marks / count; // Ensures floating-point division
10. Sequence Points, Order of Evaluation, and Undefined Behavior
10.1 Sequence Points
A sequence point is a designated juncture in program execution where all side effects of preceding evaluations are guaranteed to be complete, and no side effects of subsequent evaluations have begun.
Key sequence points include:
- At the terminating semicolon of an expression statement.
- The
&&,||, and comma (,) operators (after evaluating the left operand). - The
?:conditional operator (after evaluating the condition expression). - Immediately before a function call executes (after evaluating all arguments).
10.2 Undefined Behavior (UB)
Modifying a scalar variable more than once between two consecutive sequence points—or modifying it and reading its value for something other than computing the new value—results in Undefined Behavior according to the C ISO standard:
cint i = 5;
i = i++; // UNDEFINED BEHAVIOR!
int x = ++i + ++i; // UNDEFINED BEHAVIOR!
printf("%d %d\n", ++i, i++); // UNDEFINED BEHAVIOR: Argument evaluation order is unspecified!
Best Practice: Keep expressions clean, singular, and free of concurrent side effects to ensure predictable and portable code across all compilers and CPU architectures.