Building C Programs: Exercises

See here how to set up a development environment to do these exercises.

  • You should complete the essential exercises if you want to claim you know how to program in C.
  • If you want to go further and make sure you understand everything, you can also complete the additional exercises.

Essential Exercises

  1. Using macros
  2. Conditional code inclusion
  3. Modules: breaking down a program into several source files
  4. Using makefiles
  5. Type casting

Using Macros

The program macro.c generates a series of random numbers and outputs the distribution of their value into several bins:

./macro
bin 0: [000 - 020[ *******************
bin 1: [020 - 040[ *******************
bin 2: [040 - 060[ *******************
bin 3: [060 - 080[ **********************
bin 4: [080 - 100[ ******************

There are several redundant hardcoded numbers in the code of macro.c that should rather be defined as macros (constants) to ease the code clarity and the possibilities of evolution. Fix this problem by introducing at least 2 macros:

  • SAMPLE_SIZE defining the size (i.e. number of integers) of the array manipulated by the program -- currently 1000 in the provided code sample
  • MAX_VAL defining the value that the generated random integers can take as the range [0 - MAX_VAL[, currently 100 in the code sample.

Define these macros to be 10 for SAMPLE_SIZE and 50 for MAX_VAL. An example of expected output is:

./macro
bin 0: [000 - 010[ 
bin 1: [010 - 020[ **********
bin 2: [020 - 030[ ********************
bin 3: [030 - 040[ **************************************************
bin 4: [040 - 050[ ********************

To check the correctness of your program, use a use a Linux distribution with check50 installed and write your solution in a file named macro.c. In a terminal, with that file in the local directory, check with this command:

check50 -l --ansi-log olivierpierre/comp26020-problems/2024-2025/week4-compilation/01-macro

Conditional Code Inclusion

The program macro-conditional.c is a variant of the program presented in the previous exercise, where many debug messages are printed on the standard output:

./macro-conditional
[DEBUG] Allocating memory
[DEBUG] Allocation successfull
[DEBUG] Filling array
[DEBUG] Generated 1000 random numbers
[DEBUG] Dividing numbers into bins
[DEBUG] Printing results
bin 0: [000 - 020[ ********************
bin 1: [020 - 040[ *******************
bin 2: [040 - 060[ ********************
bin 3: [060 - 080[ ********************
bin 4: [080 - 100[ *******************
[DEBUG] Printing done
[DEBUG] Memory freed

Update the program so that the display of the debug output happens only when the macro DEBUGMODE is defined. Do not define the macro itself in the code, we will rather use the -D gcc parameter to do so at compile time:

gcc -DDEBUGMODE macro-conditional.c -o macro-conditional
./macro-conditional
# Debug output displayed

gcc macro-conditional.c -o macro-conditional
./macro-conditional
# Debug output suppressed

To check the correctness of your program, use a use a Linux distribution with check50 installed and write your solution in a file named macro-conditional.c. In a terminal, with that file in the local directory, check with this command:

check50 -l --ansi-log olivierpierre/comp26020-problems/2024-2025/week4-compilation/02-macro-conditional

Modules: Breaking Down a Program into Several Source Files

The objective is to break down the program module.c into several modules:

  • module1.c and the corresponding header module1.h, containing module1_function1 and module1_function2
  • module2.c and module2.h containing module2_function1
  • module3.c and module3.h containing module3_function1 and module3_enum
  • main.c containing the main function.

Take care of including in C files only the necessary headers. The expected output is:

./module
module3_function1 called with parameter CASE2
res1: 84
res2: 1.000000
res3: 1595255563434

To check the correctness of your program, use a use a Linux distribution with check50 installed. In a terminal, with all the mentioned source files in the local directory, check with this command:

check50 -l --ansi-log olivierpierre/comp26020-problems/2024-2025/week4-compilation/03-module

Using Makefiles

Consider the program compiled from the following files:

Write a Makefile automating the compilation of this program. It should contain intermediate rules compiling 1) C source files into object file and 2) linking object files into the final executable which name should be prog. Include also a clean rule to delete the executable and intermediate object files.

You can download all the aforementioned source filed in a compressed archive here.

To check the correctness of your program, use a use a Linux distribution with check50 installed. In a terminal, with all source files as well as the Makefile in the local directory, check with this command:

check50 -l --ansi-log olivierpierre/comp26020-problems/2024-2025/week4-compilation/04-makefile

Type Casting

Complete the program cast.c by writing the generic array printing function array_print:

#include <stdio.h>

typedef enum {
    INT,
    CHAR,
    FLOAT
} array_type;

void array_print(void *ptr, int size, array_type type) {
    /* complete here */
}

int main(int argc, char **argv) {
    int int_tab[3] = {1, 2, 3};
    char char_tab[2] = {'a', 'b'};
    float float_tab[3] = {2.5, 1.1, 12.42};

    array_print(int_tab, 3, INT);
    array_print(char_tab, 2, CHAR);
    array_print(float_tab, 3, FLOAT);

    return 0;
}

The expected output is:

[1, 2, 3]
[a, b]
[2.500000, 1.100000, 12.420000]

To check the correctness of your program, use a use a Linux distribution with check50 installed and write your solution in a file named cast.c. In a terminal, with that file in the local directory, check with this command:

check50 -l --ansi-log olivierpierre/comp26020-problems/2024-2025/week4-compilation/05-cast

Additional Exercises

  1. Header inclusion
  2. Type casting (2)
  3. Investigating a bug with GDB

Header Inclusion

Consider the program constituted of the following two source files: preprocessor.c and preprocessor.h.

This program fails to compile due to missing header inclusions. Correct these issues by writing the proper include preprocessor directives. The expected output is:

./preprocessor
Please enter the amount of random number to generate:
10000000
Generated 10000000 numbers in 0.084871 seconds

To check the correctness of your program, use a use a Linux distribution with check50 installed. In a terminal, with the source file in the local directory, check with this command:

check50 -l --ansi-log olivierpierre/comp26020-problems/2024-2025/week4-compilation/06-preprocessor

Type Casting (2)

In C, characters are encoded in memory using ascii code. Knowing that there is a constant offset between the code for a given letter in lowercase and the code for that letter in capital, complete the capitalize function in the program ascii.c presented below:

#include <stdio.h>

char *alphabet = "abcdefghijklmnopqrstuvwxyz";

char capitalize(char c) {
    /* complete here */
}

int main(int argc, char **argv) {
    for(int i=0; i<26; i++)
        printf("capital %c: %c\n",
            alphabet[i], capitalize(alphabet[i]));

    return 0;
}

Ascii code in C.

  • When printed as an integer with the %d marker, the ascii code for a given char variable can be displayed.
  • You can also check out some ascii tables such as this one.

The expected output is:

capital a: A
capital b: B
capital c: C
capital d: D
capital e: E
capital f: F
# ...
capital z: Z

To check the correctness of your program, use a use a Linux distribution with check50 installed and write your solution in a file named ascii.c. In a terminal, with that file in the local directory, check with this command:

check50 -l --ansi-log olivierpierre/comp26020-problems/2024-2025/week4-compilation/07-ascii

Investigating a Bug with GDB

The following program, bug.c, contains several memory corruption bugs:

#include <stdio.h>
#include <stdlib.h>

int array[1000];

int main(int argc, char **argv) {
	int x;

	for(int i = 0; i < 10000; i++){
		int ran_num = rand()% 1000;
		array[i] = ran_num;
	}

	printf("Please enter an integer between 0 and 9999: ");
	scanf("%d", x);

	printf("array[%d] = %d\n", x, array[x]);
}

Use GDB to debug this program and fix the bugs. An example of expected execution is as follows:

Please enter an integer between 0 and 9999: 10
array[10] = 362

Note that you won't necessarily get 362 as the array's content is generated randomly, the important part is that the program does not segfault.

To check the correctness of your program, use a use a Linux distribution with check50 installed and write your solution in a file named bug.c. In a terminal, with that file in the local directory, check with this command:

check50 -l --ansi-log olivierpierre/comp26020-problems/2024-2025/week4-compilation/08-bug