Labor materials
- Slides: Debugging
- Slides: Makefile and logging
- Additional examples: Making reusable code in the form of a library
- Additional reading: Fun with valgrind
- Test files for valgrind: https://blue.pri.ee/ttu/files/iax0584/demokood/valgrind.zip
Lab tasks
In this lab you have one task, which is divided into two parts. The task is extended by two advanced tasks.
NB! Even though you can develop on any platform you want, one part of the proof will be to execute your program through Valgrind. Valgrind is known to work well on Linux and is known to be available on MacOS, however not on Windows. If you wish to develop on Windows, you need to transfer the file to a Linux environment when defending.
Download the data files: https://blue.pri.ee/ttu/files/iax0584/andmefailid/6_make_data.zip
The archive contains the files for both the base and advanced task!
Task part 1 / 2: Reading data and processing
In the first part of the task, we need to read the data, process and print it. You will also need to demonstrate your use of compiling using a Makefile and testing memory errors using valgrind.
Requirements
- Read data from the input file
- Data file structure
<õppeaine nimi> <hinnete arv> <hinded> - Data must be stored in a structure array. For grades, you can create a reasonably sized array in the struct – e.g. 20 grades (based on the data file for the task)
- Data file structure
- Print the following information
- Name of the subject
- Grades given in the subject
- Average grade for the subject
- Keep your reading, calculating average and output in separate functions (i.e. all called from main).
- Code must be divided into two code files, which both have their own header files. Division must make sense, but details are up to you.
- Code must be compiled using a Makefile
- At minimum, Makefile must describe the recipe all , have a variable CFLAGS and use the following flags for compilation: -Wall -Wextra -Wconversion -g
- Other aspects of the complexity and structure of the Makefile are up to you!
- During the defence
- Show compilation using a Makefile
- Show the output through valgrind to prove that there are no issues.
Hints
The number of grades for each subject can be different, which means that every line in the file might have a different length. Due to this, we cannot read the data file with just a single fscanf() function call like shown on previous weeks.
Due to this, we need to perform the reading of the data using nested loops
- The outer loop will read the name of the subject and the number of grades in that subject
- The inner loop will use the number of grades obtained from the outer loop to specify the number of iterations. Do not forget to check the return value of fscanf() of the inner loop
Testing
This is an example of the code structure that you could have.
NB! Names of files are not determined, it’s just a sample. Also the data file can be in the same folder as the code files.
1 2 3 4 5 6 7 8 9 |
. ├── data │ ├── data_grades.csv │ └── data_grades.txt ├── analyzer.c ├── analyzer.h ├── Makefile ├── subjects_processor.c ├── subjects_processor.h |
NB! The following output exampel doesn’t show valgrind output. Make sure to test using valgrind as well!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
Subject: Programming Grades: 4 5 5 4 2 5 1 4 2 5 Average: 3.70 Subject: Databases Grades: 5 3 3 4 4 3 Average: 3.67 Subject: Mechatronics Grades: 3 3 4 4 5 0 Average: 3.17 Subject: Physics Grades: 5 5 3 3 2 3 4 5 1 1 2 4 Average: 3.17 Subject: Ethics Grades: 5 5 5 4 5 3 Average: 4.50 Subject: Scientology Grades: N/A Average: N/A Subject: Chemistry Grades: 4 4 3 4 5 4 5 Average: 4.14 |
Task part 2 / 2: Logging
In this part of the task, you need to add logging to your program.
Requirements
- Add logging for the program
- One log per line. Logs cannot be on two lines!
- Every log starts with a timestamp. Timestamp must contain the date (day, month, year) and the time (hours, minutes, seconds)
- When executing the program again, the logs must not be erased from the previous executions.
- Events to log
- Opening and closing of the input file
- Reading of the input file completed. Print the number of lines read.
- Reading of the input file aborted due to reaching the limit of the array. Print the max size in the log. NB! You have two arrays – struct array and grades array in each struct!
- Error calculating average grade.
- Integrity of the log must be ensured even if the program crashes
- Option 1: You can force writing of the output buffer to the file using fflush()
- Option 2:Close the log file after each write – closing of the file writes the buffer to the file.
- If the log file fails to open, program must continue its work (and not exit!). Notify the user of failure.
Hints
- Prepare the string before calling the logger function! Do not overwhelm the logging function with parameters, you want it to be simple to call. To prepare the string, a good option would be to use snprintf()
- To simplify the logger function, it is recommended to to pass it the name of the file nor the pointer to the log file. Some possible options:
- Open and close the file in the log function
- This is also one place where are global variable would not be out of place. If you wish, you can create the file pointer as a global variable (not a good solution, but an acceptable one). However an even better solution would be to write the advanced task.
- I.e. calling the logger function might something like this:
Logger("Program started");
Advanced task 1: logging library
In this task, you will create a logging library. Hint: you can use it for homework 2.
Requirements
- Add a set of .c and .g files and move your logging code there
- Add 4 levels for logging: OFF, ERROR, WARNING ja INFO – these are enums!
- On each line of the log file, print the logging level of the message
- Log should be only written to the file if the set logging level permits it.
I.e.
INFO level means that all messages will be logged;
WARNING level means that only messages with the logging level WARNING and ERROR are logged;
ERROR level means that only messages with the logging level ERROR are logged
OFF means logs are not produced
- To call logger, you now needs to include the level of the log (ERROR, WARNING või INFO)
E.g. Logger(LOG_INFO, "Program started"); - Your library must support giving a specific name to the log file
E.g. LoggerSetOutputName("log.txt"); - Your library must support setting the logging level
E.g. LoggerSetLoggingLevel(LOG_INFO); - The settings for the log must be kept internally in the logging library. Settings include the log file name and logging level (you can add additional ones if you want). Keep these as global variables in your logging library.
- Settings for the logger should have default values – e.g. if developer forgets or does not want to set them up, then defaults would be used and program wouldn’t crash.
- All logs must be timestamped as described in the base task.
Advanced task 2: Reading simplified CSV
In this task our purpose is to show a simplified way to read names that consist of multiple words.
Requirements
- Use the simple CSV version of the input file
- Data fields are separated from each other by a comma, data fields do not contain commas.
Walkthrough
NB! We are using a simplified version of CSV files and reading then. You cannot read any CSV file in this manner! If a fields would contain a comma, this method of reading could not be used!
If you need complete CSV support, either use a library (e.g. libcsv) or write your own that could be based on a finite state machine (FSM) – you read a character and then depending on the current state and that character, you choose what to do.
scanf() family of function supports describing simple regular expressions for reading and describing data.
E.g. to read a time, we can use the format scanf("%d:%d", &hours, &minutes); – with this format, the user can enter the time as 14:35 . Variable hours would get 14 as a value and minutes would get 35 . However, if the user chose to enter 14 35 , it would not match the format – hours would still get 14 , because we didn’t expect a space in the format, the reading stops and minutes will be left uninitialized.
Based on this knowledge, we can compose a format fscanf(fp, "%[^,],%d", ...) . Replace the file pinter with your own and instead of three dots, put your variables where to store the data.
- First field will be read as a string, until the comma (comma is not read).
- Then the comma will be read out of the buffer (and thrown away)
- Then, a single integer will be read (number of grades in the subject).
This method allows you to read the first two data fields. The rest of the format for getting the grades you must make on your own (don’t forget the commas!).
After this class, you should
- be able to compose a simple Makefile
- Know how to declare a variable and use one
- Know how to write a recipe, including multiple recipes in a file
- Know what goes into a recipe
- Know about implicit rules for Makefiles
- be able to compile code using Makefiles
- Know to to use valgrind and read its output to test your programs for bugs
- Know how to get the current time inside of a program and be able to format the time string
- Know basics about logging and its importance
Additional content
- Make manual
https://www.gnu.org/software/make/manual/make.html - What is a Makefile and how does it work?
https://opensource.com/article/18/8/what-how-makefile - Compiling Programs with Make
https://web.stanford.edu/class/archive/cs/cs107/cs107.1194/resources/make - Makefile tutor project (sample projects with Makefile’iga)
https://github.com/clemedon/Makefile_tutor - Makefile tutorial
https://makefiletutorial.com - VMWare VSphere Logging options (levels)
https://docs.vmware.com/en/VMware-vSphere/8.0/vsphere-vcenter-configuration/GUID-0439D577-66F7-4584-AF05-5EB41A761873.html - An example of a logging library for C (more complex than we need)
https://github.com/rxi/log.c - strftime() function
https://www.cplusplus.com/reference/ctime/strftime/ - Valgrind documentation
https://valgrind.org/docs/manual/index.html - Compiling as a library guide
http://www.yolinux.com/TUTORIALS/LibraryArchives-StaticAndDynamic.html - Address Sanitizer documentation
https://github.com/google/sanitizers/wiki/AddressSanitizer