Lab materials
- Slides: Dynamic memory 1
Lab tasks
This week we have one lab task that is extended by two advanced tasks.
Task 1: random data file generator
This week, we will create a program that can generate random data files. These are useful for testing other applications without having access to real data.
Download the starter code: https://blue.pri.ee/ttu/files/iax0584/aluskoodid/7_generator_starter.zip
Requirements
- Build your application on the starter code given
- Ask the user for how many generated entries they need. There must be no upper bound!
- All entries are stored in a struct array during generation. Struct array itself is generated using dynamic memory allocation.
- Pick random first and last name and curriculum code from the pools given to you.
- Generate admission points
- Admission points are in range from 10 to 30, ends inclusive. Precision is 0.1 points (e.g. 24.7)
- Sort the structures based on the generated last name. If last names for two entries match, order them by their first name.
- Write the output in the following format:
<index> <last name> <first name> <curriculum code> <admission points>- Index is a unique integer. First entry will have the index as 0, every following one is incremented by one.
- Make sure that all resources (including memory) is freed when the program exits, use valgrind to make sure.
Qsord comparison function
I’m proposing two different comparison function options. In the first case, the type cast will be done as needed, removing the need for additional variables.
1 2 3 4 5 6 7 8 9 10 11 12 |
int ComparCandidate(const void *a, const void *b) { // Get comparison for last names int ret = strcmp(((candidate *)a)->lName, ((candidate *)b)->lName); // When last names match, return difference by first name if (ret == 0) return strcmp(((candidate *)a)->fName, ((candidate *)b)->fName); // return difference by last name return ret; } |
In the second case, we will create temporary pointers that take up some memory, but improve the readability of the code.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
int ComparCandidate(const void *a, const void *b) { // Temporary cast pointers for readability candidate *pA = (candidate *)a; candidate *pB = (candidate *)b; // Get comparison for last names int ret = strcmp(a->lName, b->lName); // When last names match, return difference by first name if (ret == 0) return strcmp(a->fName, b->fName); // return difference by last name return ret; } |
Workflow
- Create the necessary structure declaration to keep the data
- Ask the user how many entries to generate
- Allocate the memory, check the allocation!
- Generate the necessary entries
- For every person, you must generate the entries randomly.
- For every field from the pools, you must create a random number to pick out the member
Hint: you can either copy the name to your struct or only keep a pointer to the name - In addition, you need to generate the admission points.
Hint: Think about this mathematically – e.g. what’s the difference between 30 and 300!?! rand() function will always give you an int, regardless if what you try to do to it.
- Sort the structure array
- Write the results to an output file
- Free the allocated memory
Check with valgrind for correctness! Not only in the end, but also if you encounter some weirdness, crashes or corruption!
Testing
The output of your application should be relatively simple and short.
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 |
risto@risto-lt3-tux:~/Nextcloud/work/ttu/teaching/_generic/prog2/lab/wk7_generator/generator_solution$ valgrind ./generator ==12389== Memcheck, a memory error detector ==12389== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al. ==12389== Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright info ==12389== Command: ./generator ==12389== The database contains: 103 first names 105 last names 3 curriculum codes How many candidates to generate? > 500 Successfully generated 500 entries Successfully written 500 lines to generated_candidates.txt ==12389== ==12389== HEAP SUMMARY: ==12389== in use at exit: 0 bytes in 0 blocks ==12389== total heap usage: 6 allocs, 6 frees, 13,016 bytes allocated ==12389== ==12389== All heap blocks were freed -- no leaks are possible ==12389== ==12389== For lists of detected and suppressed errors, rerun with: -s ==12389== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0) |
When looking out the output file, we see that everything is sorted by name (showing only first 15 lines)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
0 Aas Heidy IACB 27.9 1 Aas Juhan IACB 29.6 2 Aas Reet IACB 18.2 3 Aasa Anne EARB 10.1 4 Aasa Julia MVEB 13.8 5 Aasa Sven EARB 14.5 6 Aasa Taavi MVEB 16.1 7 Aasa Urve EARB 29.4 8 Aasa Valdo IACB 21.2 9 Allik Ivari IACB 21.3 10 Allik Kerttu MVEB 29.8 11 Allik Kerttu MVEB 21.4 12 Allik Laivi MVEB 24.2 13 Allik Peeter IACB 22.3 14 Allik Rainer IACB 17.5 |
Advanced task 1: output file formats
In this task, we’ll add CSV as a secondary output format for our application. User must be able to choose which output format they want.
Requirements
- Add ability to your program to generate data in the CSV format
- First line of the CSV must be the header with the field names.
- This will be followed by data lines. Each member is separated by a comma. NB! In CSV, we don’t put a space after the comma!
- Ask the user in which format they wish the file to be generated in (CSV or space-delimitted) and generate the appropriate data file.
- For the space-delimitted file, the extension must be .txt and for the CSV file, use the extension .csv .
- Make sure that the CSV is correctly generated – try to open (or import) it using Libreoffice Calc’i or Microsoft Office and see if they recognize the format correctly.
Advanced task 2: settings
In this task, we’ll make our generation a bit more flexible and add settings to it.
Requirements
- All settings must be kept in a structure – create a new structure declaration for this!
- All settings must have default values
- Program must use the defaults if the user does not wish to change the settings. What the user needs to do to alter the settings is up to the developer
E.g. the program could ask if they wanted to change them as the first thing it does or the user can use a specific command like argument, that allows settings to be edited. - User must be able to alter the following settings, while inside of the program.
- Which data fields are generated (it must be possible to turn each one of them on or off).
- Name of the output file (only the name part. Extension must be chosen automatically based on the output format!)
- Output format (this is from advanced task 1, move the location of the setting into the struct).
- Number of items generated (base task part, move the location of the setting to the struct)
- Lowe rand upper bounds for the admission points
- NB! The number of entries generated must be asked regardless if the user wished to change the settings or not. Purpose of this is just to keep all settings neatly in one struct.
- User must be shown the settings the generation will be performed under regardless if they chose to edit it or not.
Note: if you wish, you can make a settings file, however it is not necessary. The updates to settings do not need to be remembered between executions. However if you do include a settings file, make sure that the user can change those settings within the program without editing the file manually.
Hint: You can make nice use of the trinary operator here
printf("First name: %10s\n", settings.genFirstName ? "Yes": "No");
After this class, you should
- Know how to use dynamic memory allocation
- Know how to check for memory leaks.
- Know in which situations it is reasonable to use dynamic memory and in which situations you should not.
- Know the difference between function call stack and heap.