On this page

C Programming Notes

Structured Programming Concepts · Unit 1

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 (? :), and sizeof operators, along with complete operator precedence and associativity tables.
  • Control Flow & Decision Making: Two-way selection (if-else), multi-way branching (else-if ladders, 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

  1. Problem solving, algorithms, pseudocode and flowcharts
  2. Introduction to C, program structure and compilation
  3. Tokens, identifiers, literals and comments
  4. Data types, sizes, units and conversions
  5. Variables, constants, scope, lifetime and storage classes
  6. Input, output and formatting
  7. Operators, precedence, evaluation and bit manipulation
  8. Decisions and original scenario practice
  9. Loops, tracing, patterns and numeric problems
  10. Functions and recursion: added foundations
  11. Arrays, searching, sorting and matrices: added foundations
  12. C strings and standard string operations
  13. Worked string algorithms
  14. Pointers, addresses and pointer arithmetic
  15. Pointers with functions, callbacks and returned pointers
  16. Dynamic allocation, leaks, stack and heap
  17. Structures, unions, enumerations and type aliases
  18. Bit-fields
  19. Variable argument lists
  20. File handling, positioning and binary data
  21. Standard library revision reference
  22. Preprocessing and coding conventions: added foundations
  23. Errors, behavior categories and debugging
  24. Worked arithmetic practice
  25. Output prediction and validity practice
  26. Short-answer and comparison bank
  27. Final revision sheets and mastery checklist
  28. 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:

  1. Inputs: What values arrive, in what form and how many?
  2. Outputs: What must be calculated, displayed or stored?
  3. Constraints: What values are allowed? Can the collection be empty? Can values repeat?
  4. Processing: What formula, comparison or repetition connects input to output?
  5. 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. 08 is invalid.
  • Suffixes specify types or influence type selection: 25u, 25L, 25LL.
  • Unsuffixed floating literals are normally double: 2.5; use 2.5f for float.
  • An ordinary character constant such as 'A' has type int in C, even though it commonly initializes a char.
  • "A" is an array containing 'A' and '\0', not the same thing as 'A'.
  • -5 is unary minus applied to the constant 5.

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 * 4 groups as 2 + (3 * 4) and gives 14.
  • 20 / 5 * 2 groups as (20 / 5) * 2 and gives 8.
  • a = b = 7 groups as a = (b = 7).
  • x & 1 == 0 groups as x & (1 == 0); use (x & 1) == 0 for the intended bit test.
  • a + b / 2.0 is not the average of a and b; 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 int and unsigned int; other permitted types can depend on the implementation.
  • You cannot take a bit-field's address or apply sizeof to 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 int field 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

  1. Confirm declarations, types, bounds and lifetime.
  2. Check whether any operation makes behavior undefined.
  3. Identify grouping using precedence and parentheses.
  4. Identify required sequencing rather than assuming left-to-right evaluation.
  5. Trace values and pointer targets separately.
  6. 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.

  1. Which is the correct scanf format for double *? A %d · B %f · C %lf · D %c
  2. Which loop executes its reached body at least once? A for · B while · C do/while · D none
  3. For char s[]="hi", what is sizeof s? A 2 · B 3 · C pointer size · D unspecified
  4. What does strcmp(a,b)==0 establish for valid strings? A same address · B same contents · C same capacity · D unequal
  5. Which operation changes the pointed-to integer but not pointer p? A p++ · B p+1 · C (*p)++ · D &p
  6. Which allocator zeroes the allocated bits? A malloc · B calloc · C free · D realloc always
  7. Which format prints size_t? A %d · B %f · C %zu · D %p
  8. What does opening an existing file with w do? A append · B preserve contents · C truncate · D necessarily fail
  9. Which short-circuits? A & · B | · C && · D ^
  10. Is sizeof(int) universally four? A yes · B no
  11. Which symbol accesses a structure member through a pointer? A . · B -> · C :: · D &
  12. 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 < n for 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).

Saved Bookmarks

0

Request or Upload Content

Send missing topics, notes, question banks, or corrections directly to Sehjal

Click to upload or drag and drop
PDF, PNG, JPG, DOCX, ZIP (Up to 10MB)

Submitting opens FormSubmit's confirmation or error page. You can return to StudyDump with your browser Back button.