Skip to content

Commit

Permalink
Merge branch 'release/v1.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
NicMcPhee committed Sep 13, 2016
2 parents d8cb1a1 + 62a3c5c commit 97ed928
Show file tree
Hide file tree
Showing 4 changed files with 260 additions and 0 deletions.
3 changes: 3 additions & 0 deletions Credits.md
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.
158 changes: 158 additions & 0 deletions README.md
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.
95 changes: 95 additions & 0 deletions check_whitespace.c
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;
}
4 changes: 4 additions & 0 deletions leak_report.md
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._

0 comments on commit 97ed928

Please sign in to comment.