C Programming Notes
Course: Structured Programming Concepts
Course Code: 26CSE0105 / 26CSE0101 • Credits: 4 (L-T-P: 2-0-4)
Curriculum Alignment: Chitkara University, Punjab • B.E. Computer Science & Engineering (Semester 01)
Standard Baseline: Modern ISO C (C11 / C17 Standards Compliant)
Purpose: Comprehensive exam revision handbook covering problem solving, logic development, programming syntax, dry-run tracing, output prediction, and code debugging.
Course & Examination Overview
This revision handbook covers the complete Structured Programming Concepts curriculum for Semester 01. It is designed to prepare students for theoretical examinations (CAT-1, CAT-2, End-Term) as well as practical programming evaluations:
- Problem Solving & Logic Development: Algorithm design and characteristics, standard flowchart symbols and conventions, pseudocode drafting, and structured vs. unstructured programming paradigms.
- Core C Fundamentals: Program anatomy, compilation stages (
gcc), tokens, identifiers, keywords, primitive data types, type conversion rules, escape sequences, and formatted console I/O (printf/scanf). - Operators & Expressions: Arithmetic, relational, logical, bitwise, assignment, conditional (
? :), andsizeofoperators, along with complete operator precedence and associativity tables. - Control Flow & Decision Making: Two-way selection (
if-else), multi-way branching (else-ifladders,switch-case), menu-driven programming, iterative loops (while,do-while,for), nested loops, and pattern generation. - Modular Programming & Scope: User-defined functions, function prototypes, parameter passing mechanisms (pass-by-value vs. pass-by-reference), return values, recursion, and storage classes (
auto,static,extern,register). - Aggregates & Memory Management: 1D and 2D arrays, matrix operations, searching (linear/binary) and sorting, character arrays (strings) and string libraries (
string.h), pointers and pointer arithmetic, dynamic memory allocation (malloc,calloc,realloc,free), memory leak prevention, and user-defined records (struct,union,typedef, and bit-fields). - File Systems & Debugging: Stream-based file handling (
fopen,fclose,fprintf,fscanf,fread,fwrite), classification of programming errors (syntax, runtime, logical), output prediction questions, and systematic debugging strategies.
[!TIP] How to Study This Guide: For every program, trace the logic through the supplied dry-run tables before looking at the verified output. Pay close attention to boundary conditions (e.g., zero, negative values, and equal-element ties).
Contents
- Problem solving, algorithms, pseudocode and flowcharts
- Introduction to C, program structure and compilation
- Tokens, identifiers, literals and comments
- Data types, sizes, units and conversions
- Variables, constants, scope, lifetime and storage classes
- Input, output and formatting
- Operators, precedence, evaluation and bit manipulation
- Decisions and original scenario practice
- Loops, tracing, patterns and numeric problems
- Functions and recursion: added foundations
- Arrays, searching, sorting and matrices: added foundations
- C strings and standard string operations
- Worked string algorithms
- Pointers, addresses and pointer arithmetic
- Pointers with functions, callbacks and returned pointers
- Dynamic allocation, leaks, stack and heap
- Structures, unions, enumerations and type aliases
- Bit-fields
- Variable argument lists
- File handling, positioning and binary data
- Standard library revision reference
- Preprocessing and coding conventions: added foundations
- Errors, behavior categories and debugging
- Worked arithmetic practice
- Output prediction and validity practice
- Short-answer and comparison bank
- Final revision sheets and mastery checklist
- Curriculum syllabus mapping and programming best practices
1. Problem solving, algorithms, pseudocode and flowcharts
1.1 From a question to a program
A programming problem describes a required transformation from inputs to outputs. Before coding, identify:
- Inputs: What values arrive, in what form and how many?
- Outputs: What must be calculated, displayed or stored?
- Constraints: What values are allowed? Can the collection be empty? Can values repeat?
- Processing: What formula, comparison or repetition connects input to output?
- Tests: What ordinary and boundary cases will show whether the solution works?
For an average, the inputs might be three marks, processing is their sum divided by three, and output is a decimal result. For a maximum, the processing is comparison rather than addition. Writing these distinctions prevents coding the wrong problem correctly.
1.2 Algorithm and its characteristics
Definition for an exam: An algorithm is a finite sequence of precise, executable steps for solving a specified problem.
| Characteristic | What it means | Example of a failure |
|---|---|---|
| Input | Zero or more supplied values are identified | Referring to an unexplained value |
| Output | At least one result is specified | Calculating without producing the required result |
| Definiteness | Every instruction has an unambiguous meaning | “Choose a suitable number” without a rule |
| Finiteness | Execution eventually terminates | Repetition with no reachable stopping condition |
| Effectiveness | Each step can actually be carried out | Requiring an impossible operation |
| Correctness | Valid inputs produce the required outputs | A maximum algorithm that fails on equal values |
Efficiency matters too: two correct algorithms can use different amounts of time and memory. A sequential scan for an array maximum takes time proportional to the number of elements.
1.3 Common strategies
- Decomposition: Split a larger task into input, calculation, validation and display functions.
- Brute force: Examine every candidate, appropriate for small search spaces.
- Pattern recognition: Identify a repeated mathematical or structural relationship.
- Working backward: Start from the desired result and infer the required preceding state.
- Divide and conquer: Divide into smaller subproblems, solve them, and combine their results. Not every informal split is a divide-and-conquer algorithm.
The basic control structures are sequence, selection and iteration. Recursion solves a problem using smaller calls to the same function and needs a base case.
1.4 One problem in three representations
Problem: Find the greatest of three integers, including ties.
Algorithm: Read the three values. Set largest to the first. Replace it with the second if the second is greater. Repeat for the third. Display largest and stop.
Pseudocode:
BEGIN
READ a, b, c
largest ← a
IF b > largest THEN
largest ← b
END IF
IF c > largest THEN
largest ← c
END IF
PRINT largest
END
Flowchart:
flowchart TD
A([Start]) --> B[/Read a, b, c/]
B --> C[largest = a]
C --> D{b > largest?}
D -- Yes --> E[largest = b]
D -- No --> F{c > largest?}
E --> F
F -- Yes --> G[largest = c]
F -- No --> H[/Print largest/]
G --> H
H --> I([Stop])
| Symbol | Meaning |
|---|---|
| Oval | Start or stop |
| Rectangle | Processing or assignment |
| Parallelogram | Input/output |
| Diamond | Decision with labelled outcomes |
| Arrow | Direction of execution |
| Connector circle | Connection between separated parts |
Program 1: Largest of three, including ties
#include <stdio.h>
int main(void) {
int a, b, c;
if (scanf("%d%d%d", &a, &b, &c) != 3) {
puts("Invalid input");
return 1;
}
int largest = a;
if (b > largest) largest = b;
if (c > largest) largest = c;
printf("Largest = %d\n", largest);
return 0;
}
Sample input:
9 9 2
Expected output:
Largest = 9
Other verified cases (⏎ marks a line break):
| Input | Expected output | Exit status |
|---|---|---|
-8 -3 -9 |
Largest = -3 ⏎ |
0 |
4 4 4 |
Largest = 4 ⏎ |
0 |
x |
Invalid input ⏎ |
1 |
Dry run for input 9 9 2:
| Step | Test | largest afterwards |
|---|---|---|
| Initialize | First value | 9 |
| Compare second | 9 > 9 is false |
9 |
| Compare third | 2 > 9 is false |
9 |
Using independent “greater than both others” checks with a final unconditional third-value choice can fail when the first two values tie. Initialization-and-update handles ties naturally.
1.5 Comparison and structured programming
| Algorithm | Pseudocode | Flowchart |
|---|---|---|
| Step-by-step solution | Programming-like expression of the solution | Visual representation of the solution |
| Can be written in ordinary language | Uses conventions such as READ and IF | Uses standard symbols |
| Focuses on the method | Eases translation into code | Exposes branching and repetition |
Structured programming organizes logic using blocks, functions, decisions and loops. Excessive unstructured jumps obscure how control reaches a statement. A goto is legal C; its existence alone does not make a program incorrect, but ordinary exam algorithms are usually clearer without it.
2. Introduction to C, program structure and compilation
2.1 Background and features
C is a general-purpose procedural language developed by Dennis Ritchie at Bell Labs around 1972, closely associated with UNIX. BCPL and B influenced its development. ANSI standardized C in 1989; the corresponding ISO standard followed in 1990. Later editions include C99, C11 and C17. Do not mix language versions when answering questions about keywords or syntax.
Useful features to explain in a theory answer:
- Functions support procedural decomposition and reusable code.
- Types describe values and valid operations.
- Operators support arithmetic, comparisons, logic and bit manipulation.
- Pointers provide indirect access and support dynamic data structures.
- Compilation can produce efficient native programs.
- A standard library supplies common services.
- Source portability is possible when implementation-specific assumptions are avoided.
C does not automatically check every array access or reclaim each allocation when a pointer leaves scope. It gives the programmer responsibility for these operations.
2.2 Anatomy of a program
Program 2: A complete C program
#include <stdio.h>
int main(void) {
int packs = 3, pencils_per_pack = 12;
printf("Total pencils = %d\n", packs * pencils_per_pack);
return 0;
}
Input: None.
Expected output:
Total pencils = 36
| Part | Purpose |
|---|---|
#include <stdio.h> |
Makes the standard I/O declarations available |
int main(void) |
Defines a hosted-program entry point taking no arguments |
| Declarations | Introduce typed objects |
| Expression/statement | Calculates or performs an action |
| Semicolon | Terminates many statements and declarations |
| Braces | Delimit a function or compound statement |
return 0; |
Reports successful program termination |
A header supplies declarations and related definitions; including it is not the same as copying every library function's implementation into your program.
Standard hosted forms include int main(void) and int main(int argc, char *argv[]). Here argc counts command-line arguments and argv contains strings. Ordinary helper functions may return void or char, but those are not the portable return types for hosted main. In C99 and later, reaching the closing brace of main returns zero.
2.3 Translation stages
| Stage | Main task | Typical output |
|---|---|---|
| Preprocessing | Includes headers, expands macros, handles conditional inclusion | Preprocessed source |
| Compilation | Checks and translates C constructs | Assembly, conceptually |
| Assembly | Encodes assembly instructions | Object file |
| Linking | Resolves definitions and combines objects/libraries | Executable |
| Loading/execution | Starts the executable in its environment | Running process |
Toolchains may combine stages internally. The conceptual separation helps identify why undeclared variables and missing function definitions produce different kinds of diagnostics.
gcc -std=c17 -Wall -Wextra -Wpedantic main.c -o main
gcc -E main.c -o main.i
gcc -S main.c -o main.s
gcc -c main.c -o main.o
gcc main.o -o main
gcc main.c helper.c -o main
gcc math_example.c -lm -o math_example
-o names the output; -g adds debugging information; -O2 requests optimization. -Wall is useful but does not mean every warning. Some systems need -lm after the source/object files for mathematical functions.
2.4 Setup and terminal essentials
Install an editor and a C compiler; they are separate components. VS Code's C/C++ extension helps editing but does not itself provide the compiler. Make the installed compiler available through PATH, reopen the terminal and verify gcc --version. On macOS the gcc command may identify a Clang-based toolchain; inspect the version output rather than assuming its implementation.
| Task | Windows CMD | Linux/macOS shell |
|---|---|---|
| Current directory | cd |
pwd |
| List entries | dir |
ls |
| Change directory | cd folder |
cd folder |
| Parent directory | cd .. |
cd .. |
| Root directory | cd \ |
cd / |
| Create directory | mkdir folder |
mkdir folder |
| Remove empty directory | rmdir folder |
rmdir folder |
| Create empty file | type nul > new.c |
touch new.c |
| Display contents | type new.c |
cat new.c |
| Copy | copy a.c b.c |
cp a.c b.c |
| Move/rename | move / ren |
mv |
| Delete file | del file |
rm file |
| Clear display | cls |
clear |
| Locate command | where gcc |
command -v gcc |
| Run local executable | main.exe |
./main |
| Exit | exit |
exit |
In PowerShell, use .\main.exe for the local executable. Quote paths containing spaces. Creation commands using > can overwrite an existing file. Network/process commands from the website are general terminal knowledge, not C syntax: examples include Windows ipconfig/tasklist, macOS ifconfig/ps, and ping on both.
3. Tokens, identifiers, literals and comments
Token: A smallest meaningful lexical unit recognized during translation.
| Category | Examples | Recognition rule |
|---|---|---|
| Keyword | int, if, return |
Reserved language spelling |
| Identifier | total, find_max |
Programmer-chosen name, subject to rules |
| Constant | 42, 2.5, 'A' |
Numeric or character value |
| String literal | "Ready" |
Quoted character sequence |
| Operator/punctuator | +, =, ;, ( |
Operation or syntactic structure |
Introductory books often split operators and special symbols into separate token categories. In formal lexical terminology many operator spellings are punctuators; use your course's classification while recognizing the overlap.
Worked tokenization: int total = marks + 5; consists of int, total, =, marks, +, 5, ;.
For ordinary identifiers, use letters, digits and underscores; do not start with a digit, insert a space or use a keyword. Case matters. score and Score differ. Avoid names reserved for implementations, especially leading double underscores and underscore-plus-capital forms.
| Valid beginner name | Invalid beginner name | Why invalid |
|---|---|---|
student_2 |
2student |
Starts with digit |
totalMarks |
total marks |
Contains space |
average |
float |
Keyword |
final_score |
final-score |
Hyphen becomes subtraction punctuation |
Keywords and version awareness
The traditional 32 C90 keywords are:
auto break case char const continue default do
double else enum extern float for goto if
int long register return short signed sizeof static
struct switch typedef union unsigned void volatile while
C17 also has later additions such as inline, restrict, _Bool, _Alignas, _Alignof, _Atomic, _Generic, _Noreturn, _Static_assert, _Thread_local, _Complex and _Imaginary. Therefore “C has exactly 32 keywords” needs a C90 qualification.
Literal forms
- Decimal integer:
25; octal:031; hexadecimal:0x19. These represent the same number. - An integer literal with a leading zero uses octal in the C17 examples.
08is invalid. - Suffixes specify types or influence type selection:
25u,25L,25LL. - Unsuffixed floating literals are normally
double:2.5; use2.5fforfloat. - An ordinary character constant such as
'A'has typeintin C, even though it commonly initializes achar. "A"is an array containing'A'and'\0', not the same thing as'A'.-5is unary minus applied to the constant5.
Comments and escapes
// ends at the line ending; /* ... */ spans lines. Block comments do not nest. Comment markers inside a string literal are ordinary string characters.
| Escape | Meaning |
|---|---|
\n |
Newline |
\t |
Horizontal tab |
\r |
Carriage return |
\b |
Backspace |
\a |
Alert |
\f |
Form feed |
\v |
Vertical tab |
\\ |
Backslash |
\", \' |
Quote characters |
\0 |
Null character |
Exam distinction: 0, '0', '\0', NULL and "0" are not interchangeable. They respectively describe a number, the digit character, the null character, a null-pointer macro, and a string containing a digit plus terminator.
4. Data types, sizes, units and conversions
4.1 Basic types and modifiers
| Type | Use | Common modern size, not a universal promise |
|---|---|---|
char |
Character-sized integer | 1 C byte, commonly 8 bits |
short |
Small integer | 2 bytes |
int |
General integer | 4 bytes |
long |
Wider-range integer | 4 or 8 bytes |
long long |
At least 64-bit-range integer | 8 bytes |
float |
Floating point | 4 bytes |
double |
Higher-precision floating point | 8 bytes |
long double |
At least double's precision | Implementation-dependent |
_Bool |
Boolean zero/one value | Implementation-dependent |
void |
No value/incomplete type | Not an ordinary sized object type |
short int and short are equivalent spellings. unsigned means unsigned int when used alone as a type. signed, unsigned, short and long cannot be combined arbitrarily with every type: unsigned float is invalid.
Arrays, pointers and functions build more complex types. Structures, unions and enumerations describe grouped or named data. typedef creates an alias, not a separate distinct type.
4.2 Portable facts and illustrative ranges
sizeof measures size in C bytes and has type size_t. sizeof(char) is always one. CHAR_BIT from <limits.h> reports bits per byte. Sizes follow sizeof(char) <= sizeof(short) <= sizeof(int) <= sizeof(long) <= sizeof(long long).
For an illustrative 32-bit two's-complement signed integer, the range is −2,147,483,648 through 2,147,483,647. A 32-bit unsigned integer ranges from zero through 4,294,967,295. Use INT_MIN, INT_MAX and UINT_MAX rather than assuming a particular implementation. <float.h> provides floating limits and precision macros.
Unsigned arithmetic wraps modulo one more than its maximum value. Signed overflow is not defined wraparound in C17. Floating arithmetic has finite precision: many decimal fractions cannot be represented exactly in binary.
Fragment:
printf("int occupies %zu bytes\n", sizeof(int));
For an ordinary non-VLA operand, sizeof does not evaluate that operand. Thus sizeof(x++) does not increment an ordinary integer x.
4.3 Units
| Decimal | Bytes | Binary | Bytes |
|---|---|---|---|
| kB | 1,000 | KiB | 1,024 |
| MB | 1,000² | MiB | 1,024² |
| GB | 1,000³ | GiB | 1,024³ |
| TB | 1,000⁴ | TiB | 1,024⁴ |
| PB | 1,000⁵ | PiB | 1,024⁵ |
| EB | 1,000⁶ | EiB | 1,024⁶ |
| ZB | 1,000⁷ | ZiB | 1,024⁷ |
| YB | 1,000⁸ | YiB | 1,024⁸ |
b denotes bits, B bytes. In an eight-bit-byte system, 3 KiB is 3 × 1024 = 3072 bytes or 24576 bits. Do not mix 1000-based and 1024-based multipliers within one conversion.
4.4 Conversions
An implicit conversion follows language rules, for example assigning an int to a double. An explicit cast asks for conversion: (double)total. Conversion can lose range or precision; a cast does not make every conversion safe.
Program 3: Integer and floating division
#include <stdio.h>
int main(void) {
int total = 17, count = 4;
double first = total / count;
double second = (double)total / count;
printf("Integer result stored in double: %.2f\n", first);
printf("Floating division: %.2f\n", second);
printf("Truncated conversion: %d\n", (int)second);
return 0;
}
Input: None.
Expected output:
Integer result stored in double: 4.00
Floating division: 4.25
Truncated conversion: 4
| Expression | Operation performed | Result |
|---|---|---|
17 / 4 |
Integer division | 4 |
(double)(17 / 4) |
Integer division first, then conversion | 4.0 |
(double)17 / 4 |
Floating division | 4.25 |
(int)4.25 |
Truncation toward zero | 4 |
(int)-4.25 |
Truncation toward zero | -4 |
Small integer types undergo integer promotions in many expressions. Mixed signed/unsigned comparisons deserve attention; converting a negative operand to an unsigned type can change the result. For example, -1 < 1u is false because the comparison uses unsigned conversion rules. Avoid memorizing a universal “type hierarchy” without its signedness and rank rules.
5. Variables, constants, scope, lifetime and storage classes
5.1 Four related operations
| Term | Example | Meaning |
|---|---|---|
| Declaration | extern int total; |
Introduces information about a name/type |
| Definition | int total; inside a block |
Creates the object |
| Initialization | int total = 0; |
Supplies an initial value during definition |
| Assignment | total = 7; |
Stores a value into an existing modifiable object |
An uninitialized automatic integer has no usable initialized value. Do not read it expecting a meaningful “random number.” Objects with static storage duration receive default initialization when no explicit initializer is supplied.
5.2 Constants and macros
Fragment:
const int max_attempts = 4;
#define CAPACITY 80
A const object has a type and follows C scope rules. A macro is preprocessing substitution. A numeric literal is the value written directly in source. An enumeration constant is another way to supply an integer constant expression.
const object |
Object-like macro |
|---|---|
| Compiler type checking applies | Replacement tokens are processed before compilation |
| Has ordinary scope and storage duration | Remains defined until #undef or preprocessing ends |
| Can have an address | Macro itself is not an object |
| Not automatically an integer constant expression in C | Replacement may form a constant expression |
Do not put a semicolon in a simple numeric #define replacement unless it is intentionally part of the replacement. Do not attempt to change an originally const object through a casted pointer.
5.3 Scope versus lifetime versus linkage
- Scope: Where can a name be used in source code?
- Lifetime/storage duration: When does the object exist?
- Linkage: Can declarations in different scopes or files name the same entity?
| Scope | Applies to |
|---|---|
| Block | Names declared inside a block |
| File | Names declared outside functions, from declaration onward |
| Function | Labels |
| Prototype | Parameter names in a function prototype |
| Declaration context | Typical storage duration | Linkage |
|---|---|---|
| Ordinary local variable | Automatic | None |
Local static variable |
Static | None |
| Ordinary file-scope variable | Static | Usually external |
File-scope static variable |
Static | Internal |
| Allocated object | Allocated | Accessed through pointers, not linkage of a declared name |
extern can declare a definition supplied elsewhere; compile and link the source files together. register is a historical optimization hint, not a promise of a hardware register; in C17 its address cannot be taken with &. auto is the default storage-class spelling for ordinary local objects. static means different things at block and file scope, as the table shows. _Thread_local exists in C11/C17 but is outside this introductory guide's worked programs.
Program 4: Shadowing and retained local state
#include <stdio.h>
static void visit(void) {
static int count = 0;
++count;
printf("Visit %d\n", count);
}
int main(void) {
int value = 6;
{
int value = 14;
printf("Inner = %d\n", value);
}
printf("Outer = %d\n", value);
visit();
visit();
return 0;
}
Input: None.
Expected output:
Inner = 14
Outer = 6
Visit 1
Visit 2
Dry run: The inner value is a separate object. On leaving its block, the outer name becomes visible again. Each visit call finds the same retained count, so its initial zero is not reapplied on every call.
6. Input, output and formatting
6.1 Format reference
| Type/value | printf conversion |
scanf destination/conversion |
|---|---|---|
int |
%d |
int *, %d |
unsigned int |
%u |
unsigned int *, %u |
long |
%ld |
long *, %ld |
long long |
%lld |
long long *, %lld |
float |
%f after promotion |
float *, %f |
double |
%f |
double *, %lf |
long double |
%Lf |
long double *, %Lf |
| Character | %c with promoted integer |
char *, %c |
| Terminated string | %s |
Character buffer, bounded %s |
size_t |
%zu |
size_t *, %zu |
ptrdiff_t |
%td |
Appropriate pointer, %td |
| Object-pointer address | %p with (void *)p |
Avoid beginner use; input requires suitable void ** |
%x and %o display unsigned integers in hexadecimal and octal. In scanf, %i recognizes an integer base from its prefix; %d requests decimal.
printf("%.2f", x) controls decimal precision. %6d uses a minimum width of six characters, %-6d left-aligns, and %04d pads a suitable number with zeros. Width is a minimum, not a truncation limit. %% prints a literal percent sign.
6.2 Function differences
| Function | Action | Important detail |
|---|---|---|
printf |
Formatted output | Returns character count, or negative on failure |
puts |
String output | Adds a newline |
putchar |
Character output | Does not add a newline automatically |
scanf |
Formatted input | Returns successful assignment count, or EOF if input fails before assignment |
getchar |
Read one character | Returns int, preserving EOF |
fgets |
Bounded line input | Keeps newline if it fits; returns pointer or NULL |
A scalar input needs an address: scanf("%d", &count). An array name normally supplies a pointer to its first element: scanf("%19s", word) for char word[20]. The maximum field width leaves space for the terminating null character.
6.3 Whitespace and line input
%s skips leading whitespace and stops at the next whitespace. %c does not skip whitespace by default. A leading space in " %c" skips preceding whitespace.
A scanset such as "%19[^\n]" reads at most 19 characters other than newline. It can fail when the next character is already a newline. A leading space changes the behavior by discarding preceding whitespace, including blank lines.
fgets(buffer, capacity, stdin) reads at most capacity - 1 characters and appends \0 on success. Check its result before using the buffer. An absent newline can indicate an incomplete long line, so define whether to reject it or read the remainder. Do not use gets; it cannot enforce capacity and was removed from the C standard in C11.
Program 5: Numeric input followed by a full line
#include <stdio.h>
#include <string.h>
int main(void) {
int age, ch;
char name[40];
if (scanf("%d", &age) != 1 || age < 0 || age > 150) {
puts("Invalid age");
return 1;
}
/* This input contract puts the name on the NEXT line. */
while ((ch = getchar()) != '\n' && ch != EOF) { }
if (fgets(name, sizeof name, stdin) == NULL) {
puts("Missing name");
return 1;
}
if (strchr(name, '\n') == NULL && !feof(stdin)) {
puts("Name line too long");
return 1;
}
name[strcspn(name, "\n")] = '\0';
printf("%s is %d\n", name, age);
return 0;
}
Sample input:
19
Riya Sen
Expected output:
Riya Sen is 19
Other verified cases (⏎ marks a line break):
| Input | Expected output | Exit status |
|---|---|---|
abc ⏎ |
Invalid age ⏎ |
1 |
18 ⏎ |
Missing name ⏎ |
1 |
The program deliberately consumes the rest of the numeric-input line because its input contract places the name on the next line. Blindly inserting an extra getchar() is unreliable when there are extra spaces or characters. For stronger numeric parsing, read a line and use strtol with end-pointer, range and overflow checks.
Common mistakes: mismatched formats, forgetting scalar &, unbounded %s, reading a leftover newline with %c, passing nonterminated data to %s, and expecting fflush(stdin) to portably discard input.
7. Operators, precedence, evaluation and bit manipulation
7.1 Operator families
| Family | Operators | Example |
|---|---|---|
| Arithmetic | + - * / % |
remainder = n % 10 |
| Increment/decrement | ++ -- |
++count |
| Relational/equality | < <= > >= == != |
marks >= 40 |
| Logical | && || ! |
age >= 18 && valid |
| Bitwise | & | ^ ~ << >> |
flags & mask |
| Assignment | = and compound forms |
total += value |
| Conditional | ?: |
a > b ? a : b |
| Other | sizeof, casts, comma, ., ->, &, * |
p->marks |
= assigns; == compares. A comparison yields int zero or one. In conditions, zero is false and nonzero is true. Mathematical chained comparisons do not work as mathematical notation: write low < x && x < high, not low < x < high.
Integer division truncates toward zero; for example -17 / 4 is -4 and -17 % 4 is -1. The remainder obeys the corresponding division identity when the operations are valid. % does not accept floating operands; use fmod for floating remainder.
7.2 Precedence and associativity
Rows below are ordered highest to lowest. Operators in one row share precedence.
| Group | Operators | Associativity |
|---|---|---|
| Postfix | calls, [], ., ->, postfix ++ -- |
Left to right |
| Unary/cast | prefix ++ --, unary + -, ! ~, * &, sizeof, casts |
Right to left |
| Multiplicative | * / % |
Left to right |
| Additive | + - |
Left to right |
| Shift | << >> |
Left to right |
| Relational | < <= > >= |
Left to right |
| Equality | == != |
Left to right |
| Bitwise AND | & |
Left to right |
| Bitwise XOR | ^ |
Left to right |
| Bitwise OR | | |
Left to right |
| Logical AND | && |
Left to right |
| Logical OR | || |
Left to right |
| Conditional | ?: |
Right to left |
| Assignment | = += -= *= /= %= <<= >>= &= ^= |= |
Right to left |
| Comma | , |
Left to right |
Parentheses override grouping. Precedence and associativity describe parsing, not generally the order in which operands execute.
Worked examples:
2 + 3 * 4groups as2 + (3 * 4)and gives 14.20 / 5 * 2groups as(20 / 5) * 2and gives 8.a = b = 7groups asa = (b = 7).x & 1 == 0groups asx & (1 == 0); use(x & 1) == 0for the intended bit test.a + b / 2.0is not the average ofaandb; use(a + b) / 2.0.
7.3 Logic and short-circuiting
| A | B | A AND B | A OR B | NOT A |
|---|---|---|---|---|
| 0 | 0 | 0 | 0 | 1 |
| 0 | 1 | 0 | 1 | 1 |
| 1 | 0 | 0 | 1 | 0 |
| 1 | 1 | 1 | 1 | 0 |
&& evaluates its right operand only if the left is true. || evaluates its right operand only if the left is false. This supports p != NULL && *p > 0: the dereference is skipped for a null pointer. & and | are not substitutes for short-circuit logical operators.
7.4 Bitwise worked example
For the low four bits, take a = 10u (1010) and b = 6u (0110).
| Expression | Low-bit result | Decimal |
|---|---|---|
a & b |
0010 |
2 |
a | b |
1110 |
14 |
a ^ b |
1100 |
12 |
a >> 1 |
0101 |
5 |
a << 1 |
10100 |
20 |
~a complements the entire promoted type, not just four drawn bits. On a 32-bit unsigned type, ~10u is 4,294,967,285. State the assumed width if giving a numeric answer.
For unsigned flags and a valid bit position k, 1u << k creates a mask. Set using OR, clear using AND with the complemented mask, toggle using XOR, and test using AND. Keep the shift count nonnegative and smaller than the type's width. Signed shifts have additional restrictions; use unsigned types for these exercises.
Program 6: Flags and short-circuit evaluation
#include <stdio.h>
int main(void) {
unsigned int flags = 5u; /* binary 0101 */
flags |= 2u; /* set bit 1 */
flags &= ~4u; /* clear bit 2 */
flags ^= 1u; /* toggle bit 0 */
int changes = 0;
int skipped = 0 && ++changes;
int evaluated = 1 && ++changes;
printf("Flags = %u\n", flags);
printf("Results = %d %d, changes = %d\n", skipped, evaluated, changes);
return 0;
}
Input: None.
Expected output:
Flags = 2
Results = 0 1, changes = 1
7.5 Increment and expression validity
++x yields the updated value; x++ yields the previous value and also increments x. Do not infer that postfix changes only after the entire statement: the relevant concept is sequencing.
Defined fragment:
int x = 4;
int a = x++; /* a=4, x=5 */
int b = ++x; /* b=6, x=6 */
Intentional undefined-behavior examples in C17:
x = x++ + 1;
int y = x++ + ++x;
printf("%d %d", x++, x);
These are not puzzles with one portable numeric answer. Separate the updates into full expressions. A comma operator sequences its left operand before its right; commas separating arguments do not do that. Likewise &&, || and the condition of ?: provide relevant sequencing that ordinary + does not.
8. Decisions and original scenario practice
8.1 Syntax and selection rules
Syntax templates:
if (condition) {
/* true branch */
} else {
/* false branch */
}
if (highest_condition) {
/* first category */
} else if (next_condition) {
/* second category */
} else {
/* remaining category */
}
switch (integral_expression) {
case CONSTANT_1: /* work */ break;
case CONSTANT_2: /* work */ break;
default: /* work */ break;
}
An else belongs to the nearest unmatched if; braces remove ambiguity. An else if chain chooses the first true branch. Separate if statements can all execute.
switch uses an integral expression and distinct integer constant case values, including character constants. A case label is not a range condition. Without a jump such as break, execution can fall through into following cases. default handles no-match input but is optional. A break exits the switch, not an enclosing loop.
For a short value choice, condition ? true_value : false_value is an expression. Only the selected alternative is evaluated. Avoid deeply nested conditional expressions when a clear branch structure is easier to trace.
Program 7: Mandatory pass checks before classification
#include <stdio.h>
int main(void) {
int a, b, c;
if (scanf("%d%d%d", &a, &b, &c) != 3 ||
a < 0 || a > 100 || b < 0 || b > 100 || c < 0 || c > 100) {
puts("Invalid marks");
return 1;
}
if (a < 40 || b < 40 || c < 40) {
puts("Fail");
} else {
double average = (a + b + c) / 3.0;
char grade;
if (average >= 85) grade = 'A';
else if (average >= 70) grade = 'B';
else if (average >= 55) grade = 'C';
else grade = 'D';
printf("Average = %.2f, grade = %c\n", average, grade);
}
return 0;
}
Sample input:
90 80 85
Expected output:
Average = 85.00, grade = A
Other verified cases (⏎ marks a line break):
| Input | Expected output | Exit status |
|---|---|---|
100 100 39 |
Fail ⏎ |
0 |
40 40 40 |
Average = 40.00, grade = D ⏎ |
0 |
101 50 50 |
Invalid marks ⏎ |
1 |
Rule order: Validate the marks first, check mandatory subject-level passing next, then classify the average. A high average does not override a failed subject under these example rules.
Program 8: Switch-based calculator
#include <stdio.h>
int main(void) {
double a, b, answer;
char operation;
if (scanf("%lf %c %lf", &a, &operation, &b) != 3) return 1;
switch (operation) {
case '+': answer = a + b; break;
case '-': answer = a - b; break;
case '*': answer = a * b; break;
case '/':
if (b == 0.0) { puts("Division by zero"); return 1; }
answer = a / b;
break;
default: puts("Unknown operation"); return 1;
}
printf("%.2f\n", answer);
return 0;
}
Sample input:
9 / 4
Expected output:
2.25
Other verified cases (⏎ marks a line break):
| Input | Expected output | Exit status |
|---|---|---|
3 * 7 |
21.00 ⏎ |
0 |
9 / 0 |
Division by zero ⏎ |
1 |
1 ? 2 |
Unknown operation ⏎ |
1 |
Program 9: Progressive slabs with a final surcharge
#include <stdio.h>
int main(void) {
int units;
if (scanf("%d", &units) != 1 || units < 0 || units > 100000) {
puts("Invalid units"); return 1;
}
double charge;
if (units <= 50) charge = units * 2.0;
else if (units <= 150) charge = 100.0 + (units - 50) * 3.0;
else charge = 400.0 + (units - 150) * 4.0;
if (charge > 800.0) charge *= 1.10;
charge += 30.0;
printf("Bill = %.2f\n", charge);
return 0;
}
Sample input:
200
Expected output:
Bill = 630.00
Other verified cases (⏎ marks a line break):
| Input | Expected output | Exit status |
|---|---|---|
50 |
Bill = 130.00 ⏎ |
0 |
250 |
Bill = 830.00 ⏎ |
0 |
300 |
Bill = 1130.00 ⏎ |
0 |
-1 |
Invalid units ⏎ |
1 |
Billing dry run for 200 units: First 50 cost 100; next 100 cost 300; remaining 50 cost 200. Subtotal is 600, so no surcharge applies. Adding the fixed 30 produces 630. Applying the final slab's rate to all 200 units would be wrong.
8.2 Ten short solved scenarios
These are new learning exercises with explicitly chosen rules. They do not reproduce the website's test bank or represent real financial/administrative policies. All named inputs below have already been validated.
| Scenario and rules | Core solution | Worked check |
|---|---|---|
| Scholarship: full if marks ≥88 and income ≤240000; partial if marks ≥72 and income ≤400000 | Test full first, then partial, otherwise none | 80 and 200000 → partial |
| Withdrawal: positive multiple of 200, ≤10000, leaving ≥300 | amount > 0 && amount % 200 == 0 && amount <= 10000 && amount <= balance - 300 |
Balance 2300, amount 2000 → allowed |
| Bonus: 15% if experience ≥6 and rating ≥8; 8% if experience ≥3 and rating ≥6; otherwise zero | Ordered branch, then bonus = salary * rate |
30000, 4, 7 → 2400 |
| Permissions: read bit 0, write bit 1, execute bit 2 | Test each (flags & (1u << k)) != 0 |
Flags 5 → read and execute |
| Wallet: cashback 4% for purchases ≥1000, otherwise 1%; reject unaffordable/nonpositive purchase | new_balance = balance - purchase + purchase * rate |
5000, 1500 → 3560 |
| Packet: bits 0–2 all required; bit 4 must be clear | (status & 7u) == 7u && (status & 16u) == 0 |
15 → accepted; 23 → rejected |
| Shopping: base 10% at ≥3000, otherwise zero; member adds 5 percentage points; coupon subtracts 100 afterwards | final = amount * (1 - rate) - coupon_value; floor at zero |
4000, member, coupon → 3300 |
| Temperature: emergency at ≥90; shutdown overrides it when cooling is off and temperature ≥70 | Test shutdown first; then ordinary bands | 95 with cooling off → shutdown |
| Loan exercise: age 23–58 and salary ≥35000 required; score ≥760 preferred, ≥680 standard | Validate eligibility before score band | 30, 40000, 700 → standard |
| Data plan: business overrides usage; otherwise ≤3 basic, ≤12 standard, above 12 heavy | Test customer type before usage bands | 2 GB business → business |
For decimal measurements, express intervals using inequalities rather than integer-only labels with gaps. For percentage adjustments, specify whether they add percentage points or apply successively: 20% then 5% gives 0.80 × 0.95 = 0.76 of the price, not 0.75.
9. Loops, tracing, patterns and numeric problems
9.1 Syntax
for (initialization; condition; update) {
/* body */
}
while (condition) {
/* body and relevant state update */
}
do {
/* body */
} while (condition);
for and while may execute zero times. do executes its body before testing, so it executes at least once when reached. The final semicolon in do ... while is required.
A for executes initialization once, then condition, body and update repeatedly. Missing condition in for (;;) behaves as true. An intentional service loop needs an external stopping policy or explicit exit.
| Statement | Effect |
|---|---|
break |
Exit nearest loop or switch |
continue in for |
Skip remaining body and proceed to update |
continue in while |
Skip remaining body and retest condition |
continue in do |
Proceed to bottom condition |
return |
Leave the function |
goto label |
Transfer to a label in the same function |
Program 10: All three loops and control statements
#include <stdio.h>
int main(void) {
for (int i = 1; i <= 5; ++i) {
if (i == 2) continue;
if (i == 5) break;
printf("%d ", i);
}
putchar('\n');
int remaining = 3;
while (remaining > 0) {
printf("%d ", remaining);
--remaining;
}
putchar('\n');
int value = 0;
do {
printf("Once: %d\n", value);
} while (value != 0);
return 0;
}
Input: None.
Expected output:
1 3 4
3 2 1
Once: 0
First-loop dry run:
i |
Action | Printed |
|---|---|---|
| 1 | Neither condition matches | 1 |
| 2 | continue |
Nothing |
| 3 | Ordinary body | 3 |
| 4 | Ordinary body | 4 |
| 5 | break |
Nothing; loop ends |
An accidental semicolon after while(condition); creates an empty loop body. A counter increment placed after a continue can be skipped forever.
9.2 Nested loops
Program 11: Nested-loop pattern
#include <stdio.h>
int main(void) {
for (int row = 1; row <= 4; ++row) {
for (int column = 1; column <= row; ++column) putchar('*');
putchar('\n');
}
return 0;
}
Input: None.
Expected output:
*
**
***
****
The inner iteration count is 1, 2, 3 and 4, so ten stars are printed in total. It is not always correct to multiply the outer limit by one fixed inner limit: first inspect whether the inner limit depends on the outer counter.
9.3 Numeric loops
Program 12: Digit sum and primality
#include <stdio.h>
static int is_prime(int n) {
if (n < 2) return 0;
for (int divisor = 2; divisor <= n / divisor; ++divisor) {
if (n % divisor == 0) return 0;
}
return 1;
}
int main(void) {
int n;
if (scanf("%d", &n) != 1 || n < 0 || n > 1000000) return 1;
int copy = n, sum = 0;
do {
sum += copy % 10;
copy /= 10;
} while (copy != 0);
printf("Digit sum = %d\nPrime = %s\n", sum, is_prime(n) ? "yes" : "no");
return 0;
}
Sample input:
137
Expected output:
Digit sum = 11
Prime = yes
Other verified cases (⏎ marks a line break):
| Input | Expected output | Exit status |
|---|---|---|
0 |
Digit sum = 0 ⏎ Prime = no ⏎ |
0 |
49 |
Digit sum = 13 ⏎ Prime = no ⏎ |
0 |
For 137, the digit loop visits copies 137, 13 and 1, extracting 7, 3 and 1. The do form processes zero as one digit. The primality test rejects values below two and tries divisors only while divisor <= n / divisor, avoiding an overflowing divisor * divisor condition.
| Problem | Loop pattern | Important edge |
|---|---|---|
| Sum 1 through n | Accumulate each integer | n=0, result range |
| Digit sum | %10 then /10 |
Zero and negative-input policy |
| Number reversal | reverse = reverse*10 + digit |
Overflow and leading zeros |
| Factorial | Multiply 1 through n | 0! = 1, rapid growth |
| Prime test | Search for a divisor | 0 and 1 are not prime |
| Multiplication table | Multiply fixed value by loop counter | Required start/end |
A nonterminating loop is a possible logical error, but infinite-looking syntax can be intentional. For exam tracing, record each condition test, output and update separately.
10. Functions and recursion: added foundations
10.1 Declaration, definition and call
A function groups a task under a name. A declaration communicates its return and parameter types, a definition supplies its body, and a call executes it.
Syntax fragments:
int difference(int left, int right); /* prototype */
int difference(int left, int right) { /* definition */
return left - right;
}
/* Inside another function: */
int answer = difference(9, 4); /* call */
Parameters belong to the function; arguments are the expressions supplied by the caller. The function receives values, not the caller's variable names. A return value is separate from text printed by the function. Printing 5 does not return the integer 5 to the caller.
A function with no parameters should use (void) in the C17 style here. A function with no returned value uses return type void. A non-void function should provide a suitable value on each relevant return path. Functions can use a prototype before their later definition.
| Pattern | Example purpose |
|---|---|
| No parameters, no result | Display a fixed menu |
| Parameters, no result | Display a supplied record |
| No parameters, result | Obtain a stored configuration value |
| Parameters, result | Calculate a maximum or average |
10.2 Pass-by-value
Assigning a new value to an ordinary parameter does not change the caller's variable. To change an object in the caller, pass its address and dereference the copied pointer. To change the caller's pointer itself, pass the address of that pointer. Chapter 15 demonstrates both.
10.3 Recursion
A recursive function calls itself directly or indirectly. It needs a base case and progress toward that base case.
Program 13: Recursive factorial with a bounded domain
#include <stdio.h>
static unsigned long long factorial(unsigned int n) {
if (n <= 1u) return 1ull;
return n * factorial(n - 1u);
}
int main(void) {
unsigned int n;
if (scanf("%u", &n) != 1 || n > 20u) {
puts("Use an integer from 0 to 20"); return 1;
}
printf("%llu\n", factorial(n));
return 0;
}
Sample input:
5
Expected output:
120
Other verified cases (⏎ marks a line break):
| Input | Expected output | Exit status |
|---|---|---|
0 |
1 ⏎ |
0 |
20 |
2432902008176640000 ⏎ |
0 |
21 |
Use an integer from 0 to 20 ⏎ |
1 |
Call and return trace for 4:
| Call | Waits for | Returns |
|---|---|---|
factorial(4) |
4 * factorial(3) |
24 |
factorial(3) |
3 * factorial(2) |
6 |
factorial(2) |
2 * factorial(1) |
2 |
factorial(1) |
Base case | 1 |
Each active call has its own automatic parameters. Recursive calls consume additional execution resources; a missing base case can exhaust them. Iterative factorial uses a loop and accumulator instead. Neither recursion nor iteration automatically prevents arithmetic overflow. Restricting this example to 0–20 keeps its result within the minimum guaranteed range of unsigned long long.
11. Arrays, searching, sorting and matrices: added foundations
11.1 Definition and initialization
An array stores a fixed number of consecutive elements of one type. For int scores[5], valid indices are 0 through 4. C does not automatically make scores[5] a safe access.
Fragments:
int a[4] = {2, 4, 6, 8};
int b[4] = {2}; /* 2, 0, 0, 0 */
int c[] = {7, 9}; /* length inferred as 2 */
int zeros[4] = {0};
An ordinary uninitialized automatic array does not receive implicit zero initialization. Partial initialization, however, initializes the remaining elements to zero. Arrays cannot be assigned with a = b; copy elements or use a suitable library operation.
When the actual array is available, sizeof a / sizeof a[0] computes its element count. Once passed as an array parameter, it is adjusted to a pointer parameter, so pass the count separately.
11.2 Common operations
Program 14: Read, aggregate, search and reverse an array
#include <stdio.h>
int main(void) {
int values[20], n, target;
if (scanf("%d", &n) != 1 || n < 1 || n > 20) return 1;
long long sum = 0;
for (int i = 0; i < n; ++i) {
if (scanf("%d", &values[i]) != 1 || values[i] < -1000 || values[i] > 1000) return 1;
sum += values[i];
}
if (scanf("%d", &target) != 1) return 1;
int smallest = values[0], largest = values[0], found = -1;
for (int i = 0; i < n; ++i) {
if (values[i] < smallest) smallest = values[i];
if (values[i] > largest) largest = values[i];
if (found == -1 && values[i] == target) found = i;
}
printf("Sum=%lld Min=%d Max=%d Index=%d\n", sum, smallest, largest, found);
for (int left = 0, right = n - 1; left < right; ++left, --right) {
int temporary = values[left];
values[left] = values[right];
values[right] = temporary;
}
for (int i = 0; i < n; ++i) printf("%d%c", values[i], i + 1 == n ? '\n' : ' ');
return 0;
}
Sample input:
4
-3 8 2 8
8
Expected output:
Sum=15 Min=-3 Max=8 Index=1
8 2 8 -3
Other verified cases (⏎ marks a line break):
| Input | Expected output | Exit status |
|---|---|---|
1 ⏎ -9 ⏎ 2 |
Sum=-9 Min=-9 Max=-9 Index=-1 ⏎ -9 ⏎ |
0 |
0 |
(no output) |
1 |
The first line of input is the count, followed by that many elements and then a search target. The search retains the first matching index; -1 means absent. Initializing the extrema from the first element correctly handles all-negative arrays.
Reverse trace for [-3, 8, 2, 8]: Exchange positions 0 and 3 to get [8, 8, 2, -3]; exchange positions 1 and 2 to get [8, 2, 8, -3]. Stop when the cursors meet or cross.
A linear search checks elements sequentially and works without sorting. Binary search requires sorted input and repeatedly discards half the remaining interval; it is an additional topic to include only if required by your syllabus.
11.3 Bubble sort
Program 15: Bubble sort
#include <stdio.h>
int main(void) {
int values[] = {9, 2, 6, 2};
size_t n = sizeof values / sizeof values[0];
for (size_t pass = 0; pass + 1 < n; ++pass) {
int changed = 0;
for (size_t j = 0; j + 1 < n - pass; ++j) {
if (values[j] > values[j + 1]) {
int temporary = values[j];
values[j] = values[j + 1];
values[j + 1] = temporary;
changed = 1;
}
}
if (!changed) break;
}
for (size_t i = 0; i < n; ++i) printf("%d%c", values[i], i + 1 == n ? '\n' : ' ');
return 0;
}
Input: None.
Expected output:
2 2 6 9
Adjacent out-of-order values are exchanged. After one full pass, the largest remaining value has moved to the right end of the active portion.
Pass on [9,2,6,2] |
Result |
|---|---|
| First | [2,6,2,9] |
| Second | [2,2,6,9] |
| Third | No swaps; stop |
Worst-case comparisons grow quadratically with the number of values. The early-exit flag makes already sorted input cheaper. Swapping only when strictly greater preserves the order of equal elements.
11.4 Two-dimensional arrays
int matrix[2][3] is an array of two rows, each a three-integer array. matrix[r][c] accesses column c of row r. C stores array elements consecutively, and rows follow one another in row-major order.
Program 16: Row-aware matrix traversal and transpose
#include <stdio.h>
int main(void) {
int matrix[2][3] = {{2, 4, 6}, {1, 3, 5}};
int (*row)[3] = matrix;
for (size_t r = 0; r < 2; ++r) {
int sum = 0;
for (size_t c = 0; c < 3; ++c) sum += row[r][c];
printf("Row %zu sum = %d\n", r, sum);
}
puts("Transpose:");
for (size_t c = 0; c < 3; ++c) {
for (size_t r = 0; r < 2; ++r) printf("%d%c", row[r][c], r == 1 ? '\n' : ' ');
}
return 0;
}
Input: None.
Expected output:
Row 0 sum = 12
Row 1 sum = 9
Transpose:
2 1
4 3
6 5
A pointer to a row has type int (*)[3]; incrementing it advances one entire row. Keep traversal within each row's bounds rather than advancing an int * beyond its originating row and assuming that all such accesses are portable.
Matrix addition requires equal dimensions and adds corresponding entries. A transpose changes rows into columns. Matrix multiplication is a different operation: dimensions r×k and k×c produce r×c, with each output a sum of k products. These last two formula descriptions are supplemental foundations, not additional website lessons.
12. C strings and standard string operations
12.1 Representation
A C string is a character sequence terminated by a null character. The terminator occupies storage but is not included in the string's length.
| Declaration | Storage and mutability |
|---|---|
char word[] = "cat"; |
Four-character modifiable array |
const char *p = "cat"; |
Pointer to a literal; do not modify its characters |
char word[8] = "cat"; |
Eight-element array, remaining elements zero-initialized |
char word[3] = "cat"; |
Three characters with no null terminator; not a valid terminated string |
An array can have capacity greater than its current string length. sizeof reports storage of the actual array; strlen counts characters up to the terminator. Applying strlen requires a valid terminated string.
Program 17: String length, copy and comparison
#include <stdio.h>
#include <string.h>
int main(void) {
char source[] = "C lab";
char copy[20];
size_t length = 0;
while (source[length] != '\0') ++length;
size_t i;
for (i = 0; source[i] != '\0'; ++i) copy[i] = source[i];
copy[i] = '\0';
printf("Length=%zu Size=%zu Copy=%s\n", length, sizeof source, copy);
printf("Equal=%d\n", strcmp(source, copy) == 0);
return 0;
}
Input: None.
Expected output:
Length=5 Size=6 Copy=C lab
Equal=1
12.2 Library functions
Include <string.h>.
| Function form | Meaning | Requirement or trap |
|---|---|---|
strlen(s) |
Length, type size_t |
Does not include \0 |
strcpy(dst, src) |
Copy including terminator | Destination must fit; no overlapping strings |
strncpy(dst, src, n) |
Copy/pad up to n characters |
May not terminate when source is long |
strcat(dst, src) |
Append source | Destination must already be terminated and have space |
strcmp(a, b) |
Lexicographic comparison | Use negative/zero/positive, not necessarily −1/0/1 |
strchr(s, ch) |
First matching character | Returns pointer or NULL |
strstr(s, pattern) |
First matching substring | Empty pattern matches at start |
memcpy(dst, src, n) |
Copy bytes | Regions must not overlap |
memmove(dst, src, n) |
Copy bytes with overlap permitted | Both ranges still need sufficient valid storage |
a == b on character pointers compares addresses, not string content. To test equal strings, use strcmp(a, b) == 0.
Capacity calculation: Appending a string of length m to one of length n requires at least n + m + 1 characters of destination capacity. The extra one is not optional.
13. Worked string algorithms
These examples operate on byte-oriented C strings. They do not implement Unicode grapheme processing. State whether a problem is case-sensitive, whether punctuation counts and what qualifies as a word separator.
13.1 Longest word
Program 18: Longest word, including the final word
#include <ctype.h>
#include <stdio.h>
#include <string.h>
int main(void) {
char text[160];
if (fgets(text, sizeof text, stdin) == NULL) return 1;
if (strchr(text, '\n') == NULL && !feof(stdin)) return 1;
size_t start = 0, length = 0, best_start = 0, best_length = 0;
for (size_t i = 0; ; ++i) {
if (text[i] == '\0' || isspace((unsigned char)text[i])) {
if (length > best_length) {
best_start = start;
best_length = length;
}
length = 0;
if (text[i] == '\0') break;
} else {
if (length == 0) start = i;
++length;
}
}
if (best_length == 0) puts("No word");
else printf("%.*s (%zu)\n", (int)best_length, text + best_start, best_length);
return 0;
}
Sample input:
we build compilers
Expected output:
compilers (9)
Other verified cases (⏎ marks a line break):
| Input | Expected output | Exit status |
|---|---|---|
⏎ |
No word ⏎ |
0 |
cat dog ⏎ |
cat (3) ⏎ |
0 |
final |
final (5) ⏎ |
0 |
A word ends at whitespace or the terminating null character. Processing the terminator as a boundary prevents losing the final word. The strict greater-than update keeps the first of equally long words. The example uses isspace to recognize tabs and other whitespace, not just ordinary spaces.
| Input portion | Current length | Best word |
|---|---|---|
we |
2 at first boundary | we |
build |
5 at next boundary | build |
compilers |
9 at final boundary | compilers |
13.2 Remove duplicates and remove a selected character
Program 19: Stable duplicate removal and character filtering
#include <stdio.h>
static void unique_characters(char *text) {
char *write = text;
for (char *read = text; *read != '\0'; ++read) {
char *seen = text;
while (seen < write && *seen != *read) ++seen;
if (seen == write) *write++ = *read;
}
*write = '\0';
}
static void remove_character(char *text, char unwanted) {
char *write = text;
for (char *read = text; *read != '\0'; ++read) {
if (*read != unwanted) *write++ = *read;
}
*write = '\0';
}
int main(void) {
char first[] = "balloon";
char second[] = "banana";
char empty[] = "";
unique_characters(first);
remove_character(second, 'a');
unique_characters(empty);
printf("Unique=%s Filtered=%s Empty=[%s]\n", first, second, empty);
return 0;
}
Input: None.
Expected output:
Unique=balon Filtered=bnn Empty=[]
A read cursor examines input while a write cursor builds the retained prefix. The write cursor never moves ahead of unread input. Duplicate removal searches the already retained prefix, preserving first-occurrence order. Character filtering retains every character except the selected one. Both explicitly place the final terminator.
Duplicate removal here is quadratic in the worst case. Filtering is linear. A frequency/seen table can improve duplicate checking when the character domain is defined.
13.3 Frequency, first unique character and anagrams
Program 20: Character frequencies, first unique character and anagrams
#include <stdio.h>
#include <string.h>
static size_t frequency(const char *text, char target) {
size_t count = 0;
for (; *text != '\0'; ++text) if (*text == target) ++count;
return count;
}
static int anagram(const char *left, const char *right) {
if (strlen(left) != strlen(right)) return 0;
for (const char *p = left; *p != '\0'; ++p) {
if (frequency(left, *p) != frequency(right, *p)) return 0;
}
return 1;
}
int main(void) {
const char *text = "swiss";
const char *first_unique = NULL;
for (const char *p = text; *p != '\0'; ++p) {
if (frequency(text, *p) == 1) { first_unique = p; break; }
}
printf("s count=%zu\n", frequency(text, 's'));
if (first_unique != NULL) printf("First unique=%c\n", *first_unique);
printf("Anagrams=%d %d %d\n", anagram("angel", "glean"),
anagram("aab", "abb"), anagram("", ""));
return 0;
}
Input: None.
Expected output:
s count=3
First unique=w
Anagrams=1 0 1
The simple frequency helper makes the algorithm easy to trace without character-table assumptions. Two strings are anagrams under this example's rules when they have equal lengths and matching character counts; case and spaces count as ordinary characters.
This approach is quadratic. A frequency table can make bounded-alphabet counting linear, but a possibly signed char must be converted appropriately before indexing. A 256-entry table assumes an eight-bit character domain. Be explicit about that assumption.
13.4 Substrings and rotations
Program 21: Substring search and string rotations
#include <stdio.h>
#include <string.h>
static const char *find_text(const char *text, const char *pattern) {
if (*pattern == '\0') return text;
for (; *text != '\0'; ++text) {
const char *a = text, *b = pattern;
while (*a != '\0' && *b != '\0' && *a == *b) { ++a; ++b; }
if (*b == '\0') return text;
}
return NULL;
}
static int rotation(const char *a, const char *b) {
size_t n = strlen(a);
if (n != strlen(b)) return 0;
if (n == 0) return 1;
for (size_t start = 0; start < n; ++start) {
size_t i = 0;
while (i < n && a[(start + i) % n] == b[i]) ++i;
if (i == n) return 1;
}
return 0;
}
int main(void) {
const char *found = find_text("data structures", "struct");
printf("Found=%s\n", found != NULL ? found : "none");
printf("Empty match=%d\n", find_text("", "") != NULL);
printf("Rotation=%d %d %d\n", rotation("WXYZ", "YZWX"),
rotation("WXYZ", "XYZ"), rotation("", ""));
return 0;
}
Input: None.
Expected output:
Found=structures
Empty match=1
Rotation=1 0 1
Substring search starts a fresh comparison at each candidate position. An empty pattern succeeds immediately. A failed partial comparison does not imply that later starts must fail.
For rotations, equal lengths are mandatory. The program tests each cyclic starting position and avoids a doubled temporary buffer. For modest exam strings, its quadratic method is clear. Another method searches for the second string in two concatenated copies of the first, but it needs enough destination space and the same equal-length check.
13.5 Word reversal and palindrome
Program 22: Reverse each word and test a palindrome
#include <ctype.h>
#include <stdio.h>
#include <string.h>
static void reverse_range(char *start, char *end) {
/* end is one past the range, including for an empty range. */
while (start < end) {
--end;
if (start >= end) break;
char temporary = *start;
*start++ = *end;
*end = temporary;
}
}
static void reverse_words(char *text) {
char *p = text;
while (*p != '\0') {
while (*p != '\0' && isspace((unsigned char)*p)) ++p;
char *start = p;
while (*p != '\0' && !isspace((unsigned char)*p)) ++p;
reverse_range(start, p);
}
}
static int palindrome(const char *text) {
size_t n = strlen(text);
for (size_t i = 0; i < n / 2; ++i) {
if (text[i] != text[n - 1 - i]) return 0;
}
return 1;
}
int main(void) {
char text[] = " code well ";
char empty[] = "";
reverse_words(text);
reverse_words(empty);
printf("[%s]\n", text);
printf("Palindrome=%d %d %d\n", palindrome("level"), palindrome("Level"), palindrome(""));
return 0;
}
Input: None.
Expected output:
[ edoc llew ]
Palindrome=1 0 1
reverse_range receives an end pointer one past the selected range. It checks for an empty range before moving backward. This matters for empty strings and repeated separators.
Reversing each word preserves word order; reversing the entire sentence is a different task. A palindrome check compares opposite characters only until the middle. Here level passes, Level fails because comparison is case-sensitive, and the empty string passes the conventional definition.
13.6 Run-length encoding
Program 23: Run-length encoding with multi-digit counts
#include <stdio.h>
static void encode(const char *text) {
while (*text != '\0') {
char character = *text;
size_t count = 0;
do { ++count; ++text; } while (*text == character);
printf("%c%zu", character, count);
}
putchar('\n');
}
int main(void) {
encode("aaaaaaaaaaaabbc");
encode("");
return 0;
}
Input: None.
Expected output:
a12b2c1
Count a maximal run before printing its character and count. Printing counts numerically handles runs longer than nine. Adding '0' to a count only creates one decimal digit for counts 0–9.
Run-length encoding does not always make text shorter. If a stored output buffer is required, reserve enough capacity and define an unambiguous format when the input itself can contain digits. The example prints the learning representation directly rather than claiming to implement a general reversible compression format.
13.7 Test checklist for strings
Test empty input, a single character, no match, a match at the beginning/end, repeated characters, all separators, a final word without newline, equally long words and a long run. Never modify a string literal. Do not form text - 1 or an end pointer based on unsigned length - 1 before checking that the length permits it.
14. Pointers, addresses and pointer arithmetic
14.1 A pointer is a separate object
Fragment:
int score = 18;
int *p = &score;
| Expression | Meaning |
|---|---|
score |
The integer's value |
&score |
Address of the integer |
p |
Stored pointer value, identifying score |
*p |
The referred-to integer object |
&p |
Address of the pointer object |
An illustrative memory table can use invented addresses, but those are not output predictions:
| Object | Illustrative address | Stored value |
|---|---|---|
score |
1000 | 18 |
p |
2000 | Address 1000 |
Pointer size is not necessarily the size of its target. An int * can point to a four-byte integer while the pointer itself occupies eight bytes on a common 64-bit system.
14.2 Validity
A pointer must have an appropriate initialized value before access. Null means “no object/function target,” not a usable object at address zero. A dangling pointer no longer has a live target. An uninitialized automatic pointer is sometimes informally called wild.
A pointer being nonnull is not sufficient: bounds, lifetime, alignment and allowed type access also matter. Print object addresses using %p with (void *)p, not %d.
14.3 Arithmetic and array relationships
For a pointer within an array, adding one advances one element. If int is four bytes, that commonly corresponds to four addressable bytes, but element-based reasoning is more portable than assuming numeric addresses.
Allowed forms include p + n, p - n, increments/decrements and subtraction of appropriately related pointers. The result must stay within the array or one past it. One-past is suitable for comparison but not dereferencing. Pointer subtraction gives an element count of type ptrdiff_t; print it with %td.
Do not add two pointers, multiply pointers, or subtract pointers into unrelated arrays. Relational ordering is defined for relevant related objects, not as a general ordering of arbitrary allocations.
| Declaration | Meaning |
|---|---|
int *p; |
Pointer to integer |
int **p; |
Pointer to pointer to integer |
int *p[3]; |
Array of three integer pointers |
int (*p)[3]; |
Pointer to a three-integer array |
int (*p)(int, int); |
Pointer to a function taking two integers and returning one |
Array expressions usually convert to pointers to their first elements, with important exceptions such as operands of sizeof and unary &. Thus a and &a can denote the same starting address but have different types and different arithmetic strides.
Fragment:
int a[3] = {2, 5, 8};
int *element = a;
int (*whole)[3] = &a;
/* element + 1 advances one int; whole + 1 advances one entire array. */
14.4 Increment and dereference
| Expression | Interpretation |
|---|---|
*p++ |
*(p++): use old target, then advance pointer |
(*p)++ |
Increment the object, yield its old value |
*++p |
Advance pointer, access new target |
++*p |
Increment object, yield its new value |
*(p + 1) |
Access next element without changing p |
*p + 1 |
Add one to current object's value |
These descriptions assume valid bounds and modifiable objects where needed. They do not license combining multiple unsequenced modifications in one expression.
15. Pointers with functions, callbacks and returned pointers
Program 24: Pointer movement, mutation, swapping and double indirection
#include <stddef.h>
#include <stdio.h>
static void swap(int *a, int *b) {
int temporary = *a;
*a = *b;
*b = temporary;
}
static void retarget(int **slot, int *target) { *slot = target; }
int main(void) {
int values[] = {4, 7, 9};
int *p = values;
printf("A=%d\n", *p++);
printf("B=%d\n", (*p)++);
printf("C=%d\n", *++p);
printf("Offset=%td\n", p - values);
swap(&values[0], &values[2]);
retarget(&p, &values[1]);
printf("Array=%d %d %d, target=%d\n", values[0], values[1], values[2], *p);
return 0;
}
Input: None.
Expected output:
A=4
B=7
C=9
Offset=2
Array=9 8 4, target=8
Trace before the swap:
| Statement | Printed value | Pointer target afterwards | Array afterwards |
|---|---|---|---|
*p++ |
4 | Element 1 | [4,7,9] |
(*p)++ |
7 | Element 1 | [4,8,9] |
*++p |
9 | Element 2 | [4,8,9] |
swap receives copies of two addresses, so assignments through those addresses change the caller's elements. retarget receives the address of p, so assigning *slot changes the caller's pointer itself. Both are pass-by-value at the language level.
15.1 Returning pointers safely
A function may return a pointer to:
- Caller-owned storage that remains alive.
- Static-duration storage.
- A dynamic allocation whose ownership is transferred clearly.
It must not return the address of an automatic local object that ceases to exist on return.
Contrasting fragments:
/* Invalid result to dereference after return: */
int *bad(void) {
int value = 3;
return &value;
}
/* Storage remains alive, but all calls share the same object: */
int *retained(void) {
static int value = 3;
return &value;
}
Returning static storage is not equivalent to returning a fresh independent result. Returning an allocation creates a cleanup responsibility. Chapter 16's make_array demonstrates that contract.
15.2 Function pointers
Program 25: A callback and an array of function pointers
#include <stdio.h>
static int add(int a, int b) { return a + b; }
static int multiply(int a, int b) { return a * b; }
static int apply(int a, int b, int (*operation)(int, int)) {
return operation(a, b);
}
int main(void) {
int (*operations[2])(int, int) = {add, multiply};
printf("%d %d\n", apply(3, 8, operations[0]), apply(3, 8, operations[1]));
return 0;
}
Input: None.
Expected output:
11 24
The parentheses around *operation are essential. A callback passes behavior to another function. An array of function pointers can implement a small operation table after validating the selected index.
A clearer way to express a function returning a function pointer is a type alias:
/* Declaration fragment, assuming a compatible add function exists: */
typedef int (*BinaryOperation)(int, int);
BinaryOperation choose_operation(void) {
return add;
}
Do not call an uninitialized/null function pointer or one with an incompatible function type. Function pointers and generic object pointers are separate concepts; do not assume void * is a portable storage type for every function pointer.
16. Dynamic allocation, leaks, stack and heap
16.1 Allocation functions
Include <stdlib.h>.
| Call | Contract |
|---|---|
malloc(bytes) |
Allocate uninitialized storage or return NULL |
calloc(count, size) |
Allocate and set every bit to zero, or return NULL |
realloc(p, bytes) |
Resize an allocation, possibly moving it |
free(p) |
Release a suitable allocation; free(NULL) is allowed |
In C, the returned void * converts to an object-pointer type without a cast. Include the proper header; a cast is not a substitute for a declaration.
16.2 Size and initialization checks
For n elements, n * sizeof *p expresses the required size. Check multiplication before computing it: if n > SIZE_MAX / sizeof *p, multiplication would exceed the size type's maximum. Validate signed user input before converting it to size_t.
Uninitialized allocation contents are not valid initial integer data. Assign values before reading. calloc's all-bits-zero initializes integer storage to zero; it is not a universal guarantee of semantic zero for every possible pointer or floating representation.
Program 26: Allocate an array, read values and release storage
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
static int *make_array(size_t n) {
if (n == 0 || n > SIZE_MAX / sizeof(int)) return NULL;
return malloc(n * sizeof(int));
}
int main(void) {
int requested;
if (scanf("%d", &requested) != 1 || requested < 1 || requested > 1000) {
puts("Invalid count"); return 1;
}
size_t n = (size_t)requested;
int *values = make_array(n);
if (values == NULL) { puts("Allocation failed"); return 1; }
long long total = 0;
for (size_t i = 0; i < n; ++i) {
if (scanf("%d", &values[i]) != 1 || values[i] < -1000 || values[i] > 1000) {
free(values); puts("Invalid value"); return 1;
}
total += values[i];
}
int maximum = values[0];
for (size_t i = 1; i < n; ++i) if (values[i] > maximum) maximum = values[i];
printf("Total=%lld Maximum=%d\n", total, maximum);
free(values);
return 0;
}
Sample input:
3
-5 -2 -9
Expected output:
Total=-16 Maximum=-2
Other verified cases (⏎ marks a line break):
| Input | Expected output | Exit status |
|---|---|---|
0 |
Invalid count ⏎ |
1 |
2 ⏎ 3 x |
Invalid value ⏎ |
1 |
Ownership: make_array allocates; main becomes the owner and must free. An input error after allocation still reaches cleanup before returning. The count must be positive before initializing a maximum from element zero.
16.3 Resizing
Program 27: Zero initialization and safe resizing
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
int main(void) {
size_t old_count = 3, new_count = 5;
int *values = calloc(old_count, sizeof *values);
if (values == NULL) return 1;
printf("Initial=%d %d %d\n", values[0], values[1], values[2]);
for (size_t i = 0; i < old_count; ++i) values[i] = (int)i + 1;
if (new_count > SIZE_MAX / sizeof *values) { free(values); return 1; }
int *replacement = realloc(values, new_count * sizeof *values);
if (replacement == NULL) { free(values); return 1; }
values = replacement;
for (size_t i = old_count; i < new_count; ++i) values[i] = 0;
for (size_t i = 0; i < new_count; ++i) printf("%d%c", values[i], i + 1 == new_count ? '\n' : ' ');
free(values);
return 0;
}
Input: None.
Expected output:
Initial=0 0 0
1 2 3 0 0
For a positive requested size, a failed realloc leaves the original allocation intact. Therefore first store the returned value in a temporary pointer. Directly assigning p = realloc(p, bytes) can lose the original allocation if the result is null.
On success, use the returned pointer even if its address appears unchanged. Existing aliases into the old allocation must not be reused. Original data survives up to the lesser of old and new sizes; newly added storage is not automatically initialized by realloc. The program explicitly initializes its new elements.
Keep requested sizes positive in these examples; zero-size allocation/reallocation rules introduce version-dependent details that are not needed for the revision pattern.
16.4 Common mistakes
| Mistake | Consequence | Correction |
|---|---|---|
| Forget cleanup | Leak during program operation | Assign one clear owner |
| Overwrite only pointer | Allocation becomes unreachable | Release or retain the old allocation first |
| Free twice | Undefined behavior | Release each owned allocation once |
| Use after free | Undefined behavior | End all access after release |
| Free a local array | Invalid deallocation | Free only suitable allocated storage |
| Read before initializing | Indeterminate-data problem | Assign first |
| Access past count | Undefined behavior | Maintain bounds |
Setting one pointer to NULL after freeing does not repair other aliases. Leaks are especially harmful in long-running loops or services. Sanitizers and leak-checking tools help, but ownership reasoning is still necessary.
16.5 Stack and heap terminology
| Common stack arrangement | Common heap arrangement |
|---|---|
| Automatic function-call storage | Dynamically allocated storage |
| Lifetime tied to execution scope/calls | Lifetime ends on deallocation |
| Often inexpensive management | Allocation management adds work |
| Deep recursion/large locals can exhaust resources | Allocation may fail; fragmentation can matter |
C defines storage duration and lifetime, not one mandatory stack/heap implementation. A pointer may itself be automatic, static or dynamically allocated. Its target can have a different duration. A static local variable is not an ordinary automatic stack variable merely because its declaration is inside a function.
17. Structures, unions, enumerations and type aliases
17.1 Structures
A structure groups named members that can have different types. Define its member layout, then declare objects of that type. The definition ends with a semicolon.
Use record.member for an object and pointer->member for a pointer. p->member is equivalent to (*p).member.
Program 28: Nested structures, record arrays and pointer parameters
#include <stdio.h>
typedef struct { int day, month, year; } Date;
typedef struct {
int roll;
char name[24];
double marks;
Date joined;
} Student;
static void add_marks(Student *student, double extra) {
student->marks += extra;
}
int main(void) {
Student students[] = {
{11, "Asha", 72.0, {1, 8, 2026}},
{12, "Dev", 86.5, {2, 8, 2026}},
{13, "Ira", 81.0, {1, 8, 2026}}
};
size_t n = sizeof students / sizeof students[0], best = 0;
add_marks(&students[0], 2.0);
for (size_t i = 1; i < n; ++i) {
if (students[i].marks > students[best].marks) best = i;
}
Student copy = students[best];
printf("Top: %s %.1f\n", copy.name, copy.marks);
printf("First: %.1f, joined %d/%d/%d\n", students[0].marks,
students[0].joined.day, students[0].joined.month, students[0].joined.year);
return 0;
}
Input: None.
Expected output:
Top: Dev 86.5
First: 74.0, joined 1/8/2026
The example includes a nested date, an array of records, a pointer parameter that modifies a record and a structure assignment that copies a record's value. Copying a structure containing pointer members copies the pointers, not independently allocated target objects.
For a character-array member, initialize it in the initializer or copy a fitting string; ordinary array assignment cannot later do student.name = "New". Passing a structure by value makes a copy; passing a pointer permits access to the original. Use const Student * for a read-only interface.
17.2 Structures versus arrays
| Structure | Array |
|---|---|
| Named members, potentially different types | Indexed elements of one type |
| Models one composite record | Models a sequence |
Access with . or -> |
Access with [] |
| Whole-object assignment permitted for compatible types | Whole-array assignment not permitted |
Padding can occur between members and at the end. Do not derive every structure size by adding member sizes. Likewise, comparing structures byte-for-byte with memcmp is not a general replacement for comparing their meaningful members because padding can differ.
17.3 Union and a tag
A union overlaps the storage of its members. It is useful when one record holds one of several alternative representations.
Program 29: A tagged union
#include <stdio.h>
enum Kind { COUNT, TEMPERATURE };
struct Value {
enum Kind kind;
union { int count; double temperature; } data;
};
static void show(const struct Value *value) {
switch (value->kind) {
case COUNT: printf("Count=%d\n", value->data.count); break;
case TEMPERATURE: printf("Temperature=%.1f\n", value->data.temperature); break;
}
}
int main(void) {
struct Value value = {.kind = COUNT, .data.count = 12};
show(&value);
value.kind = TEMPERATURE;
value.data.temperature = 24.5;
show(&value);
return 0;
}
Input: None.
Expected output:
Count=12
Temperature=24.5
The separate kind tells the program which member to interpret. Writing the temperature does not preserve an independent count value. Avoid assuming that reading some other member always produces a predictable meaningful conversion.
| Structure | Union |
|---|---|
| Distinct member storage | Overlapping member storage |
| Can preserve several member values simultaneously | Used for alternatives |
| Size accommodates all members plus padding | Size accommodates largest member, with alignment/padding |
17.4 Enumeration and typedef
Fragment:
enum Result { FAIL, PASS = 5, DISTINCTION };
/* FAIL=0, PASS=5, DISTINCTION=6 */
typedef unsigned long StudentId;
An enumeration supplies named integer constants. Implicit values start at zero and continue one higher than the previous value unless explicitly set. typedef gives a type another name; it does not allocate storage or create an object. Use meaningful names rather than replacing every type with obscure abbreviations.
18. Bit-fields
A bit-field declaration specifies a member width in bits, for example unsigned int mode : 3. Unsigned three-bit values range from zero through seven. One-bit unsigned flags hold zero or one.
Program 30: Small fields with explicit unsigned widths
#include <stdio.h>
struct Status {
unsigned int enabled : 1;
unsigned int mode : 3;
unsigned int failed : 1;
};
int main(void) {
struct Status state = {1, 5, 0};
printf("Enabled=%u Mode=%u Failed=%u\n", (unsigned int)state.enabled,
(unsigned int)state.mode, (unsigned int)state.failed);
return 0;
}
Input: None.
Expected output:
Enabled=1 Mode=5 Failed=0
The program casts narrow fields to unsigned int before printing with %u, making the intended argument type explicit after promotions.
Restrictions and layout facts:
- A field width must be an appropriate integer constant expression and fit its permitted underlying type.
- Standard portable choices include
_Bool,signed intandunsigned int; other permitted types can depend on the implementation. - You cannot take a bit-field's address or apply
sizeofto that individual field. - An unnamed zero-width field can force the next field into a new allocation unit.
- Allocation order, units and padding are implementation-dependent. The sum of widths is not a promise of the structure's byte size.
- Prefer unsigned fields when the desired range is nonnegative; plain
intfield signedness introduces implementation choices.
For hardware or network formats with externally specified bit positions, explicit masks and shifts often make the representation clearer than relying on compiler bit-field layout. A structure with five bits of named fields can still occupy several bytes.
19. Variable argument lists
A variadic function has an ordinary parameter part followed by ..., using the C17 conventions here. <stdarg.h> provides the traversal facilities.
| Facility | Purpose |
|---|---|
va_list |
Store traversal state |
va_start(args, last) |
Initialize after the last named parameter |
va_arg(args, type) |
Retrieve the next argument using its expected promoted type |
va_end(args) |
Finish traversal |
va_copy(dst, src) |
Create an independent copy of traversal state |
Program 31: Reading a counted variable argument list
#include <stdarg.h>
#include <stdio.h>
static int sum_ints(int count, ...) {
int total = 0;
va_list arguments;
va_start(arguments, count);
for (int i = 0; i < count; ++i) total += va_arg(arguments, int);
va_end(arguments);
return total;
}
int main(void) {
printf("%d %d\n", sum_ints(4, 2, 5, 8, 1), sum_ints(0));
return 0;
}
Input: None.
Expected output:
16 0
The caller and function agree that count nonnegative int arguments follow. The sum must fit in int; that is part of this small example's contract. Passing too few arguments or reading with the wrong type can cause undefined behavior. Too many supplied arguments are not magically discovered: the function reads only the count it was told to process.
Default promotions apply. Read a supplied float as double; small integer types undergo integer promotions. A maximum function must require a positive count before immediately reading its first argument. An average function must avoid dividing by zero.
Variadic functions do not provide a built-in runtime list of argument types. For a fixed-type collection, passing an array and its size is often clearer and easier to validate.
20. File handling, positioning and binary data
20.1 Streams and modes
FILE * refers to a library stream object. A file on disk and the pointer variable are different objects. Obtain a stream with fopen, check for failure, perform operations and close it.
| Mode | Must file already exist? | Existing contents | Permitted work |
|---|---|---|---|
r |
Yes | Preserved | Read |
w |
No | Truncated | Write |
a |
No | Preserved | Writes append at end |
r+ |
Yes | Preserved | Read/write |
w+ |
No | Truncated | Read/write |
a+ |
No | Preserved | Read and append writes |
Add b for binary mode, as in rb, wb or w+b. Text translation can matter on some platforms. Do not assume the initial reading position of every append-update implementation; explicitly position for the intended read.
20.2 Functions and return checks
| Function | Meaning | Result/check |
|---|---|---|
fopen(path, mode) |
Open stream | NULL on failure |
fclose(fp) |
Flush/close stream | Zero on success, EOF on failure |
fgetc(fp) |
Read character | int character value or EOF |
fputc(ch, fp) |
Write character | Character value or EOF |
fgets(buf, n, fp) |
Read bounded line | Pointer or NULL |
fputs(s, fp) |
Write string | Nonnegative on success, EOF on failure |
fprintf(fp, ...) |
Formatted output | Count or negative on error |
fscanf(fp, ...) |
Formatted input | Successful assignment count |
fread(p, size, n, fp) |
Read up to n objects |
Number of complete objects read |
fwrite(p, size, n, fp) |
Write up to n objects |
Number of complete objects written |
fseek(fp, offset, origin) |
Reposition | Zero on success |
ftell(fp) |
Report position information | long, -1L on failure |
rewind(fp) |
Return to beginning and clear error/EOF indicators | No returned error code |
feof(fp) |
Test EOF indicator | Nonzero after relevant EOF encounter |
ferror(fp) |
Test error indicator | Nonzero on stream error |
fputs does not automatically append a newline, unlike puts. A successful fgets retains a newline when it reads one. Formatted file input needs the same correct destination types and width limits as console input.
Program 32: Write, append and read a text file
#include <stdio.h>
int main(void) {
const char *path = "revision_demo.txt";
FILE *stream = fopen(path, "w");
if (stream == NULL) return 1;
if (fputs("C\n", stream) == EOF) { fclose(stream); return 1; }
if (fclose(stream) == EOF) return 1;
stream = fopen(path, "a");
if (stream == NULL) return 1;
if (fprintf(stream, "Score %d\n", 87) < 0) { fclose(stream); return 1; }
if (fclose(stream) == EOF) return 1;
stream = fopen(path, "r");
if (stream == NULL) return 1;
int ch;
while ((ch = fgetc(stream)) != EOF) putchar(ch);
int failed = ferror(stream);
if (fclose(stream) == EOF) failed = 1;
if (remove(path) != 0) failed = 1;
return failed ? 1 : 0;
}
Input: None.
Expected output:
C
Score 87
File side effect: This teaching program creates/truncates and then removes revision_demo.txt in its working directory. Run it in an exercise directory without a valuable file of that name. The other file examples use temporary streams.
20.3 EOF and update transitions
Incorrect pattern: while (!feof(fp)) checks a previous status, not whether the next read will succeed. Test the read itself and keep its return in int:
/* Fragment, with an already open valid input stream: */
int ch;
while ((ch = fgetc(fp)) != EOF) {
putchar(ch);
}
For an update stream, write → read requires an intervening successful flush or positioning operation. Read → write requires an intervening positioning operation unless the input operation encountered EOF. Flushing alone is not the general read-to-write solution. These distinctions are specified in WG14 N1570, §7.21.5.3.
20.4 Positioning and overwrite
Program 33: Binary update stream, overwrite and positioning
#include <stdio.h>
int main(void) {
FILE *stream = tmpfile(); /* update stream, binary mode */
if (stream == NULL) return 1;
if (fputs("PQRST", stream) == EOF) { fclose(stream); return 1; }
if (fseek(stream, 2L, SEEK_SET) != 0) { fclose(stream); return 1; }
if (fputc('X', stream) == EOF) { fclose(stream); return 1; }
long position = ftell(stream);
if (position == -1L) { fclose(stream); return 1; }
printf("Position=%ld\n", position);
if (fseek(stream, 0L, SEEK_SET) != 0) { fclose(stream); return 1; }
int ch;
while ((ch = fgetc(stream)) != EOF) putchar(ch);
putchar('\n');
int failed = ferror(stream);
if (fclose(stream) == EOF) failed = 1;
return failed ? 1 : 0;
}
Input: None.
Expected output:
Position=3
PQXST
The stream initially contains PQRST. Offset two points at R; writing X produces PQXST, with the position then three. Seeking to the beginning makes subsequent reading start at P.
SEEK_SET refers to the beginning; SEEK_CUR to the current position; SEEK_END to the end when supported. Arbitrary byte-offset arithmetic on text streams is not portable. For text positioning, retain a valid ftell value and follow the supported fseek rules, or use fgetpos/fsetpos. Binary streams suit byte-position demonstrations, but even they do not universally guarantee every end-relative seek.
20.5 Binary blocks
Program 34: Binary round trip of integer objects
#include <stdio.h>
int main(void) {
int written[] = {12, -7, 25}, read_back[3];
FILE *stream = tmpfile();
if (stream == NULL) return 1;
if (fwrite(written, sizeof written[0], 3, stream) != 3) { fclose(stream); return 1; }
if (fseek(stream, 0L, SEEK_SET) != 0) { fclose(stream); return 1; }
if (fread(read_back, sizeof read_back[0], 3, stream) != 3) { fclose(stream); return 1; }
printf("%d %d %d\n", read_back[0], read_back[1], read_back[2]);
return fclose(stream) == EOF ? 1 : 0;
}
Input: None.
Expected output:
12 -7 25
This round trip is within one execution environment. A dump of native int objects is not automatically portable across different integer widths, byte orders or representations. A portable external format must define how values are encoded.
For a short fread, inspect EOF/error conditions and do not use unread elements as if they had been populated. A partial read can occur even after a successful open.
20.6 Solved file traces
| Operations, assuming success | Result |
|---|---|
Write AB, close, reopen append, write CD |
Contents ABCD |
Open an existing ABCDE file with w |
Contents truncated immediately |
Write one\ntwo, rewind, successful sufficiently sized fgets |
Buffer contains one\n and terminator |
Write XYZ, rewind, read one character |
Character X; next read starts at Y |
Binary temporary stream: write JKLM, seek offset 1, write !, rewind/read |
J!LM |
| Read exactly the last existing character | EOF indicator need not be set yet; another read can encounter EOF |
Two fgetc(fp) calls as arguments to one printf |
Argument evaluation order is not a reliable reading order |
Split stateful input calls into separate statements when order matters. Distinguish this unspecified choice of call order from unsequenced modifications of a scalar in expressions such as i++ + ++i.
21. Standard library revision reference
A header provides declarations, types and macros used with library facilities. Include the right header instead of relying on an undeclared function or an unrelated header to happen to include it.
21.1 Main headers in this guide
| Header | Facilities to remember |
|---|---|
<stdio.h> |
Formatted I/O, characters, strings and streams |
<stdlib.h> |
Allocation, numeric conversion, random numbers, termination, searching/sorting |
<string.h> |
String comparison/copy/search and byte operations |
<math.h> |
Roots, powers, rounding, trigonometry and logarithms |
<ctype.h> |
Character classification and case conversion |
<time.h> |
Calendar time, broken-down time, formatting and processor time |
<stdarg.h> |
Variable argument lists |
<stddef.h> |
size_t, ptrdiff_t, NULL, offsetof |
<stdint.h> |
Integer types where supported and related limits such as SIZE_MAX |
<limits.h> |
Integer limits and CHAR_BIT |
<float.h> |
Floating-point limits/precision |
<stdbool.h> |
C17 bool, true, false conveniences |
<errno.h> |
Error indicator used by selected library calls |
21.2 General utilities
| Function | Purpose | Revision caution |
|---|---|---|
abs, labs, llabs |
Integer absolute value | Most negative value may have no positive counterpart in the result type |
atoi, atof |
Simple numeric conversion | Poor error reporting; prefer checked conversion when validation matters |
strtol, strtod |
Conversion with end-pointer support | Also check range/errno where applicable |
rand |
Pseudorandom integer | Not a security-quality random generator |
srand |
Initialize pseudorandom sequence | Seed once for a sequence, not repeatedly inside a tight loop |
exit |
Terminate normally | Prefer EXIT_SUCCESS/EXIT_FAILURE when appropriate |
qsort |
Sort using a comparator | Comparator must obey its contract; subtraction can overflow |
bsearch |
Search sorted data | Input must use the comparator's ordering |
For a checked integer conversion: set errno = 0, call strtol, confirm that at least one character was consumed, examine trailing characters, reject ERANGE and verify the desired destination range before conversion. scanf's conversion-count check alone is not a general solution for arbitrarily large numeric text.
21.3 Mathematical functions
| Function | Example | Result |
|---|---|---|
sqrt |
sqrt(49.0) |
7 |
pow |
pow(2.0, 5.0) |
32 |
floor |
floor(-1.2) |
−2 |
ceil |
ceil(-1.2) |
−1 |
fabs |
fabs(-3.5) |
3.5 |
fmod |
fmod(7.5, 2.0) |
1.5 |
sin, cos, tan |
Argument in radians | Trigonometric result |
log |
log(1.0) |
0, natural logarithm |
log10 |
log10(100.0) |
2 |
Check mathematical domains rather than relying on an invalid result. For integer powers, remember that ^ is XOR, not exponentiation. pow performs floating computation; it is not always the best choice for exact large integer arithmetic.
21.4 Character functions
isalpha, isdigit, isalnum, isspace, islower and isupper return zero for false and nonzero for true. Do not assume true is exactly one. tolower and toupper convert when applicable.
The argument must be EOF or representable as unsigned char. For a plain char value, use a conversion such as isspace((unsigned char)ch). Character classification and case conversion can be locale-dependent.
21.5 Time facilities
| Facility | Meaning |
|---|---|
time_t |
Type representing calendar time |
time(NULL) |
Get current calendar time, with failure possible |
localtime |
Convert to local broken-down time |
gmtime |
Convert to UTC broken-down time |
struct tm |
Components such as year, month, day and hour |
strftime |
Format time into a bounded buffer |
difftime |
Difference between two calendar times in seconds |
clock |
Processor time used, not necessarily wall-clock duration |
tm_year counts years since 1900; tm_mon uses 0–11; tm_mday starts at one. Check returned pointers before accessing members. Some conversion functions use shared internal storage, so copy the result if another call must not overwrite it. Runtime date/time output is intentionally not used as a fixed expected answer in this guide.
Program 35: Common library functions
#include <ctype.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void) {
char text[20] = "Lab";
strcat(text, " notes"); /* capacity is sufficient here */
printf("%s: %zu\n", text, strlen(text));
printf("Root=%.1f Power=%.0f Floor=%.0f Ceil=%.0f\n",
sqrt(81.0), pow(3.0, 4.0), floor(-2.3), ceil(-2.3));
printf("Upper=%c Digit=%d Absolute=%d\n", toupper((unsigned char)'q'),
isdigit((unsigned char)'8') != 0, abs(-13));
char *end;
long value = strtol("245", &end, 10);
printf("Converted=%ld Complete=%d\n", value, *end == '\0');
return 0;
}
Input: None.
Expected output:
Lab notes: 9
Root=9.0 Power=81 Floor=-3 Ceil=-2
Upper=Q Digit=1 Absolute=13
Converted=245 Complete=1
The conversion in this example uses fixed valid text. A user-input version should apply the full checks described above.
22. Preprocessing and coding conventions: added foundations
22.1 Directives
#include requests header contents; #define creates a macro; #undef removes a macro; #if, #ifdef, #ifndef, #else, #elif and #endif control which source is included before compilation.
A runtime if selects actions while the program runs. A preprocessing conditional selects which code participates in translation. Include guards prevent repeated header contents within a translation unit.
Header-template fragment:
#ifndef EXERCISE_UTILS_H
#define EXERCISE_UTILS_H
int calculate_total(int left, int right);
#endif
Angle-bracket and quoted includes use implementation-defined search rules; quoted form is commonly used for project headers and angle brackets for system headers. Do not describe either as an absolute filesystem guarantee.
Program 36: Preprocessing versus a function
#include <stdio.h>
#define LIMIT 5
#define SQUARE(x) ((x) * (x))
static int square(int x) { return x * x; }
int main(void) {
printf("Macro=%d Function=%d\n", SQUARE(2 + 3), square(2 + 3));
#if LIMIT >= 5
puts("Large mode");
#else
puts("Small mode");
#endif
return 0;
}
Input: None.
Expected output:
Macro=25 Function=25
Large mode
22.2 Macro traps
#define BAD_SQUARE(x) x*x expands BAD_SQUARE(2+3) into 2+3*2+3, giving 11. Parenthesizing both the parameter uses and the whole expression fixes that grouping problem.
However, SQUARE(i++) with a two-use macro evaluates the argument twice and causes an unsequenced-modification problem here. Parentheses do not fix repeated evaluation. A function or suitable inline function evaluates its argument once before entering the body.
22.3 Readable exam code
Use meaningful names, consistent indentation, declarations with deliberate initial values, braces around branches, focused functions and comments describing the reasoning. Avoid unnecessary global state. Keep input, processing and output easy to locate.
Good comments explain why a boundary matters, such as initializing a maximum from the first element to handle negative data. A comment that merely restates i++ as “increment i” rarely helps a reader understand the algorithm.
23. Errors, behavior categories and debugging
23.1 Error categories
| Category | Example | Repair approach |
|---|---|---|
| Syntax/constraint problem | Missing ;, undeclared identifier, incompatible call |
Read compiler diagnostic and fix declarations/syntax |
| Link problem | Called function declared but never defined | Supply/link the matching definition or library |
| Runtime failure | Allocation/open failure | Check result and handle failure |
| Undefined behavior | Out-of-bounds access, signed overflow | Remove invalid operation; no output should be assumed |
| Logic error | Wrong average formula or loop boundary | Trace intended rule against intermediate values |
A compiler accepting code does not prove its behavior is defined. A diagnostic can be a warning rather than a hard error, and implementations can accept extensions. “It printed the expected number once” is not evidence that an undefined expression has become valid.
23.2 Four outcomes to distinguish in output questions
| Category | What an answer should say |
|---|---|
| Defined, assumptions satisfied | Give output and explain the trace |
| Implementation-defined | Identify the documented implementation choice needed |
| Unspecified | Explain the allowed choice without asserting one unique result |
| Undefined behavior | State that no portable output is specified and identify why |
Examples: plain char signedness is an implementation choice; function-argument evaluation order is often unspecified; unsequenced conflicting scalar accesses can create undefined behavior. These are not synonyms.
23.3 Find and fix
| Faulty fragment | Problem | Suitable correction |
|---|---|---|
scanf("%d", n); |
Scalar value supplied where address is required | scanf("%d", &n) and check result |
scanf("%f", &d); with double d |
Wrong destination type | %lf |
printf("%d", sizeof n); |
Wrong format for size_t |
%zu |
if (a = b) when equality intended |
Assignment changes a |
if (a == b) |
average = a + b / 2; |
Wrong grouping, possibly integer division | (a + b) / 2.0, considering sum range |
i <= n for n-element zero-based array |
Access at index n | i < n |
return &local; |
Target expires after function return | Caller storage, static storage or owned allocation |
char *s = "cat"; s[0] = 'b'; |
Attempts to modify literal | char s[] = "cat"; |
p = realloc(p, bytes); |
Can lose old allocation on failure | Temporary pointer, positive size |
while (!feof(fp)) |
Checks old EOF state | Test read result |
23.4 Verification routine
Compile with useful warnings, start with a small hand-checkable input, trace state changes, then test boundaries. Use sanitizers to help detect invalid operations during execution. Still reason about behavior that may be unexercised or implementation-dependent.
For loop problems, test zero iterations and one iteration. For extrema, test equal and all-negative values. For strings, test empty and full-capacity boundaries. For allocation, examine every exit after ownership begins. For files, examine failure to open and incomplete reads.
24. Worked arithmetic practice
24.1 Four-digit construction and reversal
Program 37: Construct, inspect and reverse four digits
#include <stdio.h>
int main(void) {
int a, b, c, d;
if (scanf("%d%d%d%d", &a, &b, &c, &d) != 4 ||
a < 1 || a > 9 || b < 0 || b > 9 ||
c < 0 || c > 9 || d < 0 || d > 9) {
puts("Invalid digits");
return 1;
}
int number = 1000*a + 100*b + 10*c + d;
int reversed = 1000*d + 100*c + 10*b + a;
printf("Number = %d\nReverse = %04d\nEnd sum = %d\n",
number, reversed, a + d);
return 0;
}
Sample input:
6 2 0 4
Expected output:
Number = 6204
Reverse = 4026
End sum = 10
Other verified cases (⏎ marks a line break):
| Input | Expected output | Exit status |
|---|---|---|
1 2 0 0 |
Number = 1200 ⏎ Reverse = 0021 ⏎ End sum = 1 ⏎ |
0 |
0 1 2 3 |
Invalid digits ⏎ |
1 |
The arithmetic result of reversing 1200 is the integer 21; %04d displays it as 0021. Formatting preserves a four-column representation, not leading zeros inside an integer's value.
24.2 Convert a duration
Problem: Convert 800 nonnegative days to years, weeks and days, assuming 365 days per year and ignoring leap years.
Solution: years = 800 / 365 = 2; remaining days are 800 % 365 = 70; weeks are 70 / 7 = 10; final days are 70 % 7 = 0.
Fragment for valid nonnegative days:
int years = days / 365;
int remaining = days % 365;
int weeks = remaining / 7;
int final_days = remaining % 7;
This decomposes a count using a stated convention; it is not a calendar-date calculation.
24.3 Triangle angle
Problem: Two angles are 45 and 65 degrees. Find the third.
Solution: 180 - (45 + 65) = 70. Require both supplied angles positive and their sum below 180 for a nondegenerate plane triangle. If numeric inputs are not already bounded, avoid overflowing the sum during validation.
24.4 Simple and compound interest as arithmetic exercises
For principal P, percentage rate r and time t in the matching rate period:
- Simple interest:
P * r * t / 100.0. - Annual compound final amount:
P * pow(1 + r / 100.0, t). - Compound interest alone: final amount minus principal.
For P=2000, r=10, t=2, the annual compound amount is 2420 and interest is 420. A different compounding frequency needs a different formula; do not silently use these assumptions for every question.
24.5 Average and digit edges
For values 7, 8 and 10, (7 + 8 + 10) / 3.0 gives approximately 8.3333. Integer division gives 8. A wider or floating accumulator can prevent overflow when summing large inputs, but floating conversion can lose exactness for very large integers.
For the four-digit integer 5824, the first digit is n / 1000 = 5 and the last is n % 10 = 4, giving edge-digit sum 9. This fixed formula assumes a positive four-digit input.
25. Output prediction and validity practice
25.1 How to solve
- Confirm declarations, types, bounds and lifetime.
- Check whether any operation makes behavior undefined.
- Identify grouping using precedence and parentheses.
- Identify required sequencing rather than assuming left-to-right evaluation.
- Trace values and pointer targets separately.
- Apply formatting exactly, including spaces and newlines.
All following defined-output fragments assume the required headers are included and are placed inside main. Their wrapper includes <stdio.h>, <stddef.h>, <string.h> and <math.h>. They have been tested separately from the complete worked programs. Unusual but valid examples such as chained comparisons are teaching traps, not recommended style.
Output question 1
printf("%d", 3 + 4 * 2);
Answer:
11
Multiplication binds more tightly than addition.
Output question 2
printf("%d", 24 / 4 * 2);
Answer:
12
Equal-precedence multiplication/division group left to right.
Output question 3
printf("%.1f", (double)(9 / 2));
Answer:
4.0
Integer division occurs before the cast.
Output question 4
printf("%.1f", 9 / 2.0);
Answer:
4.5
A floating operand makes this floating division.
Output question 5
printf("%d %d", -17 / 4, -17 % 4);
Answer:
-4 -1
Division truncates toward zero; the remainder satisfies the division identity.
Output question 6
int x = 5; int a = x++; int b = ++x; printf("%d %d %d", a, b, x);
Answer:
5 7 7
Each update occurs in a separate full expression.
Output question 7
int x = 0; int y = 0 && ++x; printf("%d %d", x, y);
Answer:
0 0
The right operand is skipped.
Output question 8
int x = 0; int y = 1 || ++x; printf("%d %d", x, y);
Answer:
0 1
The left operand already makes OR true.
Output question 9
int x = 2; int y = (x++, x + 3); printf("%d %d", x, y);
Answer:
3 6
The comma operator sequences the increment before the addition.
Output question 10
int x = 3; if (x = 0) puts("A"); else puts("B");
Answer:
B
Assignment stores zero; zero tests false. This is valid syntax but commonly a mistake.
Output question 11
printf("%d", 2 < 3 < 1);
Answer:
0
The first comparison yields 1; 1 < 1 is false.
Output question 12
printf("%u %u %u", 10u & 6u, 10u | 6u, 10u ^ 6u);
Answer:
2 14 12
Compare the binary bits of 1010 and 0110.
Output question 13
printf("%u %u", 12u >> 2, 3u << 2);
Answer:
3 12
These valid unsigned shifts divide/multiply by powers of two here.
Output question 14
int x = 2; switch (x) { case 1: putchar('A'); break; case 2: putchar('B'); default: putchar('C'); }
Answer:
BC
Case 2 has no break, so control falls through to default.
Output question 15
int i = 4; while (i < 2) { ++i; } printf("%d", i);
Answer:
4
The initial condition is false.
Output question 16
int i = 4; do { ++i; } while (i < 2); printf("%d", i);
Answer:
5
The body executes before the first condition test.
Output question 17
for (int i = 0; i < 5; ++i) { if (i == 2) continue; printf("%d", i); }
Answer:
0134
Continue skips only the remaining body for i=2.
Output question 18
int sum = 0; for (int i = 1; i <= 3; ++i) for (int j = 1; j <= i; ++j) ++sum; printf("%d", sum);
Answer:
6
Inner counts are 1+2+3.
Output question 19
int a[4] = {7}; printf("%d %d", a[0], a[3]);
Answer:
7 0
The remaining elements are zero-initialized.
Output question 20
int a[] = {2, 6, 9}; printf("%zu", sizeof a / sizeof a[0]);
Answer:
3
This is an actual array, so its full size is available.
Output question 21
char s[] = "code"; printf("%zu %zu", strlen(s), sizeof s);
Answer:
4 5
Length excludes the terminator; array storage includes it.
Output question 22
printf("%d", strcmp("same", "same") == 0);
Answer:
1
Zero from strcmp means equal contents.
Output question 23
int a[] = {3, 8, 12}; int *p = a; printf("%d ", *p++); printf("%d", *p);
Answer:
3 8
The first expression uses the old target then advances p.
Output question 24
int a[] = {3, 8}; int *p = a; printf("%d ", (*p)++); printf("%d", a[0]);
Answer:
3 4
The pointer stays in place; the element changes.
Output question 25
int a[] = {3, 8}; int *p = a; printf("%d", *++p);
Answer:
8
Advance to the next element, then dereference.
Output question 26
int a[] = {2, 4, 8, 16}; printf("%td", &a[3] - &a[1]);
Answer:
2
Pointer difference is measured in elements.
Output question 27
int x = 3; int *p = &x; int **q = &p; **q = 9; printf("%d", x);
Answer:
9
Two dereferences reach the original integer.
Output question 28
int a[] = {2, 4, 8}; printf("%d", 1[a]);
Answer:
4
Subscript syntax is defined through pointer addition; unusual but valid.
Output question 29
int x = 7; { int x = 2; printf("%d ", x); } printf("%d", x);
Answer:
2 7
The inner declaration shadows rather than changes the outer object.
Output question 30
int x = 7; size_t n = sizeof(x++); (void)n; printf("%d", x);
Answer:
7
sizeof does not evaluate this ordinary non-VLA operand.
Output question 31
enum Level { LOW = 2, MEDIUM, HIGH = 9, TOP }; printf("%d %d", MEDIUM, TOP);
Answer:
3 10
Implicit enumeration values follow the preceding value.
Output question 32
struct Point { int x, y; }; struct Point a = {2, 3}, b = a; b.x = 9; printf("%d %d", a.x, b.x);
Answer:
2 9
These structures are independent value copies.
Output question 33
printf("%.0f %.0f", floor(-2.3), ceil(-2.3));
Answer:
-3 -2
Floor goes toward negative infinity, ceil toward positive infinity.
Output question 34
printf("%d", -1 < 1u);
Answer:
0
The signed value converts to unsigned for this comparison.
Output question 35
int x = 5; int y = 1 ? 10 : ++x; printf("%d %d", x, y);
Answer:
5 10
Only the chosen alternative is evaluated.
Output question 36
int a = 1, b = 2; a += b *= 3; printf("%d %d", a, b);
Answer:
7 6
The assignments group right to left and modify different objects.
25.2 Deliberately invalid or nonportable questions
Do not execute these to “discover the answer.” Classify them from their rules.
| Fragment/context | Classification | Explanation |
|---|---|---|
int i=1; printf("%d", i++ + ++i); |
Undefined | Conflicting unsequenced updates |
int i=1; printf("%d %d", i++, i); |
Undefined | Unsequenced modification and value use |
int i=1; i = i++ + 1; |
Undefined in C17 | Conflicting updates without required sequencing |
int a[2]={1,2}; printf("%d", a[2]); |
Undefined | Index outside valid elements |
int *p=NULL; printf("%d", *p); |
Undefined | Null dereference |
| Modify through a pointer to a string literal | Undefined | Literal storage is not modifiable |
| Read allocated object through a freed pointer | Undefined | Target lifetime has ended |
int n=1; printf("%p", n); |
Undefined | Argument does not match pointer conversion |
printf("%zu", sizeof(int)); |
Implementation-dependent numeric answer | Need the implementation's type size |
printf("%d", (char)255); |
Not one portable numeric answer | Depends on char range/signedness and conversion behavior |
| Two separate function calls as arguments, each prints a label | Unspecified relative call order in general | Do not assume left argument's call executes first |
int a[2]; int *p=a+2; |
Valid formation | One-past pointer is allowed; dereferencing it is not |
int a[2]; a++; |
Constraint violation requiring diagnostic | Array expression is not a modifiable pointer variable |
int x=3; int *p=&x; p=p+1; |
Valid one-past formation | Scalar object acts as length-one array for this arithmetic; do not dereference result |
25.3 Pointer declarations: self-check
| Declaration | Answer |
|---|---|
int *a[5] |
Five pointers to integers |
int (*a)[5] |
One pointer to a five-integer array |
int *f(void) |
Function returning pointer to integer |
int (*f)(void) |
Pointer to function returning integer |
const int *p |
Pointer through which the integer cannot be modified |
int *const p = &x |
Pointer itself cannot be reassigned; target can be modified through it |
const int *const p = &x |
Neither reassignment nor target modification is allowed through this declaration |
Read declarators outward from the identifier while respecting parentheses. The const descriptions concern what is allowed through that declaration; other valid aliases can have different permissions when the underlying object is not itself const.
26. Short-answer and comparison bank
These are model answer ingredients, not a promise of your teacher's marking scheme. For a brief question, give the definition and one distinguishing point. For a longer question, add syntax, a comparison or diagram, and a worked example.
| Question | Answer ingredients |
|---|---|
| What is an algorithm? | Finite, precise and executable steps solving a defined problem; identify input/output and termination. |
| Why write pseudocode? | Express logic independently of language syntax, making design and translation easier. |
| Algorithm versus flowchart? | Written procedure versus visual control-flow representation; both describe a solution. |
| What is structured programming? | Organization using blocks, functions, sequence, selection and repetition; improves reasoning and maintenance. |
| Why is C procedural? | A program is organized around procedures/functions acting on data. |
| Compiler versus linker? | Compiler translates/checks source; linker resolves definitions and combines objects/libraries. |
| Keyword versus identifier? | Reserved language word versus programmer-selected name subject to naming rules. |
| Declaration versus definition? | Declaration introduces type/name information; an object definition supplies the object. A definition is also a declaration. |
| Initialization versus assignment? | Initial value supplied during definition versus later storage into an existing object. |
const versus #define? |
Typed object with ordinary scope versus preprocessing replacement. |
| Scope versus lifetime? | Where the name can be used versus when the object exists. |
| Local static versus ordinary local? | Both can have block scope; static retains lifetime/value between calls, ordinary automatic object does not. |
| Implicit versus explicit conversion? | Conversion from language rules versus programmer-written cast; both can lose information. |
| Why can a floating average still be wrong? | If integer division happens before assignment to floating storage, the fraction is already discarded. |
| Logical versus bitwise AND? | && combines truth tests with short-circuiting; & combines individual integer bits. |
| Precedence versus evaluation order? | Grouping of expression syntax versus timing/order of operand execution; one does not generally establish the other. |
| Prefix versus postfix increment? | Both increment; prefix yields new value and postfix yields old value, subject to sequencing rules. |
if chain versus switch? |
Conditions/ranges versus discrete integral case values; switch can fall through. |
while versus do? |
Test before body, possibly zero executions, versus test after body, at least one execution when reached. |
break versus continue? |
Leave nearest loop/switch versus skip remaining current loop body. |
| Array versus pointer? | Array contains elements; pointer stores a reference. Array expressions often convert to pointers but the objects and types differ. |
| String length versus capacity? | Number of characters before \0 versus available array elements, which must also fit the terminator. |
| Does C have reference parameters? | It passes values. Passing pointer values enables modification of caller-owned objects through indirection. |
| Why not return a local address? | The automatic object's lifetime ends, leaving no valid target for later access. |
| Pointer-to-array versus array-of-pointers? | One pointer referring to a complete array versus several pointer elements; parentheses alter the declaration. |
malloc versus calloc? |
Byte-count uninitialized allocation versus count/size allocation with bits zeroed; both can fail and require ownership/cleanup. |
| Why a temporary for realloc? | A failed positive-size resize returns null while old allocation survives; preserve its pointer. |
| What is a leak? | Allocated storage retained without eventual useful ownership/cleanup, often after losing its reference. |
| What is a dangling pointer? | A pointer whose target's lifetime has ended. |
| Structure versus union? | Distinct storage for members versus shared storage for alternatives; both can contain padding. |
enum versus typedef? |
Named integer constants/type construct versus alias for an existing type. |
| What are bit-fields? | Integer structure/union members with declared bit widths and implementation-dependent layout. |
| Why does variadic code need a contract? | It cannot automatically infer the number or types of unnamed arguments. |
| Why is getchar/fgetc stored in int? | To retain character values and distinguish the EOF result. |
| Why not while(!feof(fp))? | It tests an indicator set by past operations, not whether the next read will succeed. |
| Text versus binary I/O? | Text mode can translate representations; binary mode supports raw representations, which are still not automatically portable between systems. |
| Compile-time versus logic error? | A translation problem versus a wrong algorithm/result even when translation and execution succeed. |
| What is undefined behavior? | The language supplies no requirements for the result of the invalid operation; a particular run's output is not a valid universal answer. |
26.1 Multiple-choice self-check
Choose an answer before reading the key.
- Which is the correct
scanfformat fordouble *? A%d· B%f· C%lf· D%c - Which loop executes its reached body at least once? A
for· Bwhile· Cdo/while· D none - For
char s[]="hi", what issizeof s? A 2 · B 3 · C pointer size · D unspecified - What does
strcmp(a,b)==0establish for valid strings? A same address · B same contents · C same capacity · D unequal - Which operation changes the pointed-to integer but not pointer
p? Ap++· Bp+1· C(*p)++· D&p - Which allocator zeroes the allocated bits? A
malloc· Bcalloc· Cfree· Dreallocalways - Which format prints
size_t? A%d· B%f· C%zu· D%p - What does opening an existing file with
wdo? A append · B preserve contents · C truncate · D necessarily fail - Which short-circuits? A
&· B|· C&&· D^ - Is
sizeof(int)universally four? A yes · B no - Which symbol accesses a structure member through a pointer? A
.· B->· C::· D& - Does a successful compilation prove no undefined behavior? A yes · B no
Key with reasons: 1 C, the destination is double *; 2 C, bottom test; 3 B, terminator included; 4 B, string comparison; 5 C, dereferenced object increment; 6 B, zeroed storage; 7 C, correct size type; 8 C, write mode truncates; 9 C, conditional evaluation; 10 B, implementation-dependent; 11 B, pointer member access; 12 B, many runtime requirements are not established by compilation.
27. Final revision sheets and mastery checklist
27.1 Essential distinctions
| Do not confuse | Correct distinction |
|---|---|
= / == |
Store / compare |
'A' / "A" |
Character constant / terminated string literal |
\0 / '0' |
Terminator value / digit character |
/ with ints / floating operands |
Truncated quotient / floating quotient |
& / && |
Bits / short-circuit truth test |
| / || |
Bits / short-circuit truth test |
^ / power |
XOR / use suitable multiplication or pow |
*p++ / (*p)++ |
Move pointer / change target |
sizeof array / sizeof pointer |
Entire available array object / pointer object |
strlen / sizeof |
String length / object/type size |
. / -> |
Direct object / pointer access |
r+ / w+ |
Preserve existing file / truncate or create |
EOF / feof |
Read result sentinel / stream status test |
static local / file-scope static |
Retained local object / internal linkage |
27.2 Program-writing checklist
- Identify input range, count and required output format.
- Include the correct headers and use the expected language version.
- Initialize accumulators, flags and indices deliberately.
- Validate input before using it; avoid invalid division or empty-array extrema.
- Use
< nfor n-element zero-based arrays. - Keep a string's terminating null character and capacity in mind.
- Distinguish pointer movement from target mutation.
- Track allocation ownership and cleanup on each applicable return path.
- Check file open/read/write/close results where relevant.
- Compute expected output independently before comparing with a run.
27.3 Can you do these without looking?
| Skill | Required demonstration |
|---|---|
| Problem design | Algorithm, pseudocode and flowchart for one branching problem |
| C fundamentals | Explain each part of a complete program and its build stages |
| Expressions | Solve precedence, conversion and short-circuit examples |
| Control flow | Write a threshold classifier, switch menu and nested loop |
| Functions | Define/call a function and explain a recursive trace |
| Arrays | Read, sum, search, reverse, sort and traverse a matrix |
| Strings | Explain termination, compare/copy, then implement two scanning algorithms |
| Pointers | Trace target/value changes and write a pointer-based swap |
| Memory | Allocate, initialize, resize and release with failure handling |
| Records | Build an array of structures and explain union alternatives |
| Advanced supplied topics | Explain bit-fields and a counted variadic function |
| Files | Write/append/read and explain EOF and update transitions |
| Debugging | Classify errors and reject undefined-output questions correctly |
27.4 Balanced revision order
First revise definitions, formats, syntax and the main programs. Next practise output prediction and dry runs without executing them. Then work through boundary cases and code correction. Keep installation commands and advanced portability notes as supporting reference rather than allowing them to displace basic coding practice.
There is no marks-based prioritization here because the actual exam blueprint has not been supplied. Check your syllabus before allocating study time to advanced topics such as callbacks, bit-fields or variadic functions.
28. Curriculum syllabus mapping and programming best practices
28.1 Structured Programming Concepts Syllabus Index
The following table maps the official Structured Programming Concepts (26CSE0105 / 26CSE0101) curriculum topics to their corresponding chapters in this study guide:
| Syllabus Module / Topic | Core Coverage Area | Chapter Reference |
|---|---|---|
| Problem Solving & Logic Development | Algorithms, pseudocode, flowchart symbols & structured programming | Chapter 1 |
| Introduction to C Programming | History, program anatomy, compilation process (gcc), tokens & identifiers |
Chapter 2, Chapter 3 |
| Data Types & Type Conversions | Primitive data types, byte sizes, units, type promotion & casting | Chapter 4 |
| Variables, Constants & Scope | Variable lifecycle, constants, block scope, lifetime & storage classes | Chapter 5 |
| Standard Console I/O | Formatted output (printf), input conversion (scanf) & escape sequences |
Chapter 6 |
| Operators & Expressions | Arithmetic, relational, logical, bitwise, conditional (?:) & precedence |
Chapter 7, Chapter 24 |
| Decision Making Statements | Two-way selection (if-else), multi-way branching & switch-case |
Chapter 8 |
| Iterative Loops & Patterns | while, do-while, for, nested loops, break, continue & pattern logic |
Chapter 9 |
| Functions & Recursion | Prototypes, parameters (pass-by-value), return types & recursive calls | Chapter 10 |
| Storage Classes | Memory lifetime & visibility (auto, static, extern, register) |
Chapter 5 |
| Arrays & Matrix Operations | 1D arrays, linear/binary searching, sorting & 2D matrix manipulation | Chapter 11 |
| Strings & String Library | Character arrays, string I/O, string.h library & custom algorithms |
Chapter 12, Chapter 13 |
| Pointers & Address Arithmetic | Pointer declarations, dereferencing, pointer arithmetic & arrays | Chapter 14 |
| Pointers with Functions | Pass-by-reference simulation, callback functions & returning pointers | Chapter 15 |
| Dynamic Memory Allocation | Heap vs. stack, malloc(), calloc(), realloc(), free() & memory leaks |
Chapter 16 |
| Structures & Unions | Record grouping, nested structures, array of structures & typedef |
Chapter 17 |
| Bit-Fields | Memory-optimized structures & hardware register bit manipulation | Chapter 18 |
| Variable Argument Lists | Variadic functions with <stdarg.h> (va_list, va_start, va_arg) |
Chapter 19 |
| File Handling & Streams | File streams, text/binary mode, fopen, fclose, fread, fwrite |
Chapter 20 |
| Standard Library Reference | Comprehensive utility reference (stdio.h, stdlib.h, math.h, ctype.h) |
Chapter 21 |
| Preprocessor Directives | Macros, #define, conditional compilation & header file guards |
Chapter 22 |
| Errors & Debugging | Syntax, runtime & logical errors, segmentation faults & debugging methods | Chapter 23 |
| Examination Question Bank | Output prediction, worked arithmetic, short answers & comparison tables | Chapters 24–27 |
28.2 Standard Best Practices & Examination Pitfalls
| Common Pitfall | Recommended Standard Practice |
|---|---|
| Omitting essential header inclusions | Always include declarations: <stdio.h> for I/O, <stdlib.h> for memory allocation, <string.h> for strings. |
Nonstandard main declarations |
Always declare the entry point for hosted systems as int main(void) and return integer exit codes (return 0;). |
| Incorrect format specifiers | Use %p with (void *) cast for addresses; use %zu for size_t values (e.g. sizeof). |
| Uninitialized variables | Explicitly initialize variables before evaluation; uninitialized local variables hold indeterminate garbage values. |
| Ties in comparison algorithms | Initialize candidate maximums/minimums to the first array element or candidate, then compare sequentially. |
| Unchecked memory allocation | Always verify that malloc, calloc, or realloc did not return NULL before dereferencing allocated memory. |
Unsafe realloc assignment |
Never assign directly ptr = realloc(ptr, ...); use a temporary pointer to prevent memory leaks if reallocation fails. |
| Unbounded input hazards | Avoid gets() (removed in modern C); use fgets() with an explicit buffer limit or bounded scanf("%s"). |
Storing EOF in a char |
getchar() and fgetc() return int so they can represent all character values plus the negative constant EOF. |
| Dangling pointers | After executing free(ptr);, immediately assign ptr = NULL; to guard against accidental use-after-free bugs. |
28.3 Language Standard References
The code examples in this guide conform to modern ISO C (C11/C17) standard practices (WG14 N1570 baseline), ensuring cross-platform portability and clean compilation under GCC and Clang (-Wall -Wextra -Wpedantic).