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
- Using macros
- Conditional code inclusion
- Modules: breaking down a program into several source files
- Using makefiles
- 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 sampleMAX_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 headermodule1.h
, containingmodule1_function1
andmodule1_function2
module2.c
andmodule2.h
containingmodule2_function1
module3.c
andmodule3.h
containingmodule3_function1
andmodule3_enum
main.c
containing themain
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:
- main.c
- module1.c and the corresponding header module1.h
- module2.c and the corresponding header module2.h
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
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 givenchar
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