CMSC216 Lab03: Basic File I/O and Debugger Usage
- Due: 11:59pm Sun 21-Sep-2025 on Gradescope
- Approximately 1.00% of total grade
CODE DISTRIBUTION: lab03-code.zip
CHANGELOG:
- Tue Sep 16 04:12:27 PM EDT 2025
Several students reported that in some cases, errors like "stack smashing" may result while running
debugger_secrets. This is due to Prof K. giving too little space to thequote_actual[]array so that some quotes would not fit in it (particularly the Maurice Wilkes quote). This error has been fixed in the now updated version ofdebugger_secrets.cwhich can be downloaded here:- Tue Sep 16 12:39:07 PM EDT 2025
Several problems were reported by students about the
debugger_secrets.cfile: (1) some students need to use a space character to fix input which is hard to getfscanf()to acknowledge and (2) some students need to enter a unicode apostrophe (') which is hard. Both problems are now corrected.- To use a space character in single character fixes, enter an underscore _ which will be changed in the program to a space.
- Unicode characters have been removed from all quotes; enter an ASCII apostrophe (') to get that character.
Download the new version of
debugger_secrets.cto get these fixes; replace the old version with this new one.
Table of Contents
1 Rationale
Reading basic data from a file is essential in any programming
environment. C provides the standard fscanf() function to read
formatted data. This lab demonstrates its use and provides some
techniques to read simple, structured data from files into dynamically
allocated structures. This lab also demonstrates one type of struct
referring to an array of another type of struct which is a common
pattern that often appears in class projects. Mastering the syntax
associated with this situation is an essential C-programming skill.
As C programs become more complex, it is useful to have tools that support debugging their problems. GDB is a traditional terminal-based debugger that handles C programs well. This labs contains an exercise introducing GDB and to inspect a program with behavior that is not easily understood from its source code alone.
Associated Reading
Most good C references will cover basic file I/O and uses of functions
like fscanf(). K&R's "The C Programming Language" does so as do
many of the C references listed on the course Canvas page.
Practical use of GDB is covered in The Quick Guide to GDB with the complete account of its capabilities covered in Debugging with GDB, its official manual.
Grading Policy
Credit for this exercise is earned by completing the code/asnwers here
and submitting a Zip of the work to Gradescope. Students are
responsible to check that the results produced locally via make test
are reflected on Gradescope after submitting their completed
Zip. Successful completion earns 1 Engagement Point.
Lab Exercises are open resource/open collaboration and students are encouraged to cooperate on labs. Students may submit work as groups of up to 5 to Gradescope: one person submits then adds the names of their group members to the submission.
See the full policies in the course syllabus.
2 Codepack
The codepack for this lab is linked at the top of this document. Always download it and unzip/unpack it. It should contain the following files which are briefly described.
| File | Use | Description |
|---|---|---|
QUESTIONS.txt |
EDIT | Questions to answer: fill in the multiple choice selections in this file. |
treasure_main.c |
EDIT | Problem 1 C file to edit and complete; TODO sections are marked in the code |
treasure.h |
Provided | Problem 1 header file declaring data types and prototypes |
map1.tm |
Data | Problem 1 Data to read in treasure_main.c |
map2.tm |
Data | Problem 1 Data to read in treasure_main.c |
debugger_secrets.c |
Provided | Problem 2 program to analyze under GDB |
input.txt |
EDIT | Problem 2 input file to edit as input to debugger_secrets.c |
Makefile |
Build | Enables make test and make zip |
QUESTIONS.txt.bk |
Backup | Backup copy of the original file to help revert if needed |
QUESTIONS.md5 |
Testing | Checksum for answers in questions file |
test_quiz_filter |
Testing | Filter to extract answers from Questions file, used in testing |
test_lab03.org |
Testing | Tests for this lab |
test_code.org |
Testing | Tests for the code portion of the lab to allow individual problem testing |
testy |
Testing | Test running scripts |
gradescope-submit |
Misc | Allows submission to Gradescope from the command line |
3 QUESTIONS.txt File Contents
Below are the contents of the QUESTIONS.txt file for the lab.
Follow the instructions in it to complete the QUIZ and CODE questions
for the lab.
_________________
LAB03 QUESTIONS
_________________
Exercise Instructions
=====================
Follow the instructions below to experiment with topics related to
this exercise.
- For sections marked QUIZ, fill in an (X) for the appropriate
response in this file. Use the command `make test-quiz' to see if
all of your answers are correct.
- For sections marked CODE, complete the code indicated. Use the
command `make test-code' to check if your code is complete.
- DO NOT CHANGE any parts of this file except the QUIZ sections as it
may interfere with the tests otherwise.
- If your `QUESTIONS.txt' file seems corrupted, restore it by copying
over the `QUESTIONS.txt.bk' backup file.
- When you complete the exercises, check your answers with `make test'
and if all is well, create a zip file with `make zip' and upload it
to Gradescope. Ensure that the Autograder there reflects your local
results.
- IF YOU WORK IN A GROUP only one member needs to submit and then add
the names of their group.
PROBLEM 1: Treasuremap I/O Program
==================================
Staff will discuss some aspects of the provided treasure_main.c
file. This file has some blanks marked with ???? which need to be
filled in. It also demonstrates several important techniques that are
common in C program.
When the application is complete, it will run as shown below on the
provided map1.tm and map2.tm files.
,----
| > make # compile
| gcc -Wall -Werror -g -c treasure_main.c
| gcc -Wall -Werror -g -o treasure_main treasure_main.o treasure.h
|
| > ./treasure_main # run with no file provided
| usage: ./treasure_main <treasure_file.tm>
|
| > ./treasure_main map1.tm # run on first treasuremap
| Loading treasure map from file 'map1.tm'
| Reading map from file 'map1.tm'
| Allocating map struct
| Map is 7 by 5
| 3 treasures on the map
| Allocating array of treasure locations
| Reading treasures
| Treasure at 0 2 called 'Death_Crystals'
| Treasure at 4 1 called 'Mega_Seeds'
| Treasure at 6 3 called 'Flurbo_stash'
| Completed file, closing
| Returning pointer to heap-allocated treasure_t
|
| ==TREASURE MAP==
| ..A..
| .....
| .....
| .....
| .B...
| .....
| ...C.
| ================
| A: Death_Crystals
| B: Mega_Seeds
| C: Flurbo_stash
|
| Deallocating map
|
|
| > ./treasure_main map2.tm # run on second treasuremap
| Loading treasure map from file 'map2.tm'
| Reading map from file 'map2.tm'
| Allocating map struct
| Map is 9 by 13
| 10 treasures on the map
| Allocating array of treasure locations
| Reading treasures
| Treasure at 5 2 called 'Goblet_of_Fire'
| Treasure at 3 8 called 'Invisibility_Cloak'
| Treasure at 4 11 called 'Elder_Wand'
| Treasure at 8 10 called 'Mirror_of_Erised'
| Treasure at 1 12 called 'Philosophers_Stone'
| Treasure at 7 9 called 'Marauders_Map'
| Treasure at 8 2 called 'Pensieve'
| Treasure at 3 9 called 'Sword_of_Gryffindor'
| Treasure at 7 0 called 'Tom_Riddles_Diary'
| Treasure at 0 11 called 'Time_Turner'
| Completed file, closing
| Returning pointer to heap-allocated treasure_t
|
| ==TREASURE MAP==
| ...........J.
| ............E
| .............
| ........BH...
| ...........C.
| ..A..........
| .............
| I........F...
| ..G.......D..
| ================
| A: Goblet_of_Fire
| B: Invisibility_Cloak
| C: Elder_Wand
| D: Mirror_of_Erised
| E: Philosophers_Stone
| F: Marauders_Map
| G: Pensieve
| H: Sword_of_Gryffindor
| I: Tom_Riddles_Diary
| J: Time_Turner
|
| Deallocating map
`----
QUIZ File Input in treasure_main.c
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Make sure to study the code and ask questions to resolve the following
queries about the treasure map application.
Command Line Arguments
----------------------
The `treasure_main.c' program is run with command line parameters like
`> ./treasure_main map1.tm'. How is the file name `map1.tm' made
available in the `main()' function?
- ( ) The `fscanf()' function is used to read the command line as the
user types it in.
- ( ) The global variable `args' will be an array with `args[0]' as
the string `map1.tm'
- ( ) The `argc' parameter will have value 2 and `argv[1]' will be a
pointer to the string `map1.tm'
- ( ) The `getenv()' function is used to extract the command line
arguments from the environment.
Struct Definition
-----------------
The `treasuremap_t' struct is defined in the following location which
shows its fields (parts):
- ( ) In the c file `treasure_main.c'
- ( ) In the header `treasure.h'
- ( ) In the header `stdio.h'
- ( ) Trick question: the definition is read from the user typing
File Format
-----------
One important part of completing the `treasuremap_load()' function
will be to understand the format of the treasuremap files. According
to the documentation for the function and your observations of the
`map1.tm' and `map2.tm' files, which of the best describes the format
of these files.
- ( ) The file looks like how treasure_main prints them, as something
like
,----
| ..A..
| .....
| .....
| .....
| .B...
| .....
| ...C.
`----
and the application should just read the strings in the file into
the struct.
- ( ) The first line has the the size of the treasuremap AND the
number of treasures in the map (N). There are N remaining lines
which have treasure positions/descriptions in them.
- ( ) The first line has the number of rows/columns in the map and
each remaining line is a row/col/description position of a
treasure. The number of treasures is not given and must be detected
using EOF as a return from `fscanf()'
- ( ) The file is stored in binary format and is not easily read by
humans. However, it can be read directly into the struct via
`fread()'.
Nested Struct Syntax
--------------------
During `treasuremap_load()' the following line of code is used to read
in the row for an individual treasure's location.
,----
| fscanf(file_handle, "%d", &tmap->locations[i].row);
`----
Which of the following best describes the syntax used based on the
`treasuremap_t' type?
- ( ) `tmap' is a pointer to a struct so `->' is used to dereference
it to a field; the `locations' field is a pointer to an array so
square braces are used to dereference it. `fscanf()' needs an
address so `&' is used.
- ( ) `tmap' is a normal struct so `->' is used to access its fields;
the `locations' field is a normal array so square braces are used to
dereference it. `fscanf()' needs an address so `&' is used.
- ( ) `tmap' is a pointer so `&' is used to dereference it with `->'
getting its field. The `locations' field is a normal array so
square braces are used to dereference it.
- ( ) Trick Question: The syntax for this line is actually incorrect
and won't compile. It is a TODO item to fix its syntax.
Nested Struct Memory Allocation
-------------------------------
During the program execution, two different memory allocations are
made. Which of the following best describes how allocated memory is
related to the nested nature of the structs?
- ( ) The first allocation creates space for a treasuremap_t
struct. The second creates an array for all treasureloc_t structs
The address of the array is stored in the locations field of the
treasuremap_t struct.
- ( ) The first allocation creates space for an array of treasureloc_t
structs. The second creates a treasuremap_t struct and then connects
it to the array.
- ( ) The first allocation creates space for a treasuremap_t struct
AND the array of treasure locations. The second allocation is
unnecessary and needs to be removed.
- ( ) The current implementation is an incorrect method to declare the
structures, and it is sufficient to create local variables for both
the treasuremap_t struct and an array of treasuremap_loc structs.
Two-Dimensional Array Allocation
--------------------------------
Analyze the beginning of the `treasuremap_print()' function where the
`printspace' variable is established. Which of the following code
patterns is used to create a two-dimensional table of characters that
is used later with syntax like `printspace[i][j] = ...;'?
- ( ) A function called malloc2D(row,col) is used which allocates a
two-dimensional table and assign it printspace
- ( ) A function called calloc(row,col) is used which allocates a
two-dimensional table and assign it printspace
- ( ) First one malloc() is used to allocate all row/column elements,
then a loop is used to assign pointers to positions within the
single large memory block
- ( ) First one malloc() is used to allocate an array of pointers
sized on the height, then a loop is used to malloc() rows sized on
the width with the first array pointing at each row
Character Tricks
----------------
Locate the function `treasuremap_print()' and find the code with the
comment
,----
| // an interesting line
`----
Analyze what is happening with this line and select the answer below
which best describes it.
- ( ) It is accessing a character from an array such as A, B, C... to
display on the treasure map for each treasure.
- ( ) It is using pointer arithmetic to find the location of a
character in memory to display for each treasure.
- ( ) It is adding an integer onto character 'A' to get a new
character to display on the treasure map for each treasure.
- ( ) It is reading the character to display for the treasure from the
input file.
Memory De-allocation
--------------------
malloc() allocates blocks of memory for structs and arrays and those
blocks persist throughout program execution outliving the function
which calls malloc(). In order not to exhaust available memory, these
blocks must eventually be de-allocated: for very malloc(), a free().
treasure_main uses is responsible for de-allocating memory at the end
of program execution. Which of the following best describes how it
works?
- ( ) treasuremap_free() only calls free(tmap) which will free all
allocated memory associated with that struct
- ( ) treasuremap_free() does nothing as C keeps tracks of all
malloc'd blocks and automatically de-allocates them before main()
finishes.
- ( ) treasuremap_free() first free()'s the locations array inside the
tmap struct and then free()'s the tmap struct itself. This order is
necessary as free()'ing tmap first would lose access to its fields.
- ( ) treasuremap_main() actually has some logic bugs where the
pointer to the treasuremap struct is lost so cannot be free()'d
properly. This is a bug that needs to be fixed in the program to
pass test cases.
CODE Complete treasure_main.c
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Complete the TODO items in the `treasure_main.c' file so that the two
functions marked REQUIRED compile and run successfully. Correct output
will look like the demo shown at the top of this file.
Test that the code behaves correctly via the command
,----
| make test-code
`----
and verify that both code/quiz are correct via
,----
| make test
`----
before using
,----
| make zip
`----
to create a zip to submit.
PROBLEM 2: GDB and Debugger Secrets
===================================
Staff will survey the `debugger_secrets.c' file which reads input from
a text file specified on the command line. A sample input file is in
`input.txt' and students will nee to put start by putting their
terpmail email address on the first line. The file will need to have
additional inputs BUT determining exactly what they are is quite
challenging even though the source code for the program is available.
Instead, staff will illustrate how to start the program in a debugging
session using GDB, the GNU Debugger. A large amount of detail about
how to do so is available on GDB Quick guide which is produced by
staff:
<https://kauffman77.github.io/tutorials/gdb.html>
A typical session will start with
,----
| >> gdb -tui debugger_secrets
|
| (gdb) set args input.txt
| (gdb) break main
| (gdb) run
| ...
| (gdb) next
| (gdb) print something
| ...
| (gdb) quit
| >>
`----
QUIZ Questions
~~~~~~~~~~~~~~
To set a breakpoint to stop execution at a specific spot in code one
can use the GDB command
- ( ) `break 15' which will stop at line 15 in the current file
- ( ) `break file.c:15' which will stop at line 15 of `file.c' when it
is reached
- ( ) `break func_name' which will stop when the function `func_name'
is reached
- ( ) `break' which will stop when the current line is reached again
- ( ) Actually all of these are valid: it's nice to have options!
The following command will continue executing code until the next
break point is reached.
- ( ) `run' or `r'
- ( ) `continue' or `c'
- ( ) `break' or `b'
- ( ) `execute' or `e'
The following lines in the `debugger_secretes.c' do something peculiar
and should be analyzed and discussed.
,----
| char userid[10] = ...;
| ...;
| int magic1 = *((int *) userid);
`----
Which of the following best describes how the value for `magic1' is
set?
- ( ) The number stored in `userid' is converted from a string to an
integer and stored in `magic1'
- ( ) The memory address of userid is used as an integer to set
`magic1'
- ( ) userid is cast as an integer pointer and the 4 bytes at its
beginning of it are treated as an int and used to set `magic1'
- ( ) This line has unpredictable behavior and changes from one run to
the next making it very difficult to get the input correct.
Which of the following lines that appears later in
`debugger_secretes.c' is most related to how `magic1' is set?
- ( ) `int hash = magic1 ^ magic2;' because it also uses the same
variable
- ( ) `int mod1 = pb_rand() % quote_len;' because it involves an
unpredictable result that changes from run to run
- ( ) `quote_actual[mod1] = '_';' because it involves a character
array
- ( ) `int *quote_as_int = (int *) quote_actual; quote_as_int[mod3] =
1600085855;' because this combination similarly subverts the
normal type system treating character data as integers.
CODE
~~~~
By analyzing the code in `debugger_secrets' and using GDB, determine
the correct input needed in `input.txt' to run the program to
completion.
NOTE: students should use their own @terpmail.umd.edu addresses at the
beginning of the input which will lead to slightly different answers
being required than other students. Email addresses will be checked on
the Gradescope autograder.
4 Submission
Follow the instructions at the end of Lab01 if you need a refresher on how to upload your completed lab zip to Gradescope.