Last Updated: 2025-09-22 Mon 15:25

CMSC216 Lab04: Valgrind and Makefiles

CODE DISTRIBUTION: lab04-code.zip

CHANGELOG:

Mon Sep 22 03:24:51 PM EDT 2025
Associated Reading for the lab has been added. Particularly the section in Valgrind's manual on error messages may be useful to students.
Mon Sep 22 11:44:21 AM EDT 2025

Post 133 reported a testing problem where the directory expected by tests is makeproblem-create but the intial directory name is actually makeproblem-create-makefile. Students should rename their directory to makeproblem-create using this command.

  >> mv makeproblem-create-makefile makeproblem-create

The codepack has been updated to reflect this change which was intended to make the folder names shorter to type.

1 Rationale

Memory problems are inevitable in C programs and learning how to effectively identify and resolve them is an essential skill for low-level development. Valgrind is a valuable ally when diagnosing memory problems as it gives very good clues about the nature of the errors so long as one can interpret its error messages. Part of this Lab focuses on fixing existing code where error messages such as "Invalid Writes" and "120 bytes Definitely Lost" are abundant. Completing the lab will give solid practice on sleuthing out memory bugs using such error messages.

Build systems are essential to program development to automate repetitive tasks such as compiling code, running tests, and tracking changes which would necessitate running such tasks. The make build system is among the oldest build system and is ubiquitous across Unix systems making it a useful to know a few basics about it. This lab shows how building simple C projects can be automated with make and its Makefile input format.

Associated Reading

Valgrind has been discussed in lecture but those looking for a quick reference might consult Valgrind's Explanation of error messages as to know how to read its tea leaves.

Students only need to master the basics of make and its Makefile format which are discussed in this document. Those wanting a more complete account should consult the GNU Make Manual as it is the most widely deployed implementation of make and its manual is thorough.

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 Instructions and Questions to answer: fill in the multiple choice selections in this file.
     
mon_main.c EDIT Problem 1 C file to edit and complete; contains bugs that must be fixed
mon-top5.txt Data Problem 1 Data to read in mon_main.c
map-all.txt Data Problem 1 Data to read in mon_main.c
mon-*.txt Data Problem 1 Additional data files
test_mon_main.org Testing Problem 1 CODE tests, used in make test-prob1
     
makeproblem-demo-little Study Problem 2 Example Makefiles to study
makeproblem-demo-big Study Problem 2 Example Makefiles to study
makeproblem-create/Makefile EDIT Problem 2 Template Makefile to complete for problem
test_makefile.org Testing Problem 2 CODE tests, used in make test-prob2
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_lab04.org Testing Combined QUIZ/CODE tests for this lab
testy Testing Test running scripts

3 Problem 1 Background

This problem will focus on debugging an existing C program which was written in a rather sloppy fashion leading to some serious flaws in it including memory problems. This will be a chance to practice reading Valgrind error messages and using them to locate problems in the code and resolve them.

3.1 Context

Your current supervisor has left the following note with the code and data files in the code pack.

TRAINER: We have most of the data we needed in the mon-*.txt files but
we need to complete the analysis I promised Prof. Birch by the end of
the week.  We need the Median Base Points of the samples in the data
files. I've written a program in mon_main.c to calculate this in a
flexible way but it keeps crashing and I don't have time to debug
it. I'm leaving that to you: Sorry!  I've heard Valgrind may help to
diagnose the memory problems but I never learned to use it
myself. Please make corrections to the code to make it work so we can
determine the median Base Points for the samples outlined in the
tests.

-Prof. Oak

3.2 Intended Behavior

The intended behavior of the program mon_main is to read data files formatted like mon-top5.txt which have 3 columns. It will store all the data in an array, sort it, and then print the median (middle) element.

>> make
gcc -Wall -Werror -g  -c mon_main.c
gcc -Wall -Werror -g  -o mon_main mon_main.o

>> ./mon_main
usage: ./mon_main <input_file> [filter]

>> ./mon_main mon-top5.txt 
Sorted array:
  0: Charmander   309 Fire        
  1: Squirtle     314 Water       
  2: Bulbasaur    318 Grass       
  3: Pikachu      320 Electric    
  4: Psyduck      320 Water       
Median BASE_POINTS is
  2: Bulbasaur 318 Grass

>> ./mon_main mon-top5.txt Water
Sorted array:
  0: Squirtle     314 Water       
  1: Psyduck      320 Water       
Median BASE_POINTS is
  1: Psyduck 320 Water

>> ./mon_main mon-top5.txt Ground
No elements left after filtering on 'Ground'

Unfortunately the behavior of the program at present is somewhat less helpful.

>> ./mon_main mon-top5.txt 
Segmentation fault (core dumped)

3.3 Instructions

As is indicated (and is often the case with other people's code) the program has some bugs which will need to be corrected. The ultimate goals is to correct the code so that it passes the test cases provided. The QUIZ questions in QUESTIONS.txt relate to some of Error messages that will be produced in the tests and are a good way to get started.

4 Problem 2 Background: Makefiles

Building programs often involves compiling separate source files and eventually combining them through linking to create an executable. There are often associated tasks such as running automated tests, creating distribution files, and generating documentation that may be supported by the build system as well. Among the oldest build systems still in use is make and its associated Makefiles. This problem covers the basics of how to create a Makefile.

4.1 Structure of Makefiles

The basic structure of a Makefile involves rules which relate targets, dependencies, and commands. Simple Makefiles comprise a list of rules.

# comment above Rule 1
target1 : dependency1A depency1B
	command1A
	command1B
	command1C

# comment above Rule 2
target2 : dependency2A
	command2A
	command2B

# comment above Rule 3
target3 : dependency3A dependency3B dependency3B
	command3A
	command3B

When invoked, make target3 will load the contents of the Makefile and examine the relationship between target3 and its dependencies. If make determines it is necessary, it may run command3A, command3B in sequence to "update" target3. In other cases, make may determine that there is no need to run additional commands as target3 is already up to date. Similarly, running make target1 will possibly run command1A, command1B, command1C.

NOTE: Commands associated with a rule MUST BE INDENTED WITH A TAB. This is a notoriously bad "feature" of make which can occasionally cause problems. Be aware: leading whitespace for commands should be TAB characters.

The most common kind of target is a file that should be created such as a .o file or an executable program. The directory makeproblem-demo-little has an example Makefile showing this:

 1: # Simle Makefile to build program main_prog
 2: 
 3: # first rule in the file is the default: typing `make` is the same as
 4: # typing `make main_prog`
 5: 
 6: # rule 1
 7: main_prog : main_func.o func_01.o
 8: 	gcc -o main_prog main_func.o func_01.o
 9: 
10: # rule 2
11: main_func.o : main_func.c
12: 	gcc -c main_func.c
13: 
14: # rule 3
15: func_01.o : func_01.c
16: 	gcc -c func_01.c
17: 
18: # rule 4
19: clean : 
20: 	rm -f *.o
21: 	rm -f main_prog

The rules that appear are

  1. main_prog depends on the main_func.o and func_01.o; the program is created by invoking GCC on these files
  2. main_func.o depends on main_func.c which is created by running GCC
  3. func_01.o depends on func_01.c and is created by running GCC
  4. clean is a "phony" target with no dependencies which will remove compile artifacts by using the rm command

When one types make or make main_prog in that directory, the following will appear:

>> cd makeproblem-demo-little/
>> make
gcc -c main_func.c
gcc -c func_01.c
gcc -o main_prog main_func.o func_01.o

Observe that make detects that the two dependencies for Rule 1 are incomplete: main_func.o and func_01.o are not present. To create them, it will generate them as targets according to for the rules that have them as targets, Rule 2 and Rule 3. After running the commands for these two rules, the commands for Rule 1 are executed to generate the program.

4.2 Variables and Shortcuts in Makefiles

Makefiles support a large number of features such as explicit variables, automatic variables, pattern-based rules, and many others. A few of these are described in the sample file makeproblem-demo-little/Makefile-shortcuts. It accomplishes the same task as the Makefile but utilizes some features which are useful to know about. The comments in this file describe some of these features.

 1: # Simle Makefile to build program main_prog
 2: 
 3: # first rule in the file is the default: typing `make` is the same as
 4: # typing `make main_prog`
 5: 
 6: # rule 1
 7: main_prog : main_func.o func_01.o
 8: 	gcc -o main_prog main_func.o func_01.o
 9: 
10: # rule 2
11: main_func.o : main_func.c
12: 	gcc -c main_func.c
13: 
14: # rule 3
15: func_01.o : func_01.c
16: 	gcc -c func_01.c
17: 
18: # rule 4
19: clean : 
20: 	rm -f *.o
21: 	rm -f main_prog

By default, typing make will search for a file named Makefile but the following invocation allows using a specific file other than that:

>> make -f Makefile-shortcuts

4.3 Dependency Detection

Makefiles are useful for larger projects to automatically detect dependencies which must be updated. The example in makeproblem-demo-big/ shows a "larger" project with more files. Observe the following:

>> make                # COMPILE 1
gcc -c main_func.c
gcc -c func_01.c
gcc -c func_02.c
gcc -c func_03.c
gcc -c func_04.c
gcc -c func_05.c
gcc -c func_06.c
gcc -c func_07.c
gcc -c func_08.c
gcc -c func_09.c
gcc -c func_10.c
gcc -c func_11.c
gcc -c func_12.c
gcc -c func_13.c
gcc -c func_14.c
gcc -c func_15.c
gcc -c func_16.c
gcc -c func_17.c
gcc -c func_18.c
gcc -c func_19.c
gcc -c func_20.c
gcc -o main_prog main_func.o func_01.o func_02.o func_03.o func_04.o func_05.o func_06.o func_07.o func_08.o func_09.o func_10.o func_11.o func_12.o func_13.o func_14.o func_15.o func_16.o func_17.o func_18.o func_19.o func_20.o

>> make                # COMPILE 2
make: Nothing to be done for 'all'.

>> rm func_12.o        # delete a .o
>> touch func_05.c     # make func_05.c look like it has been edited

>> make                # COMPILE 3
gcc -c func_05.c       # only out of sync or
gcc -c func_12.c       # missing targets are regenerated
gcc -o main_prog main_func.o func_01.o func_02.o func_03.o func_04.o func_05.o func_06.o func_07.o func_08.o func_09.o func_10.o func_11.o func_12.o func_13.o func_14.o func_15.o func_16.o func_17.o func_18.o func_19.o func_20.o

The lesson above is in COMPILE 3: make detects that most of the .o files are in sync with their source files. Only the missing / out of sync targets have their rules applied to regenerated them and update the exectuable program.

5 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.

                           _________________

                            LAB04 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: A Bit of Debugging
=============================

  This problem will focus on debugging an existing C program which was
  written in a rather sloppy fashion leading to some serious flaws in it
  including memory problems.  This will be a chance to practice reading
  Valgrind error messages and using them to locate problems in the code
  and resolve them.


Context
~~~~~~~

  Your current supervisor has left the following note with the code and
  data files in the code pack.

  ,----
  | TRAINER: We have most of the data we needed in the mon-*.txt files but
  | we need to complete the analysis I promised Prof. Birch by the end of
  | the week.  We need the Median Base Points of the samples in the data
  | files. I've written a program in mon_main.c to calculate this in a
  | flexible way but it keeps crashing and I don't have time to debug
  | it. I'm leaving that to you: Sorry!  I've heard Valgrind may help to
  | diagnose the memory problems but I never learned to use it
  | myself. Please make corrections to the code to make it work so we can
  | determine the median Base Points for the samples outlined in the
  | tests.
  | 
  | -Prof. Oak
  `----


Intended Behavior
~~~~~~~~~~~~~~~~~

  The intended behavior of the program `mon_main' is to read data files
  formatted like `mon-top5.txt' which have 3 columns. It will store all
  the data in an array, sort it, and then print the median (middle)
  element.
  ,----
  | >> make
  | gcc -Wall -Werror -g  -c mon_main.c
  | gcc -Wall -Werror -g  -o mon_main mon_main.o
  | 
  | >> ./mon_main
  | usage: ./mon_main <input_file> [filter]
  | 
  | >> ./mon_main mon-top5.txt 
  | Sorted array:
  |   0: Charmander   309 Fire        
  |   1: Squirtle     314 Water       
  |   2: Bulbasaur    318 Grass       
  |   3: Pikachu      320 Electric    
  |   4: Psyduck      320 Water       
  | Median BASE_POINTS is
  |   2: Bulbasaur 318 Grass
  | 
  | >> ./mon_main mon-top5.txt Water
  | Sorted array:
  |   0: Squirtle     314 Water       
  |   1: Psyduck      320 Water       
  | Median BASE_POINTS is
  |   1: Psyduck 320 Water
  | 
  | >> ./mon_main mon-top5.txt Ground
  | No elements left after filtering on 'Ground'
  `----

  Unfortunately the behavior of the program at present is somewhat less
  helpful.
  ,----
  | >> ./mon_main mon-top5.txt 
  | Segmentation fault (core dumped)
  `----


Instructions
~~~~~~~~~~~~

  As is indicated (and is often the case with other people's code) the
  program has some bugs which will need to be corrected. The ultimate
  goals is to correct the code so that it passes the test cases
  provided.  The QUIZ questions in `QUESTIONS.txt' relate to some of
  Error messages that will be produced in the tests and are a good way
  to get started.


PROBLEM 1 QUIZ: Errors in mon_main.c
====================================

  Study the code and error messages produced by test cases or command
  line runs of the program to start determining where memory errors
  occur and how they may be corrected. Answer the following QUIZ
  questions.


First Test
~~~~~~~~~~

  Run the first test via `make test-prob1' or `make test-prob1
  testnum=1'. It will fail. Analyze its output, both the Side-by-Side
  diff output and the Valgrind Report which is lower in the results.

  Check all of the following that are relevant to the bugs reported.
  - ( ) Valgrind messages indicate that this is likely a bug in Valgrind
    itself which should be reported to the tool developers ASAP.
  - ( ) The Valgrind messages indicate that there is likely a memory
    problem with writing too far into a block of memory.
  - ( ) The Valgrind Report indicates that a memory Write is being done
    some distance BEFORE a malloc()'d block
  - ( ) The Valgrind Report indicates that a memory Write is being done
    some distance AFTER a malloc()'d block
  - ( ) The memory Write is problematic because the wrong C syntax is
    used in the program: no * or -> is used to dereference a pointer.
  - ( ) The memory Write is problematic because it is assuming a pointer
    to an array but not enough space has been allocated


  The following Valgrind Error message often appears in the VALGRIND
  REPORT towards the bottom of test results.
  ,----
  | ==85164== Invalid write of size 8
  | ==85164==    at 0x109762: main (mon_main.c:99)
  | ==85164==  Address 0x4a9e374 is 0 bytes after a block of size 132 alloc'd
  | ==85164==    at 0x48447A8: malloc (vg_replace_malloc.c:446)
  | ==85164==    by 0x109661: main (mon_main.c:83)
  `----
  Check ALL of the following that correspond to information in this
  message.
  - ( ) It reports that some variable / array element is being assigned
    (write-en to) but the pointer to the variable is out of bounds
  - ( ) It gives a source code line where the out of bounds access is
    occurring.
  - ( ) It shows the exact memory address that is being accessed that is
    out of bounds (in hexadecimal format)
  - ( ) It gives the relative position of the memory access as just
    after a block that was allocated using malloc() and the location in
    source code where that allocation occurred

  Make some code changes to correct this bug and check that any error
  messages change.


First Test Again
~~~~~~~~~~~~~~~~

  Run the first test again and check new error messages. Which part of
  the code seems to be problematic now?
  - ( ) The loop that reads data from the file into an array
  - ( ) The loop that prints out the contents of the sorted array
  - ( ) The insertion sort routine meant to sort the data
  - ( ) The indexing to determine the median (it accesses an
    out-of-bounds element)

  Use any nearby hints to fix the code and reduce the errors / generate
  new errors.


First Test AGAIN
~~~~~~~~~~~~~~~~

  If all has gone well, the output for the first test will now appear
  correct BUT there are additional problems reported by Valgrind. Check
  ALL of the following problems appear in the Valgrind report.

  - ( ) There are Invalid reads AFTER a heap-allocated block
  - ( ) There are Invalid writes BEFORE a heap-allocated block
  - ( ) Uninitialized memory used to determine a conditional jump/move
  - ( ) Memory is lost / leaked as it is not de-allocated before the end
    of the program

  One of the problems identified in the Valgrind output is associated
  with the following lines which illustrate a common beginner mistake
  (something one wouldn't expect the seasoned Prof. Oak to do but
  perhaps C isn't his strong suit...)
  ,----
  |   char *filter = malloc(sizeof(char));
  |   if(argc > 2){                 // filter present
  |     filter = argv[2];
  |   }
  |   else{                         // default to no filtering
  |     filter = NULL;
  |   }
  `----
  Why is the above code a memory error?
  - ( ) malloc()'ing memory for filter then re-assigning it to point
    elsewhere creates a memory leak: the malloc()'d block can never be
    free()'d
  - ( ) character pointers like filter should not be pointed to
    heap-allocated space with malloc()
  - ( ) When pointers should not be set to NULL as this will cause a
    segmentation fault
  - ( ) pointing filter at a string like argv[2] leads to problems later
    as argv[2] is not heap-allocated so the code that free()'s it will
    causes an error

  Use the error messages provided in the Valgrind Report to fix the
  remaining error messages associated with this test.

  Hint: there are 3 changes that need to be made.


FIRST TEST!
~~~~~~~~~~~

  The First test should now pass. In fact, a few more tests may pass.

  Carry on with this pattern of inspecting test results, especially the
  VALGRIND REPORT to continue diagnosing errors to correct them. A few
  hints:

  - Any attempt to access a NULL pointer will trigger a segmentation
    fault; this is reported as an "Invalid Read" or "Invalid Write" of
    the address 0x0. When working with pointers you know may be NULL,
    check whether they are NULL before attempting to dereference them.

  - All code paths of exiting must de-allocate all memory. If you are
    exiting the program early, make sure to free() an malloc()'d blocks
    and close any files.

  - Opening files can fail, often due to the wrong filename being typed
    in. Make sure to check for this condition and report appropriate
    errors. Attempting to work with unopened file is likely to end in
    crashes.


PROBLEM 1 CODE: Repair mon_main.c
=================================

  As indicated above, continue debugging mon_main.c until all tests
  pass. A completely corrected program should give the following results
  on running tests:

  ,----
  | >> make test-prob1
  | gcc -Wall -Werror -g -fstack-protector-all  -c mon_main.c
  | gcc -Wall -Werror -g -fstack-protector-all  -o mon_main mon_main.o
  | ./testy -o md test_mon_main.org 
  | =================================================
  | == test_mon_main.org : mon_main application tests
  | == Running 8 / 8 tests
  | 1) ./mon_main mon-lvl1.txt Fire      : ok
  | 2) ./mon_main mon-all.txt Grass      : ok
  | 3) ./mon_main mon-top5.txt           : ok
  | 4) ./mon_main mon-lvl1.txt           : ok
  | 5) ./mon_main mon-all.txt            : ok
  | 6) ./mon_main mon-all.txt Ground     : ok
  | 7) ./mon_main no-file.txt            : ok
  | 8) ./mon_main mon-lvl2.txt BadFilter : ok
  | =================================================
  | RESULTS: 8 / 8 tests passed
  `----


PROBLEM 2 QUIZ: Questions on Makefiles
======================================

  Review the presentation of `Makefile' basics in the lab description
  and analyze the examples provided in the demo directories
  `makeproblem-demo-little' and `makeproblem-demo-big'. Then answer the
  following questions.


Command Execution
~~~~~~~~~~~~~~~~~

  Which of the following best describes when the commands for a rule are
  run?
  - ( ) The commands for all targets in the Makefile are all run every
    time make is used
  - ( ) Only the commands associated with the target named on the
    command line are run; e.g. `make targ5` will run commands associated
    with targ5
  - ( ) Commands for a given rule execute only if make detects a its
    target is needed and then only if the target is missing or older
    than its dependencies


Rule Syntax
~~~~~~~~~~~

  Which of the following Rules correctly lays out the syntax for target,
  dependencies, and commands in a Makefile.
  ,----
  | # RULE A
  | target : dependency1 dependency2
  | 	command1
  | 	command2
  | 	command3
  | 
  | # RULE B
  | target : dependency1 dependency2
  | command1
  | command2
  | command3
  | 
  | # RULE C
  | dependency1 dependency2 : target 
  | 	command1
  | 	command2
  | 	command3
  | 
  | # RULE D
  | target :
  | 	command1
  | 	command2
  | 	command3
  | : dependency1 dependency2
  `----
  - ( ) RULE A
  - ( ) RULE B
  - ( ) RULE C
  - ( ) RULE D


Variance in Makefiles
~~~~~~~~~~~~~~~~~~~~~

  Check ALL that are true about Makefile features
  - ( ) Rules may have 0 dependencies
  - ( ) Rules may have 0 commands
  - ( ) Rules have no more than 1 command
  - ( ) Rules have no more than 1 dependency
  - ( ) Makefiles can set up explicit variables for use in rules
  - ( ) Makefiles provide a variety of automatic variables for use in
    rules
  - ( ) A Makefile can have deeply nested decencies: targA depends on
    targB depends on targC depends on targD etc.


Problem 2 CODE: makeproblem-create
==================================

  Complete the template in the subdirectory
  `makeproblem-create/Makefile' according to the comments there.  The
  essential idea is to build the linked list program from an earlier lab
  and add a few other targets.  Once you add rules for certain targets,
  you can test them interactively via commands like `make' and `make
  clean' in that directory. You can also run the automated tests of this
  via a problem test:

  ,----
  | >> cd lab04-code
  | >> make test-prob2   # run tests for Makefile under makeproblem-create
  | ...
  `----

6 Submission

Follow the instructions at the end of Lab01 if you need a refresher on how to upload your completed lab zip to Gradescope.


Web Accessibility
Author: Chris Kauffman (profk@umd.edu)
Date: 2025-09-22 Mon 15:25