# C Programming Fundamentals

> Detailed C programming notes covering algorithmic thinking, program structure, control flow, arrays, pointers, memory management, functions, structures, files and the preprocessor.

- Author: Muhammet Ali Köker
- Language: en
- Canonical: https://alikoker.com.tr/en/c-programming-fundamentals
- Translation: https://alikoker.com.tr/c-programlama-temelleri
- Published: 2014-01-11T17:40:00+03:00
- Modified: 2026-07-14T18:10:00+03:00
- Verified: 2026-08-08T15:00:00+03:00
- Type: article

I originally wrote these C notes while studying introductory programming and systems-programming fundamentals. The first version followed the 2013-2014 course sequence. Later revisions add standard-language changes only where they clarify the subject; they do not rewrite C's core semantics as if recent conveniences had always been part of the language.

## Unit 1: Introduction to Programming

### Problem solving

Programming is the process of turning a problem into an exact sequence of operations that a computer can execute. Writing source code is only one of the later steps. The problem must first be understood, its inputs and outputs identified, and a solution method designed.

A useful sequence is:

```text
Define the problem
    ↓
Identify inputs and outputs
    ↓
Design the algorithm
    ↓
Choose data structures
    ↓
Write the program
    ↓
Compile and run
    ↓
Test
    ↓
Evaluate the result
```

Correct syntax alone does not make a program correct. A program should solve the intended problem, cover the required cases, have defined behavior for invalid input, use resources appropriately and remain understandable enough to maintain.

### Algorithm

An algorithm is a finite and unambiguous sequence of operations that transforms defined input into the required output. Its steps must be executable, terminate, behave predictably under the stated conditions and cover the cases required by the problem.

Finding the largest of three values can be expressed without committing to any programming language:

```text
largest <- a

if b > largest
    largest <- b

if c > largest
    largest <- c

return largest
```

The algorithm is independent of C or C++; the language is the implementation vehicle.

### Pseudocode

Pseudocode expresses an algorithm without forcing it into the syntax of one language:

```text
average(list):
    total <- 0

    for each item in list:
        total <- total + item

    return total / number_of_items
```

It should make control flow, data relationships and operation order visible. It does not need to compile.

### Assignment

Assignment can be represented as:

```text
x <- 5
```

and in C as:

```c
x = 5;
```

The `=` token is assignment, not mathematical equality. The right-hand expression is evaluated and its result is stored in the object on the left. Therefore:

```c
x = x + 1;
```

is meaningful: the old value is read, incremented, and the result is written back.

### Selection and loops

C provides `if`, `else` and `switch` for selection, and `for`, `while` and `do-while` for repetition. The choice of construct should reflect the structure of the problem rather than merely shorten the source code.

```c
if (temperature < 0) {
    /* freezing risk */
} else {
    /* no freezing risk */
}
```

A pre-tested loop uses `while`; a known iteration scheme often fits `for`; a post-tested loop uses `do-while`.

### Functions

A function encapsulates a named operation:

```c
int max2(int a, int b)
{
    return a > b ? a : b;
}
```

Functions reduce duplication, split a program into units, improve testability and separate interfaces from implementations.

### Flowcharts and programming languages

Flowcharts can be useful for small algorithms. Their traditional symbols represent start/end, processing, decisions, input/output and flow. Nassi-Shneiderman diagrams express sequence, selection and repetition as structured blocks. They remain pedagogically useful, although larger programs are usually reasoned about through source code, pseudocode, state diagrams and other models.

C exposes low-level details such as addresses and object representation with relatively little runtime machinery. C++ grew historically from C but is not merely C with classes; it has its own type system, object model, templates, resource-management idioms and standard library.

## Unit 2: Structure of a C Program

### Minimal program and `main`

A hosted C program can begin with:

```c
int main(void)
{
    return 0;
}
```

Two common standard forms are:

```c
int main(void)
```

and:

```c
int main(int argc, char *argv[])
```

`void main()` is not the portable standard form for a hosted C program.

### Translation process

A simplified toolchain is:

```text
Source code
   ↓
Preprocessing
   ↓
Compilation
   ↓
Assembly
   ↓
Object code
   ↓
Linking
   ↓
Executable program
```

A compiler driver may combine these stages behind one command:

```bash
cc program.c -o program
```

Understanding the stages matters when diagnosing preprocessing errors, missing declarations, unresolved external symbols or ABI mismatches.

### Header files

Standard library declarations are made visible through headers such as:

```c
#include <stdio.h>
```

Project headers are commonly included with quotes:

```c
#include "my_header.h"
```

Headers should normally contain declarations and definitions that are safe to include from multiple translation units; ordinary externally linked object definitions belong in a `.c` file.

### Comments

Traditional C comments use:

```c
/* comment */
```

C99 standardized line comments:

```c
// comment
```

A useful comment explains purpose, constraint or non-obvious reasoning instead of repeating the statement below it.

### Fundamental data types

Core types include:

```text
char
short
int
long
long long
float
double
long double
_Bool / bool
```

The exact width of ordinary integer types is implementation-dependent within the constraints of the standard. `sizeof(int)` is commonly 4 on contemporary systems but C does not require `int` to be exactly 32 bits.

When an exact-width integer type exists, `<stdint.h>` can provide names such as:

```c
int32_t
uint32_t
int64_t
uint64_t
```

`sizeof(char)` is always 1 by definition. A C byte is not defined as universally eight bits; `CHAR_BIT` reports the implementation's number of bits per byte and is at least 8.

C23 made `bool`, `true` and `false` core-language spellings. Older code commonly obtains Boolean support through `<stdbool.h>` and `_Bool`.

### Declarations, initialization and constants

```c
int count;
double average;
char grade;
```

Automatic local objects must not be read before they have an initialized value. Initialization can be explicit:

```c
int count = 0;
double pi = 3.141592653589793;
```

Literal constants include integers, floating-point values, character constants and string literals. A read-only object can be declared with `const`:

```c
const int max_count = 100;
```

In C, a `const` object is not automatically an integer constant expression in every context. Preprocessor constants are also possible:

```c
#define MAX_COUNT 100
```

Macros are textual preprocessing constructs and therefore do not carry normal object type semantics.

### Scope, storage duration and linkage

A block-scope automatic variable exists according to its storage duration and is named only within its scope:

```c
void f(void)
{
    int x = 10;
}
```

A file-scope object can have external linkage. Excessive global state introduces hidden dependencies and should be used deliberately.

`static` has different effects depending on context. At block scope it gives an object static storage duration while retaining block scope. At file scope it gives an identifier internal linkage:

```c
static int value;
```

`extern` can declare an externally linked object defined elsewhere:

```c
extern int global_counter;
```

The historical `register` storage-class specifier was an optimization hint. Modern optimizing compilers perform register allocation themselves; `register` is not a dependable performance mechanism.

## Unit 3: Operators and Program Control

### Arithmetic and integer division

C's arithmetic operators include:

```text
+  -  *  /  %
```

Integer division discards the fractional part toward zero:

```c
7 / 2
```

produces `3`. With floating operands:

```c
7.0 / 2.0
```

produces `3.5`.

### Increment, decrement and compound assignment

```c
++i;
--i;
i++;
i--;
```

Prefix and postfix forms differ when the value of the expression itself matters. Code should avoid modifying the same scalar object multiple times in an expression when sequencing does not make the behavior well-defined.

Compound assignment includes:

```c
x += 5;
x -= 2;
x *= 3;
x /= 4;
```

It expresses update operations directly and evaluates the left operand according to the compound-assignment rules rather than being merely textual substitution.

### Comparison, logical and bitwise operators

Comparison operators are `==`, `!=`, `<`, `>`, `<=` and `>=`. Assignment and comparison are distinct:

```c
x = 5;   /* assignment */
x == 5   /* comparison */
```

Logical operators are:

```text
&&  logical AND
||  logical OR
!   logical NOT
```

`&&` and `||` short-circuit. This makes guards such as the following valid:

```c
if (p != NULL && *p > 0) {
    ...
}
```

Bitwise operators are:

```text
&   bitwise AND
|   bitwise OR
^   bitwise XOR
~   complement
<<  left shift
>>  right shift
```

They are central to masks, flags, protocols, device registers and compact representations. Logical `&&` and bitwise `&` are not interchangeable.

### `if`, conditional operator and `switch`

`if` selects a path based on a scalar condition. Nested conditions are legal, but excessive nesting makes invariants difficult to see. The conditional operator:

```c
condition ? value1 : value2
```

is an expression and is useful when the result is naturally a value.

`switch` selects among integral or enumeration case labels:

```c
switch (command) {
case 1:
    ...
    break;
case 2:
    ...
    break;
default:
    ...
    break;
}
```

Unless control is deliberately transferred, falling through from one case to another should be explicit and understandable.

### `for`, `while` and `do-while`

A `for` loop separates initialization, condition and iteration expression:

```c
for (int i = 0; i < n; ++i) {
    ...
}
```

A `while` loop is suitable when continuation depends on a condition not naturally described as a counter. `do-while` evaluates its condition after the body and therefore executes the body at least once.

`break` exits the nearest loop or `switch`. `continue` skips to the next loop iteration. `goto` exists and has legitimate low-level uses, especially structured cleanup in C, but should not replace ordinary structured control flow.

## Unit 4: Arrays and Strings

### One-dimensional arrays

```c
int values[5];
```

creates five `int` elements indexed from `0` through `4`. Array bounds are not automatically checked by the language. Access outside the array is undefined behavior.

Initialization can be explicit:

```c
int values[] = {10, 20, 30, 40};
```

Within the same scope where the object is truly an array, element count can be computed as:

```c
sizeof values / sizeof values[0]
```

This does not work after an array parameter has adjusted to a pointer.

### Multidimensional arrays

```c
int matrix[3][4];
```

is an array of three arrays of four `int`. C stores ordinary multidimensional arrays contiguously in row-major order. Matrix operations should respect dimensions and avoid integer overflow in index calculations.

A matrix addition requires matching dimensions. Matrix multiplication requires the number of columns of the left matrix to match the number of rows of the right matrix; each result element is a dot product.

### C strings

A C string is a sequence of `char` terminated by a zero byte:

```c
char text[] = "hello";
```

The array contains the five visible characters plus `\0`. This convention makes correct capacity management essential.

### Reading text safely

An unbounded input routine must not be used with a fixed buffer. `fgets` accepts the buffer capacity:

```c
char line[128];
if (fgets(line, sizeof line, stdin) != NULL) {
    ...
}
```

The retained newline, end-of-file behavior and input truncation must be handled according to the application's contract.

### String library operations

`<string.h>` provides functions such as:

```text
strlen
strcpy / strncpy
strcat / strncat
strcmp
memcpy
memmove
memset
memcmp
```

Their preconditions matter. `strcpy` and `strcat` require a destination with enough space. `strncpy` is not a general safe-string replacement because its null-termination behavior depends on the source length. Buffer size should be tracked explicitly.

`strlen` counts characters before the terminating zero byte and therefore requires a valid null-terminated string. `strcmp` performs lexicographic byte comparison and returns a value less than, equal to or greater than zero; code should not depend on a particular nonzero magnitude.

## Unit 5: Pointers and Memory

### Pointer concept

A pointer stores an address suitable for referring to an object or function of the corresponding pointer type:

```c
int x = 10;
int *p = &x;
```

`&x` obtains the address of `x`, and `*p` accesses the pointed-to object:

```c
*p = 20;
```

### Null pointers

A null pointer represents no object/function target:

```c
int *p = NULL;
```

Dereferencing a null pointer is undefined behavior. A pointer must also refer to a live object with a compatible effective type and sufficient bounds before it can safely be dereferenced.

### Pointer arithmetic

Pointer arithmetic is defined relative to array objects. If `p` points to an array element, `p + 1` points to the next element, advancing by `sizeof *p` bytes at the machine-address level. Valid arithmetic is constrained to the array object and the one-past-the-end position; the one-past pointer may be formed and compared but not dereferenced.

### Arrays and pointers

In many expressions an array expression converts to a pointer to its first element, but an array is not itself a pointer. This distinction is visible in `sizeof`, object lifetime and assignment rules.

```c
for (int *p = values; p != values + n; ++p) {
    use(*p);
}
```

is one pointer-based traversal pattern.

Pointer arrays and pointers to pointers serve different layouts:

```c
char *argv[];
char **p;
```

Their type relationships should be understood rather than inferred from syntax alone.

### Dynamic allocation

`<stdlib.h>` provides:

```c
malloc
calloc
realloc
free
```

Example:

```c
int *values = malloc(n * sizeof *values);
if (values == NULL) {
    /* allocation failure */
}
```

Before multiplying element count by element size, robust code considers whether the multiplication can overflow `size_t`.

`calloc` allocates space for an array and initializes all bytes to zero. Byte-wise zero is not a portable guarantee for every possible semantic representation in C, although it produces zero for the ordinary integer types and null characters.

A safe `realloc` pattern keeps the original pointer until success is known:

```c
int *tmp = realloc(values, new_count * sizeof *values);
if (tmp != NULL) {
    values = tmp;
}
```

If `realloc` fails for a nonzero requested size, the original allocation remains valid.

Common memory errors include leaks, double `free`, use-after-free, out-of-bounds access, returning pointers to expired automatic objects and using uninitialized pointers. These are semantic failures; successful compilation does not make them safe.

## Unit 6: Functions

### Declarations and definitions

A prototype makes a function interface visible before use:

```c
int sum(int a, int b);
```

The definition provides the body:

```c
int sum(int a, int b)
{
    return a + b;
}
```

Consistent declarations across translation units are essential for type-correct calls and ABI compatibility.

### `void` functions and parameter passing

A function that does not return a value can use `void`:

```c
void print_status(int status)
{
    ...
}
```

C passes arguments by value. To let a function modify a caller-owned object, the caller passes its address:

```c
void set_zero(int *value)
{
    if (value != NULL) {
        *value = 0;
    }
}
```

This is still value passing: the pointer value itself is copied.

Array parameters adjust to pointer parameters, so the length or another boundary normally has to be passed separately:

```c
int sum_array(const int *values, size_t count);
```

`const` communicates that the function does not modify elements through that pointer. It is an interface guarantee at the type level, not a statement that the underlying object can never be modified through any alias.

### Static local state and recursion

A `static` local retains its stored value across calls. It can be useful but introduces hidden state that affects reentrancy and concurrency.

Recursion is a natural representation for some tree, divide-and-conquer and mathematical problems. Every recursive design needs a base case and progress toward it. Stack depth is finite; tail recursion is not guaranteed to be optimized by C implementations.

## Unit 7: Structures, Unions and Bit-fields

### Structures

A structure groups named fields:

```c
struct point {
    int x;
    int y;
};
```

`typedef` can introduce a convenient alias:

```c
typedef struct point Point;
```

Initialization may be positional or designated where supported by the applicable C standard:

```c
struct point p = { .x = 10, .y = 20 };
```

A structure pointer uses `->`:

```c
p_ptr->x
```

Structures can be passed by value, but passing a pointer is often appropriate for large objects or in-place modification. Arrays of structures provide contiguous records and are often cache-friendly for record-oriented traversal.

### Layout and padding

Structure fields may contain padding inserted to satisfy alignment requirements. Therefore `sizeof(struct type)` is not necessarily the sum of member sizes. Raw structure bytes are not automatically a stable disk or network format because padding, alignment, endianness and member representation can vary.

### Unions

A union overlays members in one storage region:

```c
union value {
    int i;
    double d;
};
```

Its size is sufficient for its largest member plus any required alignment. Unions are useful in tagged representations when the program separately records which alternative is active.

### Bit-fields

Bit-fields can describe compact fields in an implementation-defined layout:

```c
struct flags {
    unsigned ready : 1;
    unsigned mode  : 3;
};
```

Allocation order, packing and some type details are implementation-defined. Bit-fields are therefore poor substitutes for explicitly encoded portable wire formats.

## Unit 8: File I/O and the Standard Library

### Opening and closing files

`fopen` returns a `FILE *` stream:

```c
FILE *fp = fopen("data.txt", "r");
if (fp == NULL) {
    ...
}
```

Common modes include read, write, append and their update/binary variants. The exact combination determines whether an existing file is truncated, preserved or required to exist.

A successfully opened stream should be closed when ownership ends:

```c
if (fclose(fp) != 0) {
    ...
}
```

### Character, line and formatted I/O

`fgetc`/`fputc` handle characters. `fgets` reads a bounded line fragment. `fprintf` and `fscanf` perform formatted I/O; format strings are part of the type contract and must match argument types.

Input parsing should test return values. A failed conversion is different from end-of-file, and partial input can leave unread characters in the stream.

### Binary I/O

`fread` and `fwrite` move arrays of bytes or objects:

```c
size_t n = fwrite(buffer, sizeof buffer[0], count, fp);
```

Writing a `struct` directly to a file can be valid for a private, same-build temporary format, but it is not a portable serialization scheme. Stable formats should define field widths, byte order, encoding and versioning explicitly.

### File position and file operations

`fseek`, `ftell` and `rewind` support file positioning subject to the stream mode and platform rules. `remove` and `rename` provide standard file operations, but atomicity and replacement semantics can still depend on the operating environment.

### Character, mathematics, date and time libraries

`<ctype.h>` classifies and converts character codes. Except for `EOF`, values passed to functions such as `isalpha` should be representable as `unsigned char`.

`<math.h>` provides mathematical functions such as `sqrt`, `sin`, `cos`, `log` and `pow`. Floating-point domain/range behavior and error reporting must be considered where numerical robustness matters.

`<time.h>` provides calendar and processor-time facilities. Date/time work should distinguish elapsed monotonic time from civil/calendar time; the standard library's facilities and platform APIs have different guarantees.

## Unit 9: The C Preprocessor

### Preprocessing

Preprocessing occurs before translation of the resulting C tokens into the program proper. Directives begin with `#` at the preprocessing level.

### Object-like macros

```c
#define BUFFER_SIZE 4096
```

performs token substitution. Parenthesized typed constants, `const` objects or enumeration constants are often preferable when ordinary language semantics can express the requirement.

### Function-like macros

```c
#define SQUARE(x) ((x) * (x))
```

requires defensive parentheses, but even then `SQUARE(i++)` has multiple side effects because the argument is substituted more than once. Inline functions are often safer when type semantics and single evaluation are desired.

### Stringification and token pasting

`#` stringifies a macro argument and `##` pastes preprocessing tokens. These features are powerful for metaprogramming and generated declarations but can make diagnostics and control flow harder to follow.

### `#undef` and conditional compilation

A macro can be removed with:

```c
#undef NAME
```

Conditional compilation uses directives such as:

```c
#if
#ifdef
#ifndef
#elif
#else
#endif
```

They are useful for platform differences, feature configuration and build-time selection. Excessive conditional compilation can fragment one source file into many hard-to-test variants.

### Header guards

A traditional guard is:

```c
#ifndef MY_HEADER_H
#define MY_HEADER_H

/* declarations */

#endif
```

It prevents repeated inclusion from producing duplicate declarations or definitions. Many compilers also support `#pragma once`, but the macro guard is the portable language-toolchain convention.

## Cite This Work

Köker, M. A. (2014). C Programming Fundamentals. alikoker.com.tr. https://alikoker.com.tr/en/c-programming-fundamentals

- BibTeX: https://alikoker.com.tr/en/c-programming-fundamentals.bib
- RIS: https://alikoker.com.tr/en/c-programming-fundamentals.ris
- CSL-JSON: https://alikoker.com.tr/en/c-programming-fundamentals.csl.json
