-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
260 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# Credits | ||
|
||
This pre-lab was initially conceived by Vincent Borchardt, KK Lamberty, and Nic McPhee, in August and September, 2012. Most of the initial implementation was by Vincent, with subsequent editing and updates was provided by Peter Dolan, KK Lamberty, and Nic McPhee. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,160 @@ | ||
# C-programming-pre-lab | ||
|
||
Pre-lab to get started on compiling and running C programs and valgrind | ||
|
||
- [Background](#background) | ||
- [Checking vs. Exploration](#checking-vs-exploration) | ||
- [Compiling and running a C program](#compiling-and-running-a-c-program) | ||
- [Using valgrind to find memory leaks](#using-valgrind-to-find-memory-leaks) | ||
- [Exercise](#exercise) | ||
|
||
Background | ||
---------------------------------------- | ||
|
||
This pre-lab is for the _C and memory management_ labs, parts 1 and 2, | ||
which includes several C programming exercises with an emphasis on arrays, pointers, | ||
and memory management. The Internet is chock full of C tutorials, etc.; | ||
some are listed on the | ||
[CSci3403 Resources Page](https://github.umn.edu/UMM-CSci3403-F15/Resources/wiki), but there are no doubt zillions of good resources out there we've never heard of. | ||
|
||
### Checking vs. Exploration | ||
|
||
[As this article points out nicely](http://www.developsense.com/2009/08/testing-vs-checking.html), | ||
it's useful to make distinction between checking (which is what we | ||
typically call testing in our courses) and exploration (he calls it | ||
testing, but I prefer exploration given that "testing" means other | ||
things). Checking is what we do to see if our code (still) works. | ||
Exploration is what we do to learn more about a domain or a tool or a | ||
language. Exploration is often crucial when we're new to a space, and | ||
it's important to recognize that the stuff we're writing when we explore | ||
is often pretty crappy (because we don't know what we're doing yet). As | ||
a result one often does the exploring off to the side, with the | ||
intention of throwing it away. I bring all this up because I suspect | ||
there will be a fair amount of exploring that goes on during this lab. | ||
Try to be intentional and honest about that. Step off to the side and | ||
try a little exploratory code to figure out if you've got an idea worked | ||
out correctly. Then throw away that "quick and dirty" code, and bring | ||
your new knowledge back to the project at hand. | ||
|
||
### Compiling and running a C program | ||
|
||
In the exercise below you'll need to edit, (re)compile, and run the C | ||
program `check_whitespace.c` that is provided in this repository. | ||
Assuming you're in the project directory, you can compile this using the | ||
command | ||
|
||
```bash | ||
gcc -g -Wall -o check_whitespace check_whitespace.c | ||
``` | ||
|
||
`gcc` is the GNU C Compiler, which is pretty much the only C compiler | ||
people use on Linux boxes these days. The meaning of the flags: | ||
|
||
- `-g` tells `gcc` to include debugging information in the generated | ||
executable. This is allows, for example, programs like `valgrind` | ||
(described below) to list line numbers where it thinks there are | ||
problems. Without `-g` it will identify functions, but not have line | ||
numbers. | ||
- `-Wall` (it's a capital 'W') is short for "Warnings all" and turns | ||
on *all* the warnings that `gcc` supports. This is a Very Good Idea | ||
because there are a ton of crazy things that C will try to | ||
"understand" for you, and `-Wall` tells the compiler to warn you | ||
about those things instead of just quietly (mis)interpreting them. | ||
You should typically use `-Wall` and make sure to figure out and | ||
clean up any warnings you do get. | ||
- `-o <name>` tells `gcc` to put the resulting executable in a file | ||
with the given name. If you don't provide the `-o` flag then `gcc` | ||
will write the executable to a file called `a.out` for strange | ||
historical reasons. | ||
|
||
Assuming your program compiled correctly (**check the output!**) then you | ||
should be able to run the program like any other executable: | ||
|
||
```{bash} | ||
./check_whitespace | ||
``` | ||
|
||
### Using valgrind to find memory leaks | ||
|
||
One of the more subtle problems with explicit memory management is that | ||
you can allocate memory that you never free up again when you're done | ||
with it. This will typically never lead to an error, but can cause a | ||
long-running process to consume more and more memory over time until its | ||
performance begins to degrade or it ultimately crashes the system. Since | ||
system processes (e.g. file servers, authentication servers, and web servers) | ||
often run for days, weeks, or months | ||
between restarts, a memory leak in such a program can be quite serious. | ||
As a simple example, consider the (silly) function: | ||
|
||
```C | ||
void f(char *str) { | ||
char *c = calloc(100, sizeof(char)); | ||
/* Do stuff with c */ | ||
return 0; | ||
} | ||
``` | ||
The problem here is the fact that `f` allocates 100 bytes (100 | ||
characters) for `c` to point to which are never freed. This means that | ||
every time we call `f`, 100 bytes will be allocated to this process that | ||
we'll *never* be able to get back because we have no way of accessing | ||
that pointer outside of `f`. To fix that problem (assuming we really | ||
need to allocate that space) we need to free it before we return: | ||
```C | ||
void f(char *str) { | ||
char *c = calloc(100, sizeof(char)); | ||
/* Do stuff with c */ | ||
free(c); | ||
return 0; | ||
} | ||
``` | ||
|
||
These sorts of memory leaks can actually be really nasty to spot, so | ||
happily there's a nice program called `valgrind` that can help identify | ||
them. If your executable is `my_prog`, then running | ||
|
||
``` {bash} | ||
valgrind ./my_prog | ||
``` | ||
|
||
will run the program as normal, and then print out a memory usage/leak | ||
report at the end. To get more detailed information, including what | ||
lines generate a leak, | ||
|
||
* Make sure to compile your program with the `-g` flag, and | ||
* Add the `--leak-check=full` flag when running `valgrind`: | ||
|
||
```bash | ||
valgrind --leak-check=full ./my_prog | ||
``` | ||
|
||
This generates lots of output of the form: | ||
|
||
==28587== 18 bytes in 1 blocks are definitely lost in loss record 50 of 50 | ||
==28587== at 0x400522F: calloc (vg_replace_malloc.c:418) | ||
==28587== by 0x80486AE: str_reverse (palindrome.c:12) | ||
==28587== by 0x804870A: palindrome (palindrome.c:27) | ||
==28587== by 0x80487FF: not_palindrome (palindrome_test.c:13) | ||
==28587== by 0x8048963: test_long_strings (palindrome_test.c:54) | ||
==28587== by 0x804A1B8: _run_test (cmockery.c:1519) | ||
==28587== by 0x804A5A7: _run_tests (cmockery.c:1624) | ||
==28587== by 0x80489B3: main (palindrome_test.c:68) | ||
|
||
This tells you that 18 bytes were lost, and that were allocated by | ||
`calloc` (the top line of the trace), which was called on line 12 of | ||
`palindrome.c` in the function `str_reverse` (next to top line of the | ||
trace), etc. Note that this tells you where the lost bytes were | ||
*allocated*, which doesn't always tell you much about where they should | ||
be *freed*, as that's going to depend on how they're used after they're | ||
allocated. | ||
|
||
Exercise | ||
------------------------------------ | ||
|
||
1. Compile the program `check_whitespace.c` | ||
and run `valgrind` on it to find any leaks it may have (hint: it has at | ||
least one). | ||
2. In `leak_report.md` describe why the memory errors happen, and how to fix them. | ||
3. Actually fix the code. | ||
4. Commit, push, etc. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
#include <stdio.h> | ||
#include <string.h> | ||
#include <stdlib.h> | ||
|
||
/* | ||
* Strips spaces from both the front and back of a string, | ||
* leaving any internal spaces alone. | ||
*/ | ||
char* strip(char* str) { | ||
int size; | ||
int num_spaces; | ||
int first_non_space, last_non_space, i; | ||
char* result; | ||
|
||
size = strlen(str); | ||
|
||
// This counts the number of leading and trailing spaces | ||
// so we can figure out how big the result array should be. | ||
num_spaces = 0; | ||
first_non_space = 0; | ||
while (first_non_space<size && str[first_non_space] == ' ') { | ||
++num_spaces; | ||
++first_non_space; | ||
} | ||
|
||
last_non_space = size-1; | ||
while (last_non_space>=0 && str[last_non_space] == ' ') { | ||
++num_spaces; | ||
--last_non_space; | ||
} | ||
|
||
// If num_spaces >= size then that means that the string | ||
// consisted of nothing but spaces, so we'll return the | ||
// empty string. | ||
if (num_spaces >= size) { | ||
return ""; | ||
} | ||
|
||
// Allocate a slot for all the "saved" characters | ||
// plus one extra for the null terminator. | ||
result = calloc(size-num_spaces+1, sizeof(char)); | ||
// Copy in the "saved" characters. | ||
for (i=first_non_space; i<=last_non_space; ++i) { | ||
result[i-first_non_space] = str[i]; | ||
} | ||
// Place the null terminator at the end of the result string. | ||
result[i-first_non_space] = '\0'; | ||
|
||
return result; | ||
} | ||
|
||
/* | ||
* Return true (1) if the given string is "clean", i.e., has | ||
* no spaces at the front or the back of the string. | ||
*/ | ||
int is_clean(char* str) { | ||
char* cleaned; | ||
int result; | ||
|
||
// We check if it's clean by calling strip and seeing if the | ||
// result is the same as the original string. | ||
cleaned = strip(str); | ||
|
||
// strcmp compares two strings, returning a negative value if | ||
// the first is less than the second (in alphabetical order), | ||
// 0 if they're equal, and a positive value if the first is | ||
// greater than the second. | ||
result = strcmp(str, cleaned); | ||
|
||
return result == 0; | ||
} | ||
|
||
int main() { | ||
int i; | ||
int NUM_STRINGS = 7; | ||
// Makes an array of 7 string constants for testing. | ||
char* strings[] = { "Morris", | ||
" stuff", | ||
"Minnesota", | ||
"nonsense ", | ||
"USA", | ||
" ", | ||
" silliness " | ||
}; | ||
|
||
for (i=0; i<NUM_STRINGS; ++i) { | ||
if (is_clean(strings[i])) { | ||
printf("The string '%s' is clean.\n", strings[i]); | ||
} else { | ||
printf("The string '%s' is NOT clean.\n", strings[i]); | ||
} | ||
} | ||
|
||
return 0; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
# Leak report | ||
|
||
_Use this document to describe whatever memory leaks you find in `clean_whitespace.c` and how you might fix them. You should also probably remove this explanatory text._ | ||
|