Programming for Problem Solving in C | Semester 1 Core Notes
Semester 1 - Page 3: Programming for Problem Solving in C. General first-year notes with 20 numbered topics, answered exam questions and six complete tested C17 programs. Semester placement and lab tasks vary; match your own outline. These are original study examples, not official syllabus claims or copied past papers.
Topic map
- Computer basics and problem specification
- Algorithms, pseudocode, flowcharts and dry runs
- C program structure, build stages and error types
- Tokens, identifiers, variables, constants and data types
- Operators, conversions, precedence and undefined behaviour
- Formatted input, output and validation
- Decision statements and switch
- Loops, break, continue and loop invariants
- Functions, prototypes, parameters and scope
- Storage duration, linkage and recursion
- One-dimensional arrays and array contracts
- Two-dimensional arrays and matrix operations
- Strings, line input and library functions
- Pointers, pointer arithmetic and indirection
- Dynamic memory and resource ownership
- Structures, unions, enums and typedef
- File I/O, modes, errors and command-line arguments
- Preprocessor, macros and standard libraries
- Searching, sorting and efficiency
- Numerical algorithms, safety and testing
1. Computer basics and problem specification
A computer receives input, processes instructions and stores or outputs results. CPU, primary memory and secondary storage have different jobs. RAM normally holds active program data; persistent storage keeps files across ordinary shutdowns. A bit is a binary digit; a byte in C has CHAR_BIT bits, commonly eight. Hardware is physical equipment; software supplies instructions. Before coding, specify inputs, valid ranges, output and failure behaviour.
Answer: It makes success and failure testable and avoids writing code for an unclear problem.
2. Algorithms, pseudocode, flowcharts and dry runs
An algorithm is a finite sequence of precise steps for a stated problem. Pseudocode expresses the logic without compiler syntax. A flowchart uses arrows for control, rectangles for processing and diamonds for decisions. A dry run traces state after each step. Correctness needs more than one successful example: test normal, boundary and invalid inputs. Decomposition splits a large task into smaller functions with clear contracts.
Answer: The algorithm describes the method; the program implements it in a language with concrete types, syntax and input handling.
3. C program structure, build stages and error types
A hosted C program commonly starts at main. Header inclusion makes declarations available; it does not paste the full implementation of every library function into your code. Preprocessing, compilation, assembly and linking are conceptual build stages. Syntax/type errors are diagnosed during translation; unresolved external definitions can fail at linking; logic errors can survive a successful build. Use a named language mode and warnings. The examples here use C17, not nonstandard console headers.
Answer: No. It does not prove the algorithm, bounds, resource handling or input assumptions are correct.
4. Tokens, identifiers, variables, constants and data types
C tokens include keywords, identifiers, constants, string literals, operators and punctuation. Identifiers are case-sensitive and cannot be keywords. A declaration names an object and its type; initialize it before reading its value. int is for integers, double for floating-point values, char for character-sized storage, and void can mean no return value. Exact widths vary by implementation: do not assume every int is 32 bits. sizeof measures bytes and returns size_t. const restricts modification through that declared access, not every possible alias.
Answer: Its value is indeterminate. Do not treat it as a predictable zero; initialize or assign it on every path before use.
5. Operators, conversions, precedence and undefined behaviour
Arithmetic, comparison, logical, assignment, conditional and bitwise operators solve different tasks. Integer division truncates toward zero: 7/2 is 3 and -7/2 is -3. A floating operand gives floating arithmetic. Parentheses clarify grouping; precedence does not in general decide operand evaluation order. Logical AND/OR short-circuit and yield truth values; bitwise AND/OR operate on bits. Signed integer overflow and out-of-bounds access are undefined behaviour, not portable wraparound or error messages. Avoid unsequenced modifications such as i++ + i++.
Answer: No. = assigns a value, whereas == compares two values. A mistaken assignment in a condition can compile but change the logic.
6. Formatted input, output and validation
printf formats output; scanf attempts conversions into supplied objects. Format specifiers must match the expected argument types. For scanf, %d needs int*, %lf needs double*, and %f needs float*. In printf, %f accepts a promoted double. Check the number of successful conversions before using values. A conversion-count check alone does not validate ranges or reject trailing input. For robust general numeric text parsing, use line input and strtol/strtod with range and end-pointer checks. The short marks example handles malformed tokens, nonfinite values and 0..100 range but is not a strict whole-line parser.
Answer: It must write the converted value into the caller object. The address identifies that object; a mismatched pointer type is unsafe.
7. Decision statements and switch
if selects a statement based on a scalar condition; an else belongs to the nearest unmatched if, so braces prevent confusion. An else-if chain handles several exclusive cases in order. switch selects by an integer-type expression and constant case labels; ordinary cases can fall through without break. A default handles unmatched values. The conditional operator chooses an expression value, not a whole menu of statements.
Answer: Use them consistently, especially with else branches, to make the intended grouping visible and resistant to later edits.
8. Loops, break, continue and loop invariants
for is useful when initialization, condition and update form a clear progression. while tests before the body; do-while tests after it, so its body runs at least once. break exits the nearest loop or switch. continue advances to the next iteration; in a for loop its update still executes. An invariant is a statement true at a chosen point on every iteration. Also prove progress toward termination. A loop with incorrect bounds can read past an array or run forever.
Answer: The condition is checked after the do-while body, so even an initially false condition allows one execution.
9. Functions, prototypes, parameters and scope
A function declaration states the interface; a definition supplies the body. C passes arguments by value, including pointer values. Passing a pointer lets a function access a caller object, but does not turn the language into true reference-parameter passing. A prototype enables argument checking. Variables declared in a block have block scope. Keep contracts explicit: accepted ranges, valid pointers, ownership and returned status. C does not allow ordinary nested function definitions.
Answer: No. An array parameter is adjusted to a pointer type; pass the element count separately or establish another explicit contract.
10. Storage duration, linkage and recursion
Scope is where a name is visible; storage duration is how long the object exists; linkage determines whether declarations refer to the same entity. An automatic local usually lives for its block execution. A static local persists between calls. File-scope static gives internal linkage. extern commonly declares an object defined elsewhere. Recursion solves a smaller subproblem using a base case, but consumes call resources. Do not return a pointer to an expired automatic local.
Answer: A reachable base case and a recursive step that moves toward it. Also consider resource depth and arithmetic overflow.
11. One-dimensional arrays and array contracts
An array holds a fixed number of elements of one type contiguously. For n elements, valid indices are 0 through n-1. Most array expressions convert to a pointer to the first element, but arrays are not pointer variables. sizeof a/sizeof a[0] finds the length only where a is the actual array, not an adjusted function parameter. A maximum function must decide empty-input behaviour and initialize from a valid element, not zero when all values may be negative.
Answer: Index 5 is outside the valid range 0..4. The behaviour is undefined; a compiler is not required to diagnose every such access.
12. Two-dimensional arrays and matrix operations
A two-dimensional array is an array of row arrays. In int a[2][3], there are two rows of three ints. Row-major storage places the row elements together. Matrix addition requires equal dimensions; multiplication requires the left column count to equal the right row count. Each output cell is a dot product. Initialize every result accumulator before adding products. The column dimension matters when passing a rectangular array to a function.
Answer: A pointer-to-pointer layout can refer to separately allocated rows. A true rectangular array has contiguous row arrays and a different type.
13. Strings, line input and library functions
A C string is a character sequence terminated by a null character. char word[]="code" creates a writable array including that terminator; a string literal must not be modified. strlen counts characters before the terminator, not the allocated capacity. strcmp compares lexicographically and returns a value less than, equal to or greater than zero, not necessarily -1 or 1. fgets limits input and can retain the newline. Check its return and possible truncation. strcpy and strcat require enough destination space. Never use removed gets or assume strrev is standard C.
Answer: One element is reserved for the null terminator. Filling all 20 with non-null characters does not produce a terminated string.
14. Pointers, pointer arithmetic and indirection
A pointer value identifies an object or function, or can be null. & obtains an address and * accesses the pointed-to object when valid. Pointer arithmetic is defined within an array and its one-past position; that one-past pointer must not be dereferenced. Adding one advances by one element, not necessarily one byte. Null, uninitialized, dangling and wrong-type pointers must not be dereferenced. const int* prevents modifying the int through that pointer; int* const prevents reseating the pointer itself.
Answer: The object it pointed to no longer exists, for example after free or after returning from the block that owned an automatic object.
15. Dynamic memory and resource ownership
malloc allocates storage with uninitialized contents; calloc allocates and sets bytes to zero. Check allocation failure before access and initialize the values you need. Guard size multiplication against overflow. free releases an allocation once; freeing null is allowed. Use-after-free and double-free are invalid. For realloc, keep the original pointer until success: overwriting it directly on failure can lose the allocation. A resized allocation can move and invalidate previous pointers into it. Specify who owns and frees memory.
Answer: If it fails for a nonzero request, the original allocation remains valid. A temporary preserves the original pointer so it can still be used or freed.
16. Structures, unions, enums and typedef
A struct groups separate named members; each member has its own storage. A union lets different members share storage, so do not treat it as several independent stored values. Size includes implementation padding and is not always the sum of member sizes. An enum names integer constants; typedef supplies a type alias, not a new object. Use dot with a struct object and arrow through a pointer. Self-referential structures use pointers to their own type rather than containing an endless instance of themselves.
Answer: The implementation can insert padding to meet alignment requirements. Do not serialize raw layout as if it were a universal file format.
17. File I/O, modes, errors and command-line arguments
fopen returns a stream pointer or null on failure. Text mode r reads an existing file; w creates or truncates; a appends. Binary mode adds b where meaningful. Check reads/writes and close errors. EOF is a result indicator, not a character to store in char; fgetc returns int so it can represent all unsigned-char values plus EOF. Read using the operation result, not while(!feof(f)). argc and argv supply command-line arguments; strings still need parsing and validation. Portable text-file seeking has restrictions; do not assume any byte offset is valid.
Answer: The end flag is set only after a read reaches the end. Test the read result and distinguish end-of-file from an actual I/O error.
18. Preprocessor, macros and standard libraries
#include, #define and conditional compilation run before ordinary compilation. An object-like macro substitutes tokens. Function-like macros need parentheses around parameters and the whole expression, and can evaluate an argument more than once. Prefer a function when side effects or type checking matter. Standard headers include stdio.h, stdlib.h, string.h, math.h, ctype.h and assert.h; conio.h is not part of standard C. Library availability and linking flags depend on the environment.
Answer: No. They fix many grouping problems but do not prevent repeated evaluation, unsuitable types or other context problems.
19. Searching, sorting and efficiency
Linear search scans elements and works on unsorted data, with O(n) worst-case comparisons. Binary search requires the correct sort order and repeatedly halves the range, using O(log n) comparisons. Selection sort chooses the smallest remaining item on each pass, using O(n squared) comparisons and O(1) extra array space. Big-O describes growth, not the exact elapsed time on every machine. State whether an algorithm changes the input and whether its equal-value order is stable.
Answer: No. Its elimination step relies on the ordering. Sort appropriately first or use a method that does not require sorting.
20. Numerical algorithms, safety and testing
Translate the mathematical rule into a bounded machine algorithm. A primality test rejects n<2 and checks possible divisors up to sqrt(n). Use d<=n/d for positive n rather than d*d<=n when the product could overflow. Factorial grows rapidly, so check before multiplication. Euclid GCD repeatedly replaces (a,b) by (b,a%b). Floating-point arithmetic has rounding error; a result can need tolerance rather than exact equality. Debug with warnings, boundary tests and optional runtime checking, not by trusting a single printed output.
Answer: Undefined behaviour has no required portable result. Explain and remove the defect rather than presenting a lucky compiler run as the language rule.
Difference table
| Pair | First | Second | Exam warning |
|---|---|---|---|
| Array / pointer | Sequence of elements | Value identifying an object/function | Array expressions often convert, but types are not identical |
| while / do-while | Pre-test | Post-test | do-while runs at least once |
| struct / union | Separate member storage | Shared member storage | Padding and representation are implementation details |
| Automatic / static local | Lifetime of block execution | Lifetime of program | Scope and lifetime are different concepts |
| malloc / calloc | Uninitialized allocated storage | Allocated bytes zeroed | Check failure; byte-zero is not every type value guarantee |
| Linear / binary search | Unsorted data allowed | Order required | Worst-case O(n) versus O(log n) comparisons |
Six complete programs and checked output
Each file below was compiled independently with gcc -std=c17 -Wall -Wextra -Wpedantic -Werror file.c -o program. Run one file at a time. Function contracts are part of correctness. The displayed outputs are actual test runs; optional failure paths depend on resources and input.
Validated marks average
#include <stdio.h>
#include <math.h>
int main(void) {
double a, b, c;
if (scanf("%lf %lf %lf", &a, &b, &c) != 3 ||
!isfinite(a) || !isfinite(b) || !isfinite(c) ||
a < 0 || a > 100 || b < 0 || b > 100 || c < 0 || c > 100) {
puts("Invalid marks");
return 1;
}
printf("Average: %.2f\n", (a + b + c) / 3.0);
return 0;
}
Checked run:
Average: 70.00
Input for that run: 60 70 80. Tests 0 0 0 and 100 100 100 printed 0.00 and 100.00. Tests bad, 101 70 80 and nan 70 80 printed Invalid marks and exited with failure.
Maximum, selection sort, binary search and prime
#include <stdio.h>
#include <stddef.h>
int is_prime(int n) {
if (n < 2) return 0;
for (int d = 2; d <= n / d; ++d)
if (n % d == 0) return 0;
return 1;
}
int max_of(const int a[], size_t n, int *out) {
if (n == 0) return 0;
int m = a[0];
for (size_t i = 1; i < n; ++i)
if (a[i] > m) m = a[i];
*out = m;
return 1;
}
void selection_sort(int a[], size_t n) {
for (size_t i = 0; i < n; ++i) {
size_t m = i;
for (size_t j = i + 1; j < n; ++j)
if (a[j] < a[m]) m = j;
int t = a[i]; a[i] = a[m]; a[m] = t;
}
}
int binary_find(const int a[], size_t n, int key) {
size_t lo = 0, hi = n;
while (lo < hi) {
size_t mid = lo + (hi - lo) / 2;
if (a[mid] == key) return (int)mid;
if (a[mid] < key) lo = mid + 1;
else hi = mid;
}
return -1;
}
int main(void) {
int a[] = {4, 9, 2, 7, 5}, m;
size_t n = sizeof a / sizeof a[0];
if (max_of(a, n, &m)) printf("max=%d\n", m);
selection_sort(a, n);
for (size_t i = 0; i < n; ++i) printf("%d%c", a[i], i + 1 == n ? '\n' : ' ');
printf("find7=%d find8=%d\n", binary_find(a,n,7), binary_find(a,n,8));
printf("empty=%d\n", max_of(a,0,&m));
for (int i = 0; i <= 10; ++i)
if (is_prime(i)) printf("%d ", i);
puts("");
printf("largeprime=%d\n", is_prime(2147483647));
return 0;
}
Checked run:
max=9 2 4 5 7 9 find7=3 find8=-1 empty=0 2 3 5 7 largeprime=1
The binary-search return is an int index for these small teaching arrays; a general API for arrays larger than INT_MAX needs a different result type. The primality loop avoids the old i*i overflow defect. Empty maximum input is explicitly rejected.
Pointer swap, GCD, checked factorial and recursion
#include <stdio.h>
#include <limits.h>
void swap(int *a, int *b) {
int t = *a; *a = *b; *b = t;
}
unsigned gcd(unsigned a, unsigned b) {
while (b != 0) {
unsigned r = a % b; a = b; b = r;
}
return a;
}
int factorial(unsigned n, unsigned long long *out) {
unsigned long long result = 1;
for (unsigned i = 2; i <= n; ++i) {
if (result > ULLONG_MAX / i) return 0;
result *= i;
}
*out = result;
return 1;
}
unsigned recursive_sum(unsigned n) {
/* Teaching contract: n <= 100, not unbounded recursion. */
return n == 0 ? 0 : n + recursive_sum(n - 1);
}
int main(void) {
int a = 3, b = 8; swap(&a, &b);
printf("swap=%d,%d gcd=%u sum=%u\n", a,b,gcd(48,18),recursive_sum(5));
unsigned long long f;
if (factorial(5,&f)) printf("5!=%llu\n",f);
if (factorial(0,&f)) printf("0!=%llu\n",f);
printf("overflow_rejected=%d\n", !factorial(100,&f));
return 0;
}
Checked run:
swap=8,3 gcd=6 sum=15 5!=120 0!=1 overflow_rejected=1
Dynamic array, structure, matrix and string
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
struct Student { char name[20]; int mark; };
int main(void) {
const size_t n = 4;
int *a = NULL;
if (n > SIZE_MAX / sizeof *a) return 1;
a = malloc(n * sizeof *a);
if (a == NULL) return 1;
for (size_t i = 0; i < n; ++i) a[i] = (int)(i + 1);
int sum = 0;
for (size_t i = 0; i < n; ++i) sum += a[i];
printf("sum=%d\n", sum);
free(a); a = NULL;
struct Student s = {"Ada", 85};
struct Student *p = &s;
printf("%s %d length=%zu\n", p->name,p->mark,strlen(p->name));
int matrix[2][2] = {{1,2},{3,4}};
printf("diagonal=%d\n",matrix[0][0]+matrix[1][1]);
char word[] = "code";
word[0] = 'C';
printf("%s\n", word);
return 0;
}
Checked run:
sum=10 Ada 85 length=3 diagonal=5 Code
Temporary text-file round trip
#include <stdio.h>
int main(void) {
FILE *f = tmpfile();
if (f == NULL) { perror("tmpfile"); return 1; }
if (fprintf(f,"Ada 85\n") < 0 || fflush(f) == EOF) {
fclose(f); return 1;
}
rewind(f);
char name[20]; int mark;
if (fscanf(f,"%19s %d",name,&mark) != 2) {
fclose(f); return 1;
}
printf("%s %d\n",name,mark);
if (fclose(f) == EOF) return 1;
return 0;
}
Checked run:
Ada 85
Bounded line input
#include <stdio.h>
#include <string.h>
int main(void) {
char line[32];
if (fgets(line,sizeof line,stdin) == NULL) return 1;
size_t len = strlen(line);
if (len > 0 && line[len-1] == '\n') line[--len] = '\0';
else if (!feof(stdin)) {
puts("No newline: check truncation or final input line");
}
printf("text=%s length=%zu\n",line,len);
return 0;
}
Checked run:
text=hello world length=11
Input for that run: hello world followed by Enter. An empty line printed length=0. A final line short without a newline printed length=5. For a long line, the notice asks you to check truncation; a production parser should consume/reject the remaining data according to its contract.
Practice and short answers
- Integer
7/2? 3. - Last index of 4 elements? 3.
- Prime check for 1? False.
- Does
sizeofan array parameter reveal array length? No, the parameter is adjusted to a pointer. - Two inputs for GCD: 48 and 18? 6.
- What must happen after successful allocation? Initialize before reading, use only valid bounds, then release owned memory once.
Coverage, references and further practice
This guide expands the earlier syntax-and-array summary to computer basics, algorithms, operators, control flow, functions, scope/lifetime, arrays/matrices, strings, pointers, memory, records, files, macros and introductory searching/sorting. These explanations are a starting point for full lab work, not a promise to match every possible course subtopic.
Reference reading: C language, arrays, pointers, formatted input, line input, allocation and compiler warnings.
Continue with the C lab file, Lab Files index and Online Compiler. Preserve indentation when copying code.
Comments
Post a Comment