pointers in C practice swap arrays strings Fedora gcc programs

Pointers in C practice — 4 real programs to stop being afraid of them

In the previous article we built the mental model for pointers in C. Now it’s time to write real programs and see them doing useful work. In this article we build four programs from scratch in gedit and compile them with gcc in Fedora — each one uses pointers in a situation where they’re the natural, correct tool.

Set up your workspace:

cd ~/GCID/IC2/Labs
mkdir Lab_pointers
cd Lab_pointers

Pointers in C practice — Program 1: Variable swap

The classic pointer program. Swapping two variables is impossible without pointers (or a temporary variable inside the function) because C passes everything by value. This program shows exactly why.

touch swap.c
gedit swap.c &
#include <stdio.h>

/* WRONG — without pointers, values don't change outside */
void swap_wrong(int a, int b) {
    int temp = a;
    a = b;
    b = temp;
    printf("Inside wrong swap: a=%d, b=%d\n", a, b);
}

/* CORRECT — with pointers, modifies originals */
void swap_correct(int *a, int *b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

/* Swap doubles */
void swap_double(double *a, double *b) {
    double temp = *a;
    *a = *b;
    *b = temp;
}

int main() {
    printf("=== VARIABLE SWAP ===\n\n");

    /* Integer swap */
    int x = 5, y = 10;
    printf("Before wrong swap:   x=%d, y=%d\n", x, y);
    swap_wrong(x, y);
    printf("After wrong swap:    x=%d, y=%d\n\n", x, y);

    printf("Before correct swap: x=%d, y=%d\n", x, y);
    swap_correct(&x, &y);
    printf("After correct swap:  x=%d, y=%d\n\n", x, y);

    /* Double swap */
    double a = 3.14, b = 2.71;
    printf("Before: a=%.2f, b=%.2f\n", a, b);
    swap_double(&a, &b);
    printf("After:  a=%.2f, b=%.2f\n\n", a, b);

    /* Practical use — sort three numbers */
    int p = 7, q = 2, r = 9;
    printf("Before sort: %d %d %d\n", p, q, r);

    /* Bubble sort three values */
    if (p > q) swap_correct(&p, &q);
    if (q > r) swap_correct(&q, &r);
    if (p > q) swap_correct(&p, &q);

    printf("After sort:  %d %d %d\n", p, q, r);

    return 0;
}
gcc swap.c -o swap -Wall
./swap

Output:

=== VARIABLE SWAP ===

Before wrong swap:   x=5, y=10
Inside wrong swap: a=10, b=5
After wrong swap:    x=5, y=10

Before correct swap: x=5, y=10
After correct swap:  x=10, y=5

Before: a=3.14, b=2.71
After:  a=2.71, b=3.14

Before sort: 7 2 9
After sort:  2 7 9

The wrong swap is the most instructive part of this program. swap_wrong receives copies of x and y — it swaps those copies perfectly, but the originals in main are untouched. swap_correct receives the addresses of x and y — it follows those addresses and swaps the actual values in memory. That’s the entire difference between pass-by-value and pass-by-pointer.

Pointers in C practice — Program 2: Array traversal with pointer arithmetic

This program shows that array indexing (arr[i]) and pointer arithmetic (*(arr+i)) are two ways of expressing exactly the same thing.

touch traversal.c
gedit traversal.c &
#include <stdio.h>

void print_with_index(int *arr, int n) {
    printf("With index notation:\n");
    for (int i = 0; i < n; i++) {
        printf("  arr[%d] = %d  (address: %p)\n",
               i, arr[i], &arr[i]);
    }
}

void print_with_pointer(int *arr, int n) {
    printf("With pointer arithmetic:\n");
    int *p = arr;
    for (int i = 0; i < n; i++) {
        printf("  *(arr+%d) = %d  (address: %p)\n",
               i, *(arr+i), (arr+i));
    }
}

void print_walking_pointer(int *arr, int n) {
    printf("Walking pointer:\n");
    int *p = arr;    /* start at beginning */
    int i = 0;
    while (p < arr + n) {    /* while not past the end */
        printf("  [%d] = %d\n", i, *p);
        p++;
        i++;
    }
}

double calculate_average(double *arr, int n) {
    double sum = 0;
    for (double *p = arr; p < arr + n; p++) {
        sum += *p;
    }
    return sum / n;
}

void reverse_array(int *arr, int n) {
    int *left  = arr;           /* pointer to first element */
    int *right = arr + n - 1;  /* pointer to last element */

    while (left < right) {
        int temp = *left;
        *left  = *right;
        *right = temp;
        left++;
        right--;
    }
}

int main() {
    printf("=== ARRAY TRAVERSAL ===\n\n");

    int numbers[6] = {10, 20, 30, 40, 50, 60};
    int n = 6;

    print_with_index(numbers, n);
    printf("\n");
    print_with_pointer(numbers, n);
    printf("\n");
    print_walking_pointer(numbers, n);

    /* Average with pointer */
    double grades[5] = {7.5, 8.0, 6.5, 9.0, 5.5};
    printf("\nGrades average: %.2f\n",
           calculate_average(grades, 5));

    /* Reverse */
    printf("\nBefore reverse: ");
    for (int i = 0; i < n; i++) printf("%d ", numbers[i]);

    reverse_array(numbers, n);

    printf("\nAfter reverse:  ");
    for (int i = 0; i < n; i++) printf("%d ", numbers[i]);
    printf("\n");

    return 0;
}
gcc traversal.c -o traversal -Wall
./traversal

Output:

=== ARRAY TRAVERSAL ===

With index notation:
  arr[0] = 10  (address: 0x7ffc...)
  arr[1] = 20  (address: 0x7ffc...+4)
  ...

With pointer arithmetic:
  *(arr+0) = 10  (address: 0x7ffc...)
  *(arr+1) = 20  (address: 0x7ffc...+4)
  ...

Grades average: 7.30

Before reverse: 10 20 30 40 50 60
After reverse:  60 50 40 30 20 10

Notice in the addresses that each consecutive element is 4 bytes further — that’s the size of int. The while (p < arr + n) pattern in print_walking_pointer is a very common C idiom — compare two pointers to know when you’ve reached the end of the array.

The reverse_array function with two pointers moving toward each other is a classic algorithm. left starts at the beginning, right at the end, they swap and move inward until they meet. No index arithmetic needed — just pointer comparison and movement.

Pointers in C practice — Program 3: Functions returning multiple values through pointers

A function can only return one value with return. Pointers let a function “return” multiple values by writing directly to addresses passed by the caller.

touch multi_return.c
gedit multi_return.c &
#include <stdio.h>

/* Divide and get both quotient and remainder */
void divide_with_remainder(int dividend, int divisor,
                            int *quotient, int *remainder) {
    if (divisor == 0) {
        *quotient  = 0;
        *remainder = 0;
        printf("Error: division by zero\n");
        return;
    }
    *quotient  = dividend / divisor;
    *remainder = dividend % divisor;
}

/* Convert seconds to hours, minutes, seconds */
void seconds_to_time(int total_seconds,
                     int *hours, int *minutes, int *seconds) {
    *hours   = total_seconds / 3600;
    *minutes = (total_seconds % 3600) / 60;
    *seconds = total_seconds % 60;
}

/* Statistics — min, max, sum, average through pointers */
void statistics(double *arr, int n,
                double *min, double *max,
                double *sum, double *average) {
    *min = arr[0];
    *max = arr[0];
    *sum = 0;

    for (int i = 0; i < n; i++) {
        if (arr[i] < *min) *min = arr[i];
        if (arr[i] > *max) *max = arr[i];
        *sum += arr[i];
    }
    *average = *sum / n;
}

/* Quadratic equation — returns number of solutions */
int solve_quadratic(double a, double b, double c,
                    double *x1, double *x2) {
    double discriminant = b*b - 4*a*c;

    if (discriminant < 0) {
        return 0;    /* no real solutions */
    } else if (discriminant == 0) {
        *x1 = -b / (2*a);
        *x2 = *x1;
        return 1;    /* one solution */
    } else {
        double sqrt_d = 0;
        /* Manual square root — Newton's method */
        double guess = discriminant;
        for (int i = 0; i < 100; i++)
            guess = (guess + discriminant/guess) / 2;
        sqrt_d = guess;

        *x1 = (-b + sqrt_d) / (2*a);
        *x2 = (-b - sqrt_d) / (2*a);
        return 2;    /* two solutions */
    }
}

int main() {
    printf("=== MULTIPLE RETURN VALUES ===\n\n");

    /* Division */
    int q, r;
    divide_with_remainder(17, 5, &q, &r);
    printf("17 / 5 = %d remainder %d\n\n", q, r);

    /* Time conversion */
    int h, m, s;
    seconds_to_time(3723, &h, &m, &s);
    printf("3723 seconds = %dh %dm %ds\n\n", h, m, s);

    /* Statistics */
    double grades[6] = {7.5, 8.0, 6.5, 9.0, 5.5, 7.0};
    double min, max, sum, avg;
    statistics(grades, 6, &min, &max, &sum, &avg);
    printf("Statistics:\n");
    printf("  Min:     %.2f\n", min);
    printf("  Max:     %.2f\n", max);
    printf("  Sum:     %.2f\n", sum);
    printf("  Average: %.2f\n\n", avg);

    /* Quadratic equation */
    double x1, x2;
    int solutions = solve_quadratic(1, -5, 6, &x1, &x2);
    printf("x² - 5x + 6 = 0\n");
    printf("Solutions: %d\n", solutions);
    if (solutions >= 1) printf("x1 = %.2f\n", x1);
    if (solutions >= 2) printf("x2 = %.2f\n", x2);

    return 0;
}
gcc multi_return.c -o multi_return -Wall
./multi_return

Output:

=== MULTIPLE RETURN VALUES ===

17 / 5 = 3 remainder 2

3723 seconds = 1h 2m 3s

Statistics:
  Min:     5.50
  Max:     9.00
  Sum:     43.50
  Average: 7.25

x² - 5x + 6 = 0
Solutions: 2
x1 = 3.00
x2 = 2.00

This pattern — using the return value for status/count and pointers for the actual results — is fundamental in C. solve_quadratic returns 0, 1 or 2 telling you how many solutions exist, while the actual solution values arrive through the pointer parameters. statistics returns void because all five results go through pointers.

Pointers in C practice Program 4: String manipulation with char pointers

Strings in C are char arrays — which means everything you know about pointers applies to strings. This program shows the most useful string operations using pointer-based traversal.

touch strings_pointers.c
gedit strings_pointers.c &
#include <stdio.h>

/* String length — count chars until null terminator */
int my_strlen(const char *s) {
    const char *p = s;
    while (*p != '\0') p++;
    return p - s;    /* pointer subtraction = number of elements between */
}

/* Count occurrences of a character */
int count_char(const char *s, char target) {
    int count = 0;
    while (*s != '\0') {
        if (*s == target) count++;
        s++;
    }
    return count;
}

/* Count vowels */
int count_vowels(const char *s) {
    int count = 0;
    while (*s != '\0') {
        char lower = (*s >= 'A' && *s <= 'Z') ? *s + 32 : *s;
        if (lower=='a'||lower=='e'||lower=='i'||lower=='o'||lower=='u')
            count++;
        s++;
    }
    return count;
}

/* Convert to uppercase in place */
void to_uppercase(char *s) {
    while (*s != '\0') {
        if (*s >= 'a' && *s <= 'z')
            *s = *s - 32;    /* lowercase to uppercase: subtract 32 */
        s++;
    }
}

/* Reverse string in place */
void reverse_string(char *s) {
    char *left  = s;
    char *right = s;

    /* Find end of string */
    while (*right != '\0') right++;
    right--;    /* point to last character before '\0' */

    /* Swap from outside in */
    while (left < right) {
        char temp = *left;
        *left  = *right;
        *right = temp;
        left++;
        right--;
    }
}

/* Check if palindrome */
int is_palindrome(const char *s) {
    const char *left  = s;
    const char *right = s;
    while (*right != '\0') right++;
    right--;

    while (left < right) {
        if (*left != *right) return 0;
        left++;
        right--;
    }
    return 1;
}

int main() {
    printf("=== STRING MANIPULATION ===\n\n");

    char text[] = "Hello World";
    printf("Original:   \"%s\"\n", text);
    printf("Length:     %d\n", my_strlen(text));
    printf("Vowels:     %d\n", count_vowels(text));
    printf("'l' count:  %d\n\n", count_char(text, 'l'));

    /* Uppercase */
    char text2[] = "Hello World";
    to_uppercase(text2);
    printf("Uppercase:  \"%s\"\n", text2);

    /* Reverse */
    char text3[] = "Hello World";
    reverse_string(text3);
    printf("Reversed:   \"%s\"\n\n", text3);

    /* Palindrome check */
    char words[4][20] = {"racecar", "hello", "level", "world"};
    printf("Palindrome check:\n");
    for (int i = 0; i < 4; i++) {
        printf("  \"%s\": %s\n", words[i],
               is_palindrome(words[i]) ? "Yes" : "No");
    }

    /* Pointer walk through string */
    printf("\nCharacter by character:\n");
    char *p = text2;
    int pos = 0;
    while (*p != '\0') {
        if (*p != ' ')
            printf("  [%d] '%c' = ASCII %d\n", pos, *p, *p);
        p++;
        pos++;
    }

    return 0;
}
gcc strings_pointers.c -o strings_pointers -Wall
./strings_pointers

Output:

=== STRING MANIPULATION ===

Original:   "Hello World"
Length:     11
Vowels:     3
'l' count:  3

Uppercase:  "HELLO WORLD"
Reversed:   "dlroW olleH"

Palindrome check:
  "racecar": Yes
  "hello": No
  "level": Yes
  "world": No

Character by character:
  [0] 'H' = ASCII 72
  [1] 'E' = ASCII 69
  ...

my_strlen shows pointer subtraction — p - s gives the number of elements between two pointers, which is exactly the string length. reverse_string uses the same two-pointer technique as reverse_array — it’s the same algorithm regardless of data type. to_uppercase modifies the string in place through the pointer, subtracting 32 from each lowercase letter (the ASCII distance between ‘a’ and ‘A’).

Visualise with Python Tutor

Select C from the dropdown and paste in pythontutor.com:

#include <stdio.h>

void swap(int *a, int *b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

int main() {
    int x = 5, y = 10;
    int *p = &x;
    int *q = &y;

    printf("Before: x=%d y=%d\n", x, y);
    swap(p, q);
    printf("After:  x=%d y=%d\n", x, y);

    int arr[3] = {10, 20, 30};
    int *ptr = arr;
    for (int i = 0; i < 3; i++) {
        printf("*(ptr+%d) = %d\n", i, *(ptr+i));
    }
    return 0;
}

Watch four key things step by step. When p = &x is assigned, Python Tutor draws an arrow from p to x — that’s what a pointer looks like in memory. When swap(p, q) is called, the function receives copies of p and q — but those copies still point to the original x and y. Inside swap, *a = *b follows the arrow to x and writes y‘s value there. When you look at the array, notice ptr points to arr[0] and *(ptr+i) steps through the array without ptr itself moving — the arithmetic happens in the expression, not in the pointer variable.

Summary and next step

In this article you practised C pointers with four real programs. You used pointers to swap values between functions, traverse arrays with pointer arithmetic, return multiple values from a single function, and manipulate strings character by character. The pattern in every program was the same: take an address, follow it, read or write the value there.

In the next article you’ll find exercises to solve on your own in Fedora.

Similar Posts

2 Comments

Leave a Reply

Your email address will not be published. Required fields are marked *