Skip to main content

Exercises (Part 3)

info

For exercises on topics before pointers, refer to C++ exercises Part 1 and Part 2 (the solutions will be pretty similar to the relative C implementation).

If you come from C++, this table will help you quickly check the main differences and similarities between the two languages:

AspectCC++
Input/Outputprintf, scanf, puts, getsstd::cout, std::cin, std::getline, std::endl
Type casting(int)xstatic_cast<int>(x), etc.
struct and typedeftypedef struct {...} Name; struct Name varstruct Name { ... }; Name var
Standard Libraries<stdio.h>, <stdlib.h>, <string.h><iostream>, <vector>, <string>, <algorithm>
Dynamic allocationmalloc, calloc, realloc, freenew, delete
NamespacesNot supportedusing namespace std
Error handlingErrno, return -1, perror()Try-catch

Now that you know about pointers and memory allocation, let's practice with some exercises!

In this section, you'll find exercises ranging from basic pointer usage to more complex memory allocation scenarios. For each exercise, one or multiple solutions are provided, but try to solve them yourself first.

A difficulty is assigned to each exercise using a 3-star rating. Keep in mind that this is a subjective rating and might not accurately reflect your personal experience.

Basic pointer (arrays & strings) exercises

Note:
The following exercises are designed for the use of automatically allocated arrays and strings, without using dynamic allocation with malloc. Therefore, assume that all pointers used already refer to valid memory areas of appropriate size.

Finding the maximum value (★☆☆)

Implement a function to find the maximum value in an integer array using the following prototype:

void find_max(int *rmax, int *values, unsigned size);

Where:

  • values is a pointer to the array
  • size is the size of the array
  • rmax is a pointer to the element in the array that represents the maximum value

You can assume that the array pointer is valid, the size is greater than 0, and is consistent with the actual values in the array.

Remember that `rmax` is a pointer - you need to store the actual maximum value at the memory location it points to using the dereference operator (*).
Click to reveal hint
Show solution
#include <stdio.h>
#include <stdlib.h>
#define SIZE 100 // Array test size

void find_max(int *rmax, int *values, unsigned size) {
for (unsigned int i = 0; i < size; i++) {
if (*rmax < *(values + i)) {
*rmax = *(values + i);
}
}
}

int main() {
int a[SIZE];
for (int i = 0; i < SIZE; i++) {
a[i] = i;
}
int max_a = a[0]; // max_a contains the value of the 1st element

find_max(&max_a, a, SIZE); // Pass address, array and size
printf("Max: %d\nAddress: %p\n", max_a, &max_a);

return 0;
}

This solution uses pointer arithmetic and assumes *rmax is pre-initialized and uses pointer arithmetic (*(values + i)).

Note
Why use a constant size with SIZE?

We use a constant size defined by the macro SIZE because array declarations in C require a compile-time constant size. Without this, the compiler would throw an error like "expression must have a constant value".

Why pass &max_a and a to find_max()?

In the find_max() function:

  • We pass &max_a (address of max_a) because we want the function to modify the original variable in the calling function.
  • For the array a, we pass it directly because arrays in C automatically decay to pointers to their first element when passed to functions, so a is equivalent to &a[0].
What happens if you pass &a instead of the array a?

If you pass &a instead of a, the function would receive a pointer to the entire array (int (*)[SIZE]), not a pointer to its first element (int *). This wouldn't match the function's parameter type, as the function expects a pointer to an int, not a pointer to an array.

Why populate the array with index values?

We populate the array with index values for simplicity. This approach ensures the array is predictable and easy to test, but the solutions will work with any array values.

Finding minimum and maximum values (★☆☆)

Implement a function similar to the previous one, but finding both the minimum and maximum values. The function should follow this prototype:

void find_minmax(int *rmin, int *rmax, int *values, unsigned size);

Where:

  • values is a pointer to the array
  • size is the size of the array
  • rmin is a pointer to the element representing the minimum value
  • rmax is a pointer to the element representing the maximum value
Show solution
void find_minmax(int *rmin, int *rmax, int *values, unsigned size) {
// Initialize rmin and rmax to the first element
*rmin = values[0]; // With array indexing OR
*rmax = *values; // With pointer arithmetic

// Iterate through the array to find min and max
for (unsigned i = 1; i < size; i++) {
if (values[i] < *rmin) {
*rmin = values[i]; // With array indexing OR
}
if (*(values + i) > *rmax) {
*rmax = *(values + i); // With pointer arithmetic
}
}
}

Array indexing (values[i]) and pointer arithmetic (*(values + i)) are interchangeable in C, as both access the same memory location (the first element plus an offset).

Computing mean and variance (★☆☆)

Implement a function to calculate the arithmetic mean and variance of a set of values, using the following prototype:

void compute_mean_variance(float *rmean, float *rvariance, float *values, unsigned size);

Where:

  • values is a pointer to the array
  • size is the size of the array
  • rmean is a pointer to store the mean value
  • rvariance is a pointer to store the variance value

Remember that the variance is calculated as:

σ2=1ni=1n(xiμ)2\sigma^2 = \frac{1}{n}\sum_{i=1}^{n}(x_i - \mu)^2

where μ\mu is the mean and nn is the number of values.

For computing variance efficiently, you should first calculate the mean, then in a second pass calculate the sum of squared differences from that mean.
Click to reveal hint
Show solution
#include <math.h>
#include <stdio.h>
#include <stdlib.h>

void compute_mean_variance(float *rmean, float *rvariance, float *values, unsigned size) {
// Calculate mean (make sure it's already initialized at 0)
for (int i = 0; i < size; i++) {
*rmean += values[i];
}
*rmean /= (float)size;

// Calculate variance
for (int i = 1; i <= size; i++) { // Or start at 0 and < size
*rvariance += pow(values[i - 1] - *rmean, 2);
}
*rvariance /= (float)size;
}

int main() {
float a[100];
for (unsigned i = 0; i < 100; i++) {
a[i] = i;
}
float mean = .0f, variance = .0f;

// Pass 'a' directly (equivalent to &a[0]) because arrays decay to pointers when used in an expression
compute_mean_variance(&mean, &variance, a, 100);
printf("Mean: %f\nVariance: %f", mean, variance);

return 0;
}

In .0f, using 'f' explicitly tells the compiler the numerical literal should be taken as a float number, instead of as a double (which is what C default to when dealing with decimal numbers).

If we don't initialize mean and variance at 0 before passing them, we must do it inside compute_mean_variance, since we are summing values to them.

In the second loop for calculating variance, the first solution starts at 1 and ends at n (including extremes) to reflect the mathematical formula, but it can also start one step ahead and adjust the index accordingly (remembering that indexes represent offsets, therefore they start from 0, not 1).

Swapping values (★☆☆)

Implement a function to swap the values of two integers using pointers. The function should have the following prototype:

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

Pointer arithmetic with arrays (★☆☆)

Write a function that takes an integer array and its size as parameters, then prints all elements using only pointer arithmetic (without using array indexing notation []). The function should have the following prototype:

void print_array(int *arr, unsigned size);
Show solution
void print_array(int *arr, unsigned size) {
int *end = arr + size; // Points just past the last element

for (int *p = arr; p < end; p++) {
printf("%d ", *p);
}
printf("\n");
}

Reverse an array (★★☆)

Write a function that reverses the elements of an array in place (without using a second array), using only pointers. The function should have the following prototype:

void reverse_array(int *arr, unsigned size);
Consider using two pointers - one starting from the beginning and one from the end of the array. Swap their values and move them toward each other until they meet in the middle.
Click to reveal hint
Show solution
void reverse_array(int *arr, unsigned size) {
int *start = arr;
int *end = arr + size - 1;

while (start < end) {
// Swap values at start and end pointers
int temp = *start;
*start = *end;
*end = temp;

// Move pointers toward the middle
start++;
end--;
}
}

Reversing an array to a new location (★☆☆)

Write a function that reverses an array of integers into another array (without modifying the original array), using the following prototype:

void reversei(int *r, const int *values, unsigned size);

Where:

  • r is a pointer to the destination array where the reversed values will be stored
  • values is a pointer to the source array
  • size is the size of both arrays
Show solution
void reversei(int *r, const int *values, unsigned size) {
for (unsigned i = 0; i < size; i++) {
r[i] = values[size - 1 - i];
}
}

// Example usage:
int main() {
int original[] = {1, 2, 3, 4, 5};
int reversed[5];
unsigned size = 5;

reversei(reversed, original, size);

printf("Original array: ");
for (unsigned i = 0; i < size; i++) {
printf("%d ", original[i]);
}

printf("\nReversed array: ");
for (unsigned i = 0; i < size; i++) {
printf("%d ", reversed[i]);
}

return 0;
}

Reversing a string (★☆☆)

Write a function that reverses a C string into another string, using the following prototype:

void reverses(char *r, const char *s);

Where:

  • r is a pointer to the destination string where the reversed string will be stored
  • s is a pointer to the source string

Try both with and without using the string.h library.

Remember that strings in C are null-terminated, so you need to find the length of the string first, then copy the characters in reverse order, and finally add the null terminator.
Click to reveal hint
Show solution
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void reverses(char *r, const char *s) {
// int length = sizeof(s) / sizeof(s[0]); // ❌ Do not do this here
int length = strlen(s); // Calculate the length of the string

// Reverse the string
for (int i = length; i > 0; i--) {
r[length - i] = s[i - 1];
}
// ↕️ or
// for (int i = 0; i < length; i++) {
// r[i] = s[length - 1 - i];
// }

r[length] = '\0'; // Null-terminate the reversed string
}

int main() {
char s[] = "Hello, World!";
int length = strlen(s); // Calculate the length of the string

printf("Original: %s\n", s);

// Allocate memory for the reversed string
char *r = (char *)malloc((length + 1) * sizeof(char)); // Include space for null terminator
if (r == NULL) {
fprintf(stderr, "Memory allocation failed\n");
return 1;
}

reverses(r, s);
printf("Reversed: %s\n", r); // Print the reversed string

free(r); // Free allocated memory

return 0;
}

Using string.h library.

Note

When an array is passed to a function in C, it "decays" into a pointer to its first element. This means operations like sizeof(array) inside the function will only return the size of the pointer itself (typically 4 or 8 bytes), not the size of the entire array.

This is why we can't calculate the array length inside the function using the sizeof(array)/sizeof(array[0]) approach that works in the calling context. Instead, we need to pass the array length as a separate parameter to the function or use strlen for strings.

Why does this happen?

The issue lies in how the two methods calculate the length of the array or string, and it depends on the type of s.

1. Using sizeof(s) / sizeof(s[0])

This method works only if s is a statically allocated array (e.g., char s[100]). The sizeof operator calculates the total size of the array in bytes (sizeof(s)) and divides it by the size of one element (sizeof(s[0])). This gives the number of elements in the array.

However, if s is a pointer (e.g., char *s), sizeof(s) will return the size of the pointer itself, not the size of the memory it points to. This makes the calculation incorrect.

2. Using strlen(s)

The strlen function works for null-terminated strings. It calculates the length of the string by iterating through the characters in s until it finds the null terminator ('\0'). This works regardless of whether s is a statically allocated array or a dynamically allocated pointer, as long as it points to a valid null-terminated string.

See this example:

#include <stdio.h>
#include <string.h>

int main() {
char s1[] = "Hello"; // Statically allocated array
char *s2 = "World"; // Pointer to a string literal

printf("Using sizeof on s1: %zu\n", sizeof(s1)); // Works, gives 6 (5 chars + '\0')
printf("Using sizeof on s2: %zu\n", sizeof(s2)); // Gives size of pointer, not the string

printf("Using strlen on s1: %zu\n", strlen(s1)); // Works, gives 5
printf("Using strlen on s2: %zu\n", strlen(s2)); // Works, gives 5

return 0;
}

ROT13 cipher (★★☆)

Write a function that implements the ROT13 cipher, which shifts each letter in a string by 13 positions in the alphabet (wrapping around if necessary). Non-alphabetic characters should remain unchanged. Use the following prototype:

void rot13(char *r, const char *s);

Where:

  • r is a pointer to the destination string where the encrypted string will be stored
  • s is a pointer to the source string

It can be helpful to refer to the ASCII table for char operations:

pointer operators

Table of ASCII values - By ASCII-Table.svg: ZZT32derivative work: Usha - ASCII-Table.svg, Public Domain, Link
The ROT13 cipher is its own inverse - applying it twice returns the original text. You'll need to handle both uppercase and lowercase letters separately.
Click to reveal hint
Show solution
#include <stdio.h>
#include <string.h>
#include <ctype.h>

void rot13(char *r, const char *s) {
for (int i = 0; s[i] != '\0'; i++) {
if (isalpha(s[i])) {
// Handle both uppercase and lowercase letters
if (islower(s[i])) {
// For lowercase: subtract 'a', add 13, take modulo 26, add 'a' back
r[i] = (char)((s[i] - 'a' + 13) % 26 + 'a');
} else if (isupper(s[i])) {
// For uppercase: subtract 'A', add 13, take modulo 26, add 'A' back
r[i] = (char)((s[i] - 'A' + 13) % 26 + 'A');
}
} else {
// Copy non-alphabetic characters as is
r[i] = s[i];
}
}

// Add null terminator at the end
r[strlen(s)] = '\0';
}

int main() {
char s[] = "example String 1!";
printf("Original: %s\n", s);

// Dynamically allocate memory for the result
char *r = (char *)malloc((strlen(s) + 1) * sizeof(char));
if (r == NULL) {
fprintf(stderr, "Memory allocation failed\n");
return 1;
}

rot13(r, s);
printf("Encrypted: %s\n", r);

// Free allocated memory
free(r);

return 0;
}

Finding a value in an array (★☆☆)

Write a function that finds the position of a specific value in an integer array, using the following prototype:

long findi(int t, const int *values, unsigned size);

Where:

  • t is the value to search for
  • values is a pointer to the array
  • size is the size of the array
  • The function should return the index of the first occurrence of the value, or -1 if not found
Show solution
long findi(int t, const int *values, unsigned size) {
for (unsigned i = 0; i < size; i++) {
if (values[i] == t) {
return i;
}
}
return -1; // Not found
}

// Example usage:
int main() {
int array[] = {10, 20, 30, 40, 50, 30, 60};
unsigned size = 7;

printf("Position of 30: %ld\n", findi(30, array, size)); // Should print 2
printf("Position of 35: %ld\n", findi(35, array, size)); // Should print -1

return 0;
}

Capitalizing words in a string (★★☆)

Write a function that converts a string so that the first letter of each word is capitalized and all other letters are lowercase. Use the following prototype:

void capitalize(char *r, const char *s);

Where:

  • r is a pointer to the destination string where the capitalized string will be stored
  • s is a pointer to the source string
A good approach is to use the C standard library functions toupper, tolower, and isspace from ctype.h library. Track whether the current character is at the start of a word (either the first character or following a space), and convert it to uppercase; otherwise, convert it to lowercase.
Another possible solution: iterate through the input string while keeping track of when a new word begins. Use toupper() for the first alphabetic character following a space (or at the start) and tolower() for all subsequent characters. Don’t forget to add a null terminator at the end.
Click to reveal hint
Show solution
#include <ctype.h>  // For toupper, tolower, isspace, isalpha
#include <stdio.h>
#include <string.h> // For strlen

// r: output string
// s: input string (const, not modified)
void capitalize(char *r, const char *s) {
// Capitalizes the first letter of each word, lowercases the rest
for (int i = 0; i < strlen(s); i++) {
if ((i == 0 || isspace(s[i - 1])) && isalpha(s[i])) {
r[i] = toupper(s[i]);
} else {
r[i] = tolower(s[i]);
}
}
// Null-terminate string (no need for -1, strlen doesn't count \0 already)
r[strlen(s)] = '\0';
}

// Example usage:
int main() {
char original[] = "aD # fAA";
printf("Original: %s\n", original);
char newString[sizeof(original)];
capitalize(newString, original);
printf("Capitalized: %s\n", newString);
return 0;
}

This solution uses standard library functions (toupper, tolower, isspace, isalpha) to check for word boundaries and convert characters as needed. It capitalizes the first letter of each word and lowercases the rest by examining each character and its context.

Counting letter frequencies (★★☆)

Write a function that counts the frequency of each letter (case-insensitive) in a string and stores the results in an array. Use the following prototype:

void freqs(unsigned *r, const char *s);

Where:

  • r is a pointer to an array of size 26, where each element will store the count of a letter (r[0] for 'a'/'A', r[1] for 'b'/'B', etc.)
  • s is a pointer to the source string
A good approach is to convert each character to lowercase (or uppercase) using tolower() or toupper() from ctype.h, then check if it's an alphabetic character with isalpha(). If it is, increment the corresponding index in the result array by subtracting 'a' from the character value (e.g., r[s[i] - 'a']++). Make sure to initialize the frequency array to zero before counting.
Click to reveal hint
Show solution
#include <ctype.h>   // for tolower, isalpha
#include <stdio.h>
#define SIZE 10

void freqs(unsigned *r, const char *s) {
// ASCII value of 'a' for reference
printf("\na = %d in ASCII", (int)'a'); // a = 97

char s_low[SIZE];
// Convert input string to lowercase in a new array
for (int i = 0; i < SIZE; i++) {
s_low[i] = tolower(s[i]);
}

// Count frequencies of alphabetic characters
for (int i = 0; i < SIZE; i++) {
if (isalpha(s_low[i])) {
// Subtract 'a' to get index (0 for 'a', 1 for 'b', ...)
r[(int)s_low[i] - 'a']++;
}
}
}

int main() {
char s[SIZE] = "abbct#tou";
unsigned r[26] = {0}; // Initialize all elements to 0

printf("String s: %s\n", s);

printf("Array r: ");
for (int i = 0; i < 26; i++) {
printf("%d", r[i]);
}

freqs(r, s);

printf("\nr freqs: ");
for (int i = 0; i < 26; i++) {
printf("%d", r[i]);
}

return 0;
}

This solution first converts the input string to lowercase using tolower() so that letter counting is case-insensitive. It then checks each character with isalpha() and increments the corresponding frequency in the result array by subtracting 'a' from the character's ASCII value.

Merging sorted arrays (★★☆)

Write a function that merges two already sorted integer arrays into a single sorted array. Use the following prototype:

void merge(int *r, const int *a1, unsigned s1, const int *a2, unsigned s2);

Where:

  • r is a pointer to the destination array where the merged result will be stored
  • a1 is a pointer to the first sorted array
  • s1 is the size of the first array
  • a2 is a pointer to the second sorted array
  • s2 is the size of the second array
Use three indices (one for each input array and one for the result). At each step, compare the current elements of both arrays and copy the smaller one to the result, incrementing the index of the array from which that element was taken. Continue until one array is exhausted, then copy any remaining elements from the other array (since they can be of different sizes, so one might terminate before the other). This approach ensures the merged array remains sorted and is efficient (O(s1 + s2) time).
Click to reveal hint
Show solution
#include <stdio.h>
#define SIZE1 10
#define SIZE2 5

// Merges two sorted arrays a1 and a2 into result array r.
// s1: size of a1, s2: size of a2
void merge(int *r, const int *a1, unsigned s1, const int *a2, unsigned s2) {
int i = 0, j = 0, k = 0;
// Merge both arrays until one is exhausted
while (i < s1 && j < s2) {
if (a1[i] <= a2[j]) {
r[k] = a1[i];
i++;
k++; // or r[k++] = a1[i++];
} else {
r[k] = a2[j];
j++;
k++;
}
}
// Copy any remaining elements from a1
while (i < s1) {
r[k] = a1[i];
i++;
k++;
}
// Copy any remaining elements from a2
while (j < s2) {
r[k] = a2[j];
j++;
k++;
}
}

int main() {
int a1[SIZE1] = {1, 2, 4, 5, 5, 6, 7, 9, 10, 20};
int a2[SIZE2] = {0, 4, 5, 6, 6};
int result[SIZE1 + SIZE2] = {0};

merge(result, a1, SIZE1, a2, SIZE2);
for (int i = 0; i < SIZE1 + SIZE2; i++) {
printf("%d ", result[i]);
}
printf("\n");

return 0;
}

Generating Fibonacci sequence (★☆☆)

Write a function that generates the first n numbers in the Fibonacci sequence. Use the following prototype:

void fibonacci(unsigned *r, unsigned n);

Where:

  • r is a pointer to the destination array where the sequence will be stored
  • n is the number of elements to generate

Remember that the Fibonacci sequence starts with 0, 1, and each subsequent number is the sum of the two preceding ones.:

Fn={0if n=01if n=1Fn1+Fn2if n2F_n = \begin{cases} 0 & \text{if } n = 0 \\ 1 & \text{if } n = 1 \\ \boxed{F_{n-1} + F_{n-2}} & \text{if } n \geq 2 \end{cases}

where FnF_n is the nn-th Fibonacci number.

Show solution
#include <stdio.h>
#define N 10

// Fills the array r with the first n Fibonacci numbers.
// Assumes r[0] and r[1] are already initialized to 0 and 1.
void fibonacci(unsigned *r, unsigned n) {
// Start from the third element and compute each Fibonacci number
for (int i = 2; i < n; i++) {
r[i] = r[i-1] + r[i-2];
}
}

int main() {
// Initialize the first two terms of the Fibonacci sequence
int a[N] = {0, 1}; // Remaining elements are set to 0 by default

fibonacci(a, N);

printf("First %u Fibonacci numbers: ", N);
for (int i = 0; i < N; i++) {
printf("%u ", a[i]);
}
printf("\n");

return 0;
}

This solution assumes the first two elements of the array are initialized to 0 and 1, and then fills the rest using the Fibonacci recurrence relation.

String Manipulation with Pointers

String length (★☆☆)

Implement your own version of the strlen function that calculates the length of a string using pointers. Your function should have this prototype:

unsigned my_strlen(const char *str);
`\0` is the character that terminates strings.
Click to reveal hint
Show solution
unsigned my_strlen(const char *str) {
const char *p = str;

// Move pointer until null terminator is found
while (*p != '\0') {
p++;
}

// Calculate the distance between the pointers
return (unsigned)(p - str);
}

String copy (★★☆)

Implement your own version of the strcpy function that copies a string to another location using pointers. Your function should have this prototype:

char *my_strcpy(char *dest, const char *src);
Remember to save the original destination pointer before modifying it, as you need to return this pointer at the end of the function.
Click to reveal hint
Show solution
char *my_strcpy(char *dest, const char *src) {
char *original_dest = dest;

// Copy each character until null terminator
while (*src != '\0') {
*dest = *src;
dest++;
src++;
}

// Add null terminator
*dest = '\0';

// Return pointer to the start of destination
return original_dest;
}

Another solution using post-increment:

char *my_strcpy(char *dest, const char *src) {
char *original_dest = dest;

while ((*dest++ = *src++) != '\0')
;

return original_dest;
}

String concatenation (★★☆)

Implement your own version of the strcat function that concatenates two strings using pointers. Your function should have this prototype:

char *my_strcat(char *dest, const char *src);
Show solution
char *my_strcat(char *dest, const char *src) {
char *original_dest = dest;

// Move to the end of dest
while (*dest != '\0') {
dest++;
}

// Copy src to the end of dest
while (*src != '\0') {
*dest = *src;
dest++;
src++;
}

// Add null terminator
*dest = '\0';

return original_dest;
}

Memory Allocation Exercises

Let's revisit some of our previous exercises with a different approach - now we'll dynamically allocate memory for the results inside the functions instead of using pre-allocated arrays.

Dynamic Array Reversal (★★☆)

Implement a function that reverses an array of integers but dynamically allocates memory for the result. The function should have this prototype:

int* reversei(const int *values, unsigned size);

Where:

  • values is a pointer to the input array
  • size is the size of the array
  • The function returns a pointer to the dynamically allocated reversed array (or NULL if allocation fails)
Remember to handle memory allocation failures by returning NULL, and the caller will be responsible for freeing the memory.
Click to reveal hint
Show solution
#include <stdio.h>
#include <stdlib.h>

int* reversei(const int *values, unsigned size) {
// Allocate memory for reversed array
int *result = (int *)malloc(size * sizeof(int));
if (result == NULL) {
return NULL; // Memory allocation failed
}

// Reverse the array
for (unsigned i = 0; i < size; i++) {
result[i] = values[size - 1 - i];
}

return result;
}

// Example usage:
int main() {
int original[] = {1, 2, 3, 4, 5};
unsigned size = 5;

int *reversed = reversei(original, size);
if (reversed == NULL) {
printf("Memory allocation failed!\n");
return 1;
}

printf("Original array: ");
for (unsigned i = 0; i < size; i++) {
printf("%d ", original[i]);
}

printf("\nReversed array: ");
for (unsigned i = 0; i < size; i++) {
printf("%d ", reversed[i]);
}
printf("\n");

// Don't forget to free the dynamically allocated memory
free(reversed);

return 0;
}

Why isn't there a free() inside reversei?

When a function like reversei allocates memory with malloc and returns a pointer to that memory, it is essential that the memory remains valid after the function returns. If you were to call free() on the allocated memory (e.g., free(result)) before returning, the pointer you return would point to memory that has already been freed. Using such a pointer results in undefined behavior, as it refers to invalid (dangling) memory.

Therefore, reversei should not call free() on the memory it allocates. Instead, it returns the pointer to the caller, transferring ownership of the memory. It is then the caller's responsibility to call free() when the memory is no longer needed. This is a common and important pattern in C: functions that allocate memory with malloc and return a pointer expect the caller to manage (and eventually free) that memory.

In the example, free(reversed); is called in main after the reversed array is used, ensuring proper memory management.

Dynamic String Reversal (★★☆)

Implement a function that reverses a C string and dynamically allocates memory for the result. The function should use a double pointer to return the result. Use this prototype:

void reverses(char **r, const char *s);

Where:

  • r is a pointer to a pointer where the reversed string will be stored (set to NULL in case of allocation failure)
  • s is a pointer to the input string
First calculate the length of the input string to allocate the correct amount of memory. Make sure to set `*r = NULL` if allocation fails.
Click to reveal hint
Show solution
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void reverses(char **r, const char *s) {
if (s == NULL || r == NULL) {
if (r != NULL) {
*r = NULL; // Set to NULL in case of invalid input
}
return;
}

// Find the length of the string
size_t len = strlen(s);

// Allocate memory for reversed string (including null terminator)
*r = (char *)malloc((len + 1) * sizeof(char));
if (*r == NULL) {
return; // Memory allocation failed
}

// Reverse the string
for (size_t i = 0; i < len; i++) {
(*r)[i] = s[len - 1 - i];
}

// Add null terminator
(*r)[len] = '\0';
}

// Example usage:
int main() {
const char *original = "Hello, World!";
char *reversed = NULL;

reverses(&reversed, original);
if (reversed == NULL) {
printf("Memory allocation failed or invalid input!\n");
return 1;
}

printf("Original: %s\n", original);
printf("Reversed: %s\n", reversed);

// Free dynamically allocated memory
free(reversed);

return 0;
}

Why size_t?

  • size_t is the standard unsigned integer type for sizes and counts in C: it’s what sizeof returns and what memory/​I/O APIs (e.g. malloc, strlen, memcpy, fread, fwrite) expect.
  • It's in <stddef.h> (and also available via <stdio.h>), and its width matches the target architecture (32 or 64‑bit), ensuring the code is portable.
  • In this exercise, both the string length and the loop index are always non-negative and could potentially reach the maximum size supported by the system. The size_t type is specifically intended for such cases, as it is guaranteed to be large enough to represent the size of any object in memory and is the standard type for array indices and sizes in C.

Dynamic Array Merging (★★★)

Implement a function that merges two sorted integer arrays into a single dynamically allocated sorted array. The function should handle duplicates and use a double pointer to return the result. Use this prototype:

void merge(int **r, const int *a1, unsigned s1, const int *a2, unsigned s2);

Where:

  • r is a pointer to a pointer where the merged array will be stored (set to NULL in case of allocation failure)
  • a1 is a pointer to the first sorted array
  • s1 is the size of the first array
  • a2 is a pointer to the second sorted array
  • s2 is the size of the second array
Since the arrays may contain duplicates, the result may have up to `s1 + s2` elements. Start by allocating memory of this size, then perform the merge operation as in the earlier exercise.
Click to reveal hint
Show solution
#include <stdio.h>
#include <stdlib.h>

void merge(int **r, const int *a1, unsigned s1, const int *a2, unsigned s2) {
// Check input validity
if (r == NULL) {
return;
}

// Handle empty input cases
if (a1 == NULL && a2 == NULL) {
*r = NULL;
return;
}

// Allocate memory for merged array (maximum possible size)
*r = (int *)malloc((s1 + s2) * sizeof(int));
if (*r == NULL) {
return; // Memory allocation failed
}

unsigned i = 0, j = 0, k = 0;

// Merge arrays
while (i < s1 && j < s2) {
if (a1[i] <= a2[j]) {
(*r)[k++] = a1[i++];
} else {
(*r)[k++] = a2[j++];
}
}

// Copy remaining elements of a1
while (i < s1) {
(*r)[k++] = a1[i++];
}

// Copy remaining elements of a2
while (j < s2) {
(*r)[k++] = a2[j++];
}

// Note: k now contains the actual size of the merged array
// We could resize the array to save memory if needed:
// *r = realloc(*r, k * sizeof(int));
// But this is optional and might fail, so we're keeping it simple.
}

// Example usage:
int main() {
int array1[] = {1, 3, 5, 7, 9};
int array2[] = {2, 4, 6, 8, 10};
unsigned size1 = 5, size2 = 5;
int *merged = NULL;

merge(&merged, array1, size1, array2, size2);
if (merged == NULL) {
printf("Memory allocation failed or invalid input!\n");
return 1;
}

printf("First array: ");
for (unsigned i = 0; i < size1; i++) {
printf("%d ", array1[i]);
}

printf("\nSecond array: ");
for (unsigned i = 0; i < size2; i++) {
printf("%d ", array2[i]);
}

printf("\nMerged array: ");
for (unsigned i = 0; i < size1 + size2; i++) {
printf("%d ", merged[i]);
}
printf("\n");

// Free dynamically allocated memory
free(merged);

return 0;
}

Dynamic String Concatenation (★★☆)

Write a function that concatenates two strings and returns the result in dynamically allocated memory. Use this prototype:

char* concat_strings(const char *s1, const char *s2);

Where:

  • s1 and s2 are the input strings to concatenate
  • The function returns a pointer to the dynamically allocated concatenated string, or NULL if allocation fails
Calculate the total length needed (length of s1 + length of s2 + 1 for null terminator), then allocate memory and copy both strings in sequence.
Click to reveal hint
Show solution
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

char* concat_strings(const char *s1, const char *s2) {
// Handle NULL inputs
if (s1 == NULL) s1 = "";
if (s2 == NULL) s2 = "";

// Calculate required length
size_t len1 = strlen(s1);
size_t len2 = strlen(s2);

// Allocate memory for concatenated string (including null terminator)
char *result = (char *)malloc((len1 + len2 + 1) * sizeof(char));
if (result == NULL) {
return NULL; // Memory allocation failed
}

// Copy first string
strcpy(result, s1);

// Append second string
strcat(result, s2);

return result;
}

// Example usage:
int main() {
const char *str1 = "Hello, ";
const char *str2 = "World!";

char *concatenated = concat_strings(str1, str2);
if (concatenated == NULL) {
printf("Memory allocation failed!\n");
return 1;
}

printf("String 1: \"%s\"\n", str1);
printf("String 2: \"%s\"\n", str2);
printf("Concatenated: \"%s\"\n", concatenated);

// Free dynamically allocated memory
free(concatenated);

return 0;
}

Dynamic array (★★☆)

Write a program that:

  1. Asks the user for the size of an array
  2. Dynamically allocates an array of integers of that size
  3. Fills the array with values entered by the user
  4. Calculates and prints the sum of all elements
  5. Properly frees the memory before exiting
Show solution
#include <stdio.h>
#include <stdlib.h>

int main() {
int size;
int *array;
int sum = 0;

// Get the size from the user
printf("Enter the size of the array: ");
scanf("%d", &size);

// Allocate memory
array = (int *)malloc(size * sizeof(int));
if (array == NULL) {
printf("Memory allocation failed!\n");
return 1;
}

// Get values from the user
printf("Enter %d integers:\n", size);
for (int i = 0; i < size; i++) {
scanf("%d", &array[i]);
sum += array[i];
}

// Print the sum
printf("Sum of all elements: %d\n", sum);

// Free the memory
free(array);

return 0;
}

Dynamic 2D array (★★★)

Write a function that creates a dynamic 2D array (matrix) with the specified number of rows and columns. Then write another function to free this matrix. Use these prototypes:

int **create_matrix(int rows, int cols);
void free_matrix(int **matrix, int rows);
For a 2D array, you need to allocate memory twice: first for the array of row pointers, then for each individual row. Make sure to handle memory allocation failures properly.
Click to reveal hint
Show solution
#include <stdio.h>
#include <stdlib.h>

int **create_matrix(int rows, int cols) {
// Allocate array of pointers (rows)
int **matrix = (int **)malloc(rows * sizeof(int *));
if (matrix == NULL) {
return NULL;
}

// Allocate each row
for (int i = 0; i < rows; i++) {
matrix[i] = (int *)malloc(cols * sizeof(int));
if (matrix[i] == NULL) {
// Free previously allocated rows
for (int j = 0; j < i; j++) {
free(matrix[j]);
}
free(matrix);
return NULL;
}
}

return matrix;
}

void free_matrix(int **matrix, int rows) {
// Free each row
for (int i = 0; i < rows; i++) {
free(matrix[i]);
}
// Free the array of pointers
free(matrix);
}

// Example usage:
int main() {
int rows = 3, cols = 4;

// Create matrix
int **matrix = create_matrix(rows, cols);
if (matrix == NULL) {
printf("Memory allocation failed!\n");
return 1;
}

// Initialize matrix with values
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
matrix[i][j] = i * cols + j;
}
}

// Print matrix
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
printf("%2d ", matrix[i][j]);
}
printf("\n");
}

// Free memory
free_matrix(matrix, rows);

return 0;
}

Resizing arrays with realloc (★★★)

Write a program that:

  1. Allocates memory for an array of 5 integers
  2. Fills the array with values
  3. Uses realloc() to expand the array to hold 10 integers
  4. Fills the additional elements
  5. Prints all elements
  6. Frees the memory
When using `realloc()`, remember that it might return a new pointer. Always assign the result to a temporary variable first to avoid losing the original pointer if allocation fails.
Click to reveal hint
Show solution
#include <stdio.h>
#include <stdlib.h>

int main() {
int *array;
int original_size = 5;
int new_size = 10;

// Allocate memory for 5 integers
array = (int *)malloc(original_size * sizeof(int));
if (array == NULL) {
printf("Initial memory allocation failed!\n");
return 1;
}

// Fill the first 5 elements
printf("Filling the first %d elements:\n", original_size);
for (int i = 0; i < original_size; i++) {
array[i] = i * 10;
printf("array[%d] = %d\n", i, array[i]);
}

// Resize the array to hold 10 integers
int *new_array = (int *)realloc(array, new_size * sizeof(int));
if (new_array == NULL) {
printf("Memory reallocation failed!\n");
free(array);
return 1;
}
array = new_array;

// Fill the additional elements
printf("\nFilling the additional %d elements:\n", new_size - original_size);
for (int i = original_size; i < new_size; i++) {
array[i] = i * 10;
printf("array[%d] = %d\n", i, array[i]);
}

// Print all elements
printf("\nAll elements after reallocation:\n");
for (int i = 0; i < new_size; i++) {
printf("array[%d] = %d\n", i, array[i]);
}

// Free memory
free(array);

return 0;
}

Advanced Pointer Exercises

Function pointers (★★★)

Write a program that uses function pointers to perform different operations (add, subtract, multiply, divide) on two numbers. The user should be able to choose which operation to perform.

A function pointer has the syntax: `return_type (*pointer_name)(parameter_types)`. You can create an array of function pointers to easily select different operations.
Click to reveal hint
Show solution
#include <stdio.h>

// Define operation functions
float add(float a, float b) { return a + b; }
float subtract(float a, float b) { return a - b; }
float multiply(float a, float b) { return a * b; }
float divide(float a, float b) { return b != 0 ? a / b : 0; }

int main() {
// Array of function pointers
float (*operations[4])(float, float) = {add, subtract, multiply, divide};
char *op_names[4] = {"Addition", "Subtraction", "Multiplication", "Division"};

float a, b;
int choice;

// Get input
printf("Enter two numbers: ");
scanf("%f %f", &a, &b);

// Display menu
printf("\nSelect operation:\n");
printf("1. Addition\n");
printf("2. Subtraction\n");
printf("3. Multiplication\n");
printf("4. Division\n");
printf("Choice: ");
scanf("%d", &choice);

// Validate choice
if (choice < 1 || choice > 4) {
printf("Invalid choice!\n");
return 1;
}

// Perform calculation using function pointer
float result = operations[choice-1](a, b);
printf("\n%s of %.2f and %.2f is %.2f\n",
op_names[choice-1], a, b, result);

return 0;
}

Double pointer to modify a pointer (★★★)

Write a function that takes a double pointer to an integer and allocates memory for a new integer value. The function should set this new memory to a value passed as a parameter.

void allocate_and_set(int **ptr, int value);
Show solution
#include <stdio.h>
#include <stdlib.h>

void allocate_and_set(int **ptr, int value) {
// Allocate new memory
*ptr = (int *)malloc(sizeof(int));

// Check if allocation was successful
if (*ptr != NULL) {
// Set the value
**ptr = value;
}
}

int main() {
int *p = NULL;

// Call function to allocate memory and set value
allocate_and_set(&p, 42);

// Check if allocation was successful
if (p != NULL) {
printf("Value: %d\n", *p);
free(p); // Don't forget to free the memory
} else {
printf("Memory allocation failed!\n");
}

return 0;
}

These exercises will help you master pointers and memory allocation in C, from basic concepts to advanced applications. Try to solve them on your own before looking at the solutions, and don't hesitate to experiment with variations.