CoreTechED

Loading

Master C fundamentals with practice problems: variables, data types, I/O, operators, conditionals, and loops. Full C99 code with edge cases.

C Fundamental Practice Problems: Complete Guide

C Fundamental Practice Problems: A Comprehensive Tutorial

Learning C is not about memorizing syntax—it’s about building muscle memory through practice. This tutorial is a structured practice guide covering the fundamental concepts every C programmer must master: variables, data types, input/output, format specifiers, operators, conditional statements, and loops.

Each section explains the concept, then walks through a representative practice problem with a complete, portable C99/C11 solution and expected output.


Conceptual Introduction & Fundamentals

Why Practice Fundamentals?

Fundamentals are the foundation of every C program, whether it’s 20 lines or 20,000. Weak fundamentals show up as:

  • Off-by-one errors in loops.
  • Format specifier mismatches causing undefined behavior.
  • Buffer overflows from unsafe input functions.
  • Silent type conversion bugs in arithmetic.
  • Incorrect edge-case handling in conditionals.

The Fundamental Topics

TopicPurpose
VariablesNamed storage for values
Data TypesSize and interpretation of values
Input / OutputCommunicating with the user
Format SpecifiersControlling how data is printed or read
OperatorsArithmetic, relational, logical, bitwise
Conditional StatementsBranching logic (if, switch)
LoopsRepetition (for, while, do-while)

Difficulty Progression

  • Easy: Printing, variables, simple arithmetic, I/O.
  • Medium: Control flow, digit manipulation, number theory basics.

Syntax & Core Mechanics

1. Variables and Data Types

c

int age = 25; double price = 19.99; char grade = 'A';

Use int for whole numbers, double for decimals, char for single characters. Prefer double over float for computation; use long long when values may exceed int.

2. Input with fgets and Parsing

Never use scanf("%s", ...) or gets for strings. Prefer fgets plus strtol/strtod for robust parsing.

c

char buffer[64]; if (fgets(buffer, sizeof(buffer), stdin) == NULL) return 1; int value = (int)strtol(buffer, NULL, 10);

3. Output with printf

Match the specifier to the type:

TypeSpecifier
int%d
unsigned int%u
long long%lld
double%f, %lf, %g
char%c
String%s
size_t%zu
Pointer%p

4. Operators

c

int a = 7, b = 3; int q = a / b; /* 2 — integer division */ int r = a % b; /* 1 */ double f = (double)a / b; /* 2.333... */

Use casting to force floating-point division.

5. Conditionals and Loops

c

if (a > b) { /* ... */ } else if (a == b) { /* ... */ } else { /* ... */ } for (int i = 0; i < n; i++) { /* ... */ } while (cond) { /* ... */ } do { /* ... */ } while (cond);

Full Practical Code Example with Explanation

Below are eight representative problems covering the fundamentals, each a self-contained C99 program with verified output.

Problem 1: Print “GeeksforGeeks” with Quotes

Goal: Print a string including literal double quotes.

c

#include <stdio.h> int main(void) { /* Escape double quotes with backslash */ printf("\"GeeksforGeeks\"\n"); return 0; }

Expected output:

text

"GeeksforGeeks"

Notes: \" inside a string literal produces a literal ".

Problem 2: Swap Two Numbers (Without Temp for Integers)

Goal: Swap a and b.

c

#include <stdio.h> int main(void) { int a = 5, b = 7; /* Arithmetic swap (works for ints; watch overflow) */ a = a + b; b = a - b; a = a - b; printf("a = %d, b = %d\n", a, b); return 0; }

Expected output:

text

a = 7, b = 5

Notes: For large values, prefer a temporary variable or XOR swap. XOR is safe for equal values but not for aliasing.

Problem 3: Convert double to int

Goal: Truncate a floating-point number.

c

#include <stdio.h> int main(void) { double d = 3.99; int i = (int)d; /* explicit cast truncates toward zero */ printf("%d\n", i); return 0; }

Expected output:

text

3

Notes: (int) truncates; round() from <math.h> rounds to nearest.

Problem 4: Last Digit of a Number

Goal: Extract the last decimal digit.

c

#include <stdio.h> #include <stdlib.h> int main(void) { int n = 12345; int last = n % 10; /* Works for non-negative; see notes for negatives */ printf("%d\n", last); return 0; }

Expected output:

text

5

Notes: For negative n, % in C99 yields a non-positive remainder. Use abs(n) % 10 or n % 10 + 10 (mod 10) for a non-negative digit.

Problem 5: Compare Two Numbers

Goal: Print which of two numbers is greater.

c

#include <stdio.h> int main(void) { int a = 10, b = 20; if (a > b) printf("%d is greater\n", a); else if (b > a) printf("%d is greater\n", b); else printf("Both are equal\n"); return 0; }

Expected output:

text

20 is greater

Problem 6: FizzBuzz (1 to N)

Goal: Print Fizz, Buzz, or FizzBuzz per divisibility rule.

c

#include <stdio.h> void fizzbuzz(int n) { for (int i = 1; i <= n; i++) { if (i % 15 == 0) printf("FizzBuzz "); else if (i % 3 == 0) printf("Fizz "); else if (i % 5 == 0) printf("Buzz "); else printf("%d ", i); } printf("\n"); } int main(void) { fizzbuzz(15); return 0; }

Expected output:

text

1 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 FizzBuzz

Notes: Check i % 15 == 0 first, otherwise the individual cases shadow the combined one.

Problem 7: Multiplication Table

Goal: Print an n × n multiplication table.

c

#include <stdio.h> int main(void) { int n = 5; for (int i = 1; i <= n; i++) { for (int j = 1; j <= n; j++) { printf("%4d", i * j); } printf("\n"); } return 0; }

Expected output:

text

 1 2 3 4 5 2 4 6 8 10 3 6 9 12 15 4 8 12 16 20 5 10 15 20 25

Problem 8: Sum of N Numbers with Safe Input

Goal: Read n integers from stdin, sum them.

c

#include <stdio.h> #include <stdlib.h> #include <errno.h> #include <limits.h> int readInteger(const char *prompt, int *outValue) { char buffer[64]; char *endPtr; long val; printf("%s", prompt); fflush(stdout); if (fgets(buffer, sizeof(buffer), stdin) == NULL) return -1; errno = 0; val = strtol(buffer, &endPtr, 10); if (endPtr == buffer || (*endPtr != '\n' && *endPtr != '\0')) return -1; if (errno == ERANGE || val < INT_MIN || val > INT_MAX) return -1; *outValue = (int)val; return 0; } int main(void) { int n; if (readInteger("How many numbers? ", &n) != 0 || n <= 0) { fprintf(stderr, "Invalid count.\n"); return 1; } long long sum = 0; for (int i = 0; i < n; i++) { int x; char prompt[40]; snprintf(prompt, sizeof(prompt), "Number %d: ", i + 1); if (readInteger(prompt, &x) != 0) { fprintf(stderr, "Invalid number.\n"); return 1; } sum += x; } printf("Sum = %lld\n", sum); return 0; }

Sample run:

text

How many numbers? 3 Number 1: 10 Number 2: 20 Number 3: 30 Sum = 60

Notes: Uses long long for the sum to prevent overflow; strtol for robust parsing.

Bonus Problem: Medium — Count Digits of an Integer

c

#include <stdio.h> #include <stdlib.h> int countDigits(long long n) { if (n == 0) return 1; if (n < 0) n = -n; int count = 0; while (n > 0) { count++; n /= 10; } return count; } int main(void) { printf("%d\n", countDigits(0)); /* 1 */ printf("%d\n", countDigits(12345)); /* 5 */ printf("%d\n", countDigits(-9876)); /* 4 */ return 0; }

Expected output:

text

1 5 4

Bonus Problem: Medium — Leap Year

c

#include <stdio.h> int isLeapYear(int year) { /* A year is leap if divisible by 4 but not 100, unless divisible by 400 */ return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0); } int main(void) { int years[] = {1900, 2000, 2024, 2023, 2100}; for (int i = 0; i < 5; i++) { printf("%d → %s\n", years[i], isLeapYear(years[i]) ? "Leap" : "Not Leap"); } return 0; }

Expected output:

text

1900 → Not Leap 2000 → Leap 2024 → Leap 2023 → Not Leap 2100 → Not Leap

Common Pitfalls, Edge Cases & Memory Considerations

1. Integer Division Truncation

5 / 2 is 2, not 2.5. Cast one operand.

c

double r = (double)a / b;

2. Format Specifier Mismatch

printf("%d", 3.14) is undefined behavior. Match specifiers to types.

3. Negative Remainders

In C99, -7 % 3 == -1, not 2. Use (n % m + m) % m for a non-negative result.

4. Off-by-One in Loops

for (i = 0; i <= n; i++) iterates n+1 times. Use < unless you need inclusive bounds.

5. Unsafe Input

Never use gets (removed in C11). Prefer fgets with size limits.

6. Uninitialized Variables

Reading before assignment is undefined behavior.

c

int x; printf("%d\n", x); /* BUG */

7. Signed/Unsigned Confusion

Mixing signed and unsigned in comparisons yields surprising results.

c

int a = -1; unsigned int b = 1; if (a < b) { /* may be FALSE due to conversion */ }

8. Overflow

int overflows for large sums. Use long long.

c

long long sum = 0;

9. Character Classifications

Use <ctype.h> functions for portability.

c

if (isdigit((unsigned char)c)) { /* ... */ }

Cast to unsigned char before passing.

10. Memory Considerations

  • Fundamental programs rarely allocate memory.
  • When they do, prefer dynamic allocation for large inputs.
  • Free everything you malloc.
  • Never return pointers to stack variables.

Summary & Best Practices

Fundamentals are the platform on which all C skill is built. Practice them until they become reflexive.

Key Takeaways

  • Variables and types define what can be stored.
  • Format specifiers define how data is printed/read.
  • Operators and control flow define how programs compute.
  • Safe input requires fgets + strtol/strtod.
  • Every bug you encounter at scale is a fundamentals bug in disguise.

Best Practices

  1. Compile with -Wall -Wextra -pedantic -std=c11 on every exercise.
  2. Match format specifiers to types.
  3. Use long long for sums; double for decimals.
  4. Prefer fgets over scanf("%s", ...).
  5. Cast before division when you need floating-point results.
  6. Initialize all variables.
  7. Handle negative remainders explicitly.
  8. Cast char to unsigned char for <ctype.h> functions.
  9. Test boundary cases: zero, negatives, maxima, empty input.
  10. Practice daily — fundamentals are perishable without repetition.

Recommended Practice Progression

  • Easy: Print GeeksforGeeks; Global Variable; Swap Numbers; Convert double to int; Input Integer; Print with Space/Without/Separator/New Line; Be Precise; Print with Quotes; Last Digit; Compute a/b; Compare Two Numbers; Sign Check; Big Number; Sum of N; Multiplication Table.
  • Medium: AP Term; GP Term; Even Odd Game; Greatest of Three; Leap Year; FizzBuzz; Calculator; First Digit; Count Digits; Days Before n Days; Prime Factorization; Next Prime.

Work through each exercise with the templates and safety rules above. By the time you finish, fundamentals become second nature.

Learn C language introduction: history, features, syntax, pointers, dynamic memory, and file I/O. Complete tutorial with practical examples and error handling.

C Language Introduction: Comprehensive Tutorial

C Language Introduction: A Comprehensive Tutorial for Programmers

C is a general-purpose, procedural programming language developed by Dennis Ritchie at Bell Laboratories in 1972. It was originally designed for implementing the UNIX operating system but quickly became one of the most influential and widely used languages in the history of computing. C provides a unique blend of high‑level constructs (functions, control structures, data types) and low‑level capabilities (direct memory access, bit manipulation), making it the language of choice for systems programming, embedded development, and performance‑critical applications.

This tutorial covers the fundamentals of C, its core syntax, a complete practical example with robust error handling, common pitfalls, and best practices for writing safe, efficient, and portable code.


Conceptual Introduction & Fundamentals

What is C?

C is a statically typed, compiled, procedural programming language. It supports structured programming through functions and blocks, but also allows direct manipulation of memory via pointers. This dual nature gives programmers fine‑grained control over hardware resources, which is essential for developing operating systems, device drivers, and real‑time systems.

Historical Significance

  • Dennis M. Ritchie created C at Bell Laboratories in 1972 while working on the UNIX operating system.
  • Before C, UNIX was written in assembly language, which was non‑portable and difficult to maintain. C enabled the OS to be rewritten in a high‑level language that could be recompiled for different hardware architectures.
  • C’s design heavily influenced many later languages, including C++, Java, C#, JavaScript, PHP, and Python. For this reason, C is often called the “mother of all programming languages.”

Key Features of C

  • Procedural and structured: Programs are built from functions and control structures.
  • Fast execution speed: Compiled to native machine code with minimal runtime overhead.
  • Low‑level memory access: Pointers allow direct manipulation of memory addresses.
  • Portability: Standard C code can be compiled on virtually any platform with a compliant compiler.
  • Rich set of operators: Including bitwise, arithmetic, logical, and pointer operators.
  • Small core language: The language itself is compact; functionality is extended through libraries.
  • Static typing: Variable types are checked at compile time, catching many errors early.

Applications of C

C is used in a wide variety of domains:

  • Operating Systems: Linux, Windows kernel components, macOS.
  • Embedded Systems: Microcontrollers, IoT devices, automotive control.
  • System Software: Device drivers, assemblers, linkers, system utilities.
  • Compiler Development: Many compilers and interpreters are written in C (e.g., GCC, LLVM, CPython).
  • Database Systems: MySQL, PostgreSQL core engines.
  • Game Development: Performance‑critical engines and physics simulations.
  • Networking: TCP/IP stacks, routers, firewalls, protocol implementations.
  • Scientific Applications: High‑performance computing, simulations.
  • Cybersecurity Tools: Penetration testing, network analyzers, security software.
  • GUI Applications: Desktop software using libraries like GTK or Win32 API.

Structure of a C Program

Every C program consists of:

  1. Preprocessor directives (#include, #define) – processed before compilation.
  2. Function definitions – including the mandatory main function, the entry point.
  3. Statements – executable instructions inside functions.
  4. Comments – documentation ignored by the compiler.

The main function returns an integer to the operating system: 0 typically indicates success, non‑zero indicates an error.


Syntax & Core Mechanics

Basic Program: Hello World

c

#include <stdio.h> // Preprocessor: include standard I/O functions int main(void) { // Entry point; returns an integer status // This is a single-line comment printf("Hello World\n"); // Print text to console return 0; // Indicate successful execution }

Expected output:

text

Hello World

Header Files

Header files declare functions, macros, and types used by the program. Common headers:

HeaderPurpose
<stdio.h>Standard I/O: printf, scanf, fgets, fopen, etc.
<stdlib.h>Memory allocation, conversion, exit, qsort
<string.h>String manipulation: strcpy, strlen, strcmp
<math.h>Mathematical functions: sqrt, pow, sin
<ctype.h>Character classification: isdigit, tolower

Include them with #include <header.h>.

Data Types and Variables

C provides basic types and modifiers to control size and signedness.

TypeTypical Size (bytes)Description
char1Single character or small integer
int4Integer (platform‑dependent)
float4Single‑precision floating point
double8Double‑precision floating point
voidN/ANo value (for functions, pointers)

Modifiers: short, long, signed, unsigned, long long.

c

int age = 25; float price = 19.99f; char grade = 'A'; unsigned long long big = 18446744073709551615ULL; const double PI = 3.141592653589793;

Variables must be declared before use. const indicates that the value cannot be modified after initialization.

Operators

  • Arithmetic: +, -, *, /, % (modulo)
  • Relational: ==, !=, <, >, <=, >=
  • Logical: &&, ||, !
  • Bitwise: &, |, ^, ~, <<, >>
  • Assignment: =, +=, -=, *=, /=, %=
  • Increment/Decrement: ++, --
  • Ternary: condition ? expr1 : expr2

Control Flow

if / else if / else

c

int score = 85; if (score >= 90) { printf("A\n"); } else if (score >= 80) { printf("B\n"); } else { printf("C or below\n"); }

Output: B

switch

c

int choice = 2; switch (choice) { case 1: printf("One\n"); break; case 2: printf("Two\n"); break; default: printf("Other\n"); }

Output: Two

Loops

c

// for loop for (int i = 0; i < 5; i++) { printf("%d ", i); } printf("\n"); // while loop int j = 0; while (j < 5) { printf("%d ", j); j++; } printf("\n"); // do-while (executes at least once) int k = 0; do { printf("%d ", k); k++; } while (k < 5); printf("\n");

Expected output:

text

0 1 2 3 4 0 1 2 3 4 0 1 2 3 4

Functions

Functions must be declared (prototype) before use, unless defined earlier.

c

#include <stdio.h> int add(int a, int b); // prototype int add(int a, int b) { return a + b; } int main(void) { int result = add(3, 4); printf("Result: %d\n", result); return 0; }

Output: Result: 7

Arrays and Strings

Arrays are contiguous blocks of memory; strings are arrays of char terminated by '\0'.

c

int numbers[5] = {10, 20, 30, 40, 50}; char name[20] = "Alice"; // contains 'A','l','i','c','e','\0' printf("First number: %d\n", numbers[0]); printf("Name: %s\n", name);

Pointers

Pointers store memory addresses. They are essential for dynamic memory, efficient array processing, and low‑level programming.

c

int x = 42; int *ptr = &x; // ptr holds address of x printf("x = %d\n", x); printf("Address of x = %p\n", (void*)&x); printf("ptr = %p\n", (void*)ptr); printf("*ptr = %d\n", *ptr); // dereference *ptr = 100; // modify x through pointer printf("x now = %d\n", x);

Pointer arithmetic: Adding an integer to a pointer advances it by n * sizeof(type).

c

int arr[3] = {1, 2, 3}; int *p = arr; // arr decays to &arr[0] printf("%d\n", *(p + 1)); // prints 2

Structures

Structures group variables of different types under one name.

c

struct Point { int x; int y; }; struct Point p1 = {10, 20}; printf("p1 = (%d, %d)\n", p1.x, p1.y);

Dynamic Memory Allocation

Use malloc, calloc, realloc, and free from <stdlib.h>. Always check for allocation failure.

c

#include <stdio.h> #include <stdlib.h> int main(void) { int *arr = malloc(5 * sizeof(int)); if (arr == NULL) { fprintf(stderr, "Memory allocation failed\n"); return 1; } for (int i = 0; i < 5; i++) { arr[i] = i * 10; } for (int i = 0; i < 5; i++) { printf("%d ", arr[i]); } printf("\n"); free(arr); // always release memory return 0; }

Expected output:

text

0 10 20 30 40

File I/O

Use fopen, fclose, fgets, fputs, fprintf, fscanf, etc.

c

#include <stdio.h> int main(void) { FILE *fp = fopen("test.txt", "w"); if (fp == NULL) { perror("fopen"); return 1; } fputs("Hello, file!\n", fp); fclose(fp); fp = fopen("test.txt", "r"); if (fp == NULL) { perror("fopen"); return 1; } char buffer[100]; if (fgets(buffer, sizeof(buffer), fp) != NULL) { printf("Read: %s", buffer); } fclose(fp); return 0; }

Output: Read: Hello, file!

Preprocessor Directives

  • #include – insert contents of a header file.
  • #define – define macros or constants.
  • #ifdef, #ifndef, #endif – conditional compilation.

c

#define DEBUG 1 #if DEBUG #define LOG(msg) printf("DEBUG: %s\n", msg) #else #define LOG(msg) #endif int main(void) { LOG("Starting program"); return 0; }

Full Practical Code Example with Explanation

This section presents a complete, robust program that demonstrates many core C concepts: dynamic memory allocation, file I/O, sorting, command‑line arguments, and error handling. The program reads integers from a text file (one per line), dynamically allocates an array to store them, sorts the array using the standard library qsort, and writes the sorted numbers to an output file. It also calculates and prints the minimum, maximum, and average.

Program: Sort and Analyze Integers from a File

c

/** * file_analyzer.c * Reads integers from a text file, sorts them, and writes to another file. * Also calculates min, max, and average. * Demonstrates: dynamic arrays, file I/O, qsort with function pointer, * error checking, and memory management. */ #include <stdio.h> #include <stdlib.h> #include <limits.h> /* for INT_MAX, INT_MIN */ /* Comparator function for qsort (ascending order) */ int compare_ints(const void *a, const void *b) { int int_a = *(const int*)a; int int_b = *(const int*)b; /* Return negative if a < b, zero if equal, positive if a > b */ return (int_a > int_b) - (int_a < int_b); } int main(int argc, char *argv[]) { /* Validate command-line arguments */ if (argc != 3) { fprintf(stderr, "Usage: %s <input_file> <output_file>\n", argv[0]); return EXIT_FAILURE; } const char *input_filename = argv[1]; const char *output_filename = argv[2]; /* Open input file for reading */ FILE *infile = fopen(input_filename, "r"); if (infile == NULL) { perror("Error opening input file"); return EXIT_FAILURE; } /* Dynamic array to hold integers */ int *numbers = NULL; size_t capacity = 0; /* current allocated size */ size_t count = 0; /* number of integers stored */ /* Temporary buffer for reading lines (safe: fgets) */ char line[64]; int value; /* Read file line by line */ while (fgets(line, sizeof(line), infile) != NULL) { /* Convert line to integer; skip empty lines or non-numeric data */ if (sscanf(line, "%d", &value) != 1) { fprintf(stderr, "Warning: skipping invalid line: %s", line); continue; } /* Check if we need to grow the array */ if (count == capacity) { /* Double the capacity (or start with 8) */ size_t new_capacity = (capacity == 0) ? 8 : capacity * 2; int *new_numbers = realloc(numbers, new_capacity * sizeof(int)); if (new_numbers == NULL) { fprintf(stderr, "Memory reallocation failed\n"); free(numbers); fclose(infile); return EXIT_FAILURE; } numbers = new_numbers; capacity = new_capacity; } /* Store the integer */ numbers[count++] = value; } /* Close input file */ fclose(infile); if (count == 0) { printf("No valid integers found in input file.\n"); free(numbers); return EXIT_SUCCESS; } /* Sort the array using qsort */ qsort(numbers, count, sizeof(int), compare_ints); /* Calculate statistics (min is numbers[0], max is numbers[count-1]) */ int min = numbers[0]; int max = numbers[count - 1]; double sum = 0.0; for (size_t i = 0; i < count; i++) { sum += numbers[i]; } double average = sum / count; /* Open output file for writing */ FILE *outfile = fopen(output_filename, "w"); if (outfile == NULL) { perror("Error opening output file"); free(numbers); return EXIT_FAILURE; } /* Write sorted numbers, one per line */ for (size_t i = 0; i < count; i++) { fprintf(outfile, "%d\n", numbers[i]); } /* Close output file */ fclose(outfile); /* Print summary to console */ printf("Successfully processed %zu integers.\n", count); printf("Minimum: %d\n", min); printf("Maximum: %d\n", max); printf("Average: %.2f\n", average); printf("Sorted numbers written to '%s'.\n", output_filename); /* Free allocated memory */ free(numbers); return EXIT_SUCCESS; }

Explanation of the Code

  1. Includes and comparator
    • <stdio.h> for I/O, <stdlib.h> for memory and qsort, <limits.h> for integer limits (not strictly needed but included).
    • compare_ints is a comparator function for qsort. It must return a negative, zero, or positive value. The expression (int_a > int_b) - (int_a < int_b) avoids potential overflow from simple subtraction.
  2. Command‑line argument validation
    • The program requires exactly two arguments: input file and output file. If not provided, it prints usage and exits with EXIT_FAILURE (defined in <stdlib.h>).
  3. File opening
    • fopen returns NULL on failure. perror prints a descriptive message based on the global errno.
  4. Dynamic array growth
    • numbers starts as NULL, capacity as 0. When count == capacity, the array is resized using realloc. Starting capacity is 8, then doubled each time, amortizing the cost of reallocation.
    • realloc can fail; if it does, we free the existing memory, close the file, and exit.
  5. Safe line reading and parsing
    • fgets reads at most sizeof(line)-1 characters, preventing buffer overflow.
    • sscanf parses the integer; if it returns 0 or EOF, the line is skipped with a warning.
  6. Sorting and statistics
    • qsort sorts the array in place. After sorting, numbers[0] is the minimum and numbers[count-1] is the maximum. Sum is computed for average.
  7. Output
    • Sorted integers are written to the output file, and statistics are printed to the console.
  8. Cleanup
    • All files are closed, and dynamically allocated memory is freed.

Expected Output

Assume input.txt contains:

text

42 7 19 100 3 -5

Running:

bash

$ ./file_analyzer input.txt output.txt

Console output:

text

Successfully processed 6 integers. Minimum: -5 Maximum: 100 Average: 27.67 Sorted numbers written to 'output.txt'.

output.txt contains:

text

-5 3 7 19 42 100

If the input file contains invalid lines (e.g., abc), the program will print warnings and continue.


Common Pitfalls, Edge Cases & Memory Considerations

C gives you immense power, but with that power comes responsibility. The following are the most common mistakes that lead to bugs, crashes, or security vulnerabilities.

1. Buffer Overflows

Using unsafe functions like gets (removed in C11) or strcpy without bounds checking can overflow buffers.

Solution: Use fgets for strings, strncpy with explicit length, or snprintf.

c

char buf[10]; fgets(buf, sizeof(buf), stdin); // safe snprintf(buf, sizeof(buf), "%s", src);

2. Off‑by‑One Errors

Arrays are zero‑indexed. Accessing arr[n] where n is the array size is out of bounds.

c

int arr[5]; for (int i = 0; i <= 5; i++) // BUG: i == 5 is out of range arr[i] = i;

Always loop i < size.

3. Uninitialized Pointers and Dangling Pointers

  • Uninitialized pointer: Contains garbage; dereferencing it causes undefined behavior.
  • Dangling pointer: Points to freed memory. Accessing it is undefined.

c

int *p; *p = 10; // BUG: p not initialized int *q = malloc(sizeof(int)); free(q); *q = 20; // BUG: q is dangling

Initialize pointers to NULL; set them to NULL after free.

4. Memory Leaks

Forgetting to free dynamically allocated memory causes leaks, especially in long‑running programs.

c

void leak(void) { int *p = malloc(100 * sizeof(int)); // no free(p) }

Always pair malloc/calloc/realloc with free; consider using tools like Valgrind.

5. Double Free

Calling free twice on the same pointer corrupts the heap.

c

int *p = malloc(10); free(p); free(p); // BUG

Set pointer to NULL after freeing; free(NULL) is safe.

6. Integer Overflow

Signed integer overflow is undefined behavior; unsigned overflow wraps around.

c

int a = INT_MAX; a++; // undefined behavior

Use larger types, check before operations, or use compiler sanitizers.

7. String Termination

C strings must be null‑terminated. Forgetting the '\0' leads to reading beyond the intended buffer.

c

char str[5]; strncpy(str, "Hello", 5); // No room for '\0' printf("%s", str); // BUG: may run past buffer

Ensure buffer has at least strlen(src) + 1 bytes.

8. Pointer Arithmetic Mistakes

Adding an integer to a pointer advances by n * sizeof(type), not n bytes unless the type is char.

c

int arr[3] = {1,2,3}; int *p = arr; p = p + 1; // points to arr[1], not arr[0]+1 byte

Understand pointer scaling.

9. File I/O Errors Not Checked

Always check return values of fopen, fread, fwrite, fclose. A failed fopen returns NULL; ignoring it causes segmentation faults.

c

FILE *fp = fopen("missing.txt", "r"); fgets(buf, sizeof(buf), fp); // BUG: fp is NULL

10. Uninitialized Variables

Reading an uninitialized automatic variable is undefined.

c

int x; printf("%d\n", x); // garbage

Initialize at declaration.


Summary & Best Practices

C remains an indispensable language for systems programming, embedded development, and performance‑critical applications. Its direct access to memory and hardware, combined with a minimal runtime, makes it both powerful and demanding.

Key Takeaways

  • C was created by Dennis Ritchie in 1972 at Bell Labs for UNIX development.
  • It is a mid‑level language: high‑level constructs with low‑level control.
  • Learning C deepens your understanding of memory, pointers, and the machine.
  • Core syntax includes data types, control flow, functions, arrays, pointers, structures, and dynamic memory.
  • Proper error handling and memory management are mandatory to write robust C code.

Best Practices

  1. Compile with warnings enabled: Use -Wall -Wextra -pedantic (GCC/Clang) to catch potential issues early.
  2. Use const liberally for variables that should not change.
  3. Check the return value of every library call that can fail (malloc, fopen, fgets, etc.).
  4. Prefer safe functions: fgets over gets; snprintf over sprintf; strncpy/strlcpy over strcpy.
  5. Initialize all variables and pointers.
  6. Set pointers to NULL after freeing.
  7. Avoid magic numbers: Use #define or enum.
  8. Keep functions small and focused to improve readability and testing.
  9. Document assumptions and invariants in comments.
  10. Use tools like Valgrind, AddressSanitizer, and UBSan to detect memory errors and undefined behavior.
  11. Write portable code: Use standard types (size_t, int32_t) and avoid platform‑specific assumptions.
  12. Always free dynamically allocated memory and close opened files.

C may be over 50 years old, but its relevance has not diminished. Mastering C is a rite of passage for any serious software engineer, providing a foundation that will serve you throughout your career, no matter which languages you use later.