Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Don't commit the executable
check_whitespace
check_whitespace_test

# Created by https://www.toptal.com/developers/gitignore/api/c,code,emacs,vim
# Edit at https://www.toptal.com/developers/gitignore?templates=c,code,emacs,vim
Expand Down
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"calloc",
"cmockery",
"malloc",
"STREQ",
"structs",
"valgrind"
]
Expand Down
135 changes: 128 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
# C-programming-pre-lab <!-- omit in toc -->

Pre-lab to get started on compiling and running C programs and using `valgrind` to identify
memory leaks.
This is a pre-lab to get you started started on compiling and running C programs
and using `valgrind` to identify memory leaks.

- [Background](#background)
- [Compiling and running a C program](#compiling-and-running-a-c-program)
- [Compiling and running the tests](#compiling-and-running-the-tests)
- [Using valgrind to find memory leaks](#using-valgrind-to-find-memory-leaks)
- [What to do](#what-to-do)

Expand Down Expand Up @@ -50,11 +51,11 @@ 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 -g -Wall -o check_whitespace main.c 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:
`gcc` is the GNU C Compiler, which, along with Clang/LLVM, dominates the
C/C++ compiler space 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`
Expand All @@ -74,13 +75,110 @@ people use on Linux boxes these days. The meaning of the flags:
will write the executable to a file called `a.out` for strange
historical reasons.

After the flags, we provide a list of all the `.c` files that need to be
compiled and [linked together](https://en.wikipedia.org/wiki/Linker_(computing))
to form a working executable.

Assuming your program compiled correctly (**check the output!**) then you
should be able to run the program like any other executable:

```{bash}
./check_whitespace
```

### Compiling and running the tests

This includes an example of using
[the GoogleTest library](https://google.github.io/googletest/)
for writing unit tests for C and C++ programs. The tests are in
`check_whitespace_test.cpp`, which has a `.cpp` extension because
tests in GoogleTest are actually C++ (`.cpp`) instead of just C.

Below are examples of a few tests in GoogleTests:

```c++
TEST(Strip, WhitespaceOnBothEnds) {
ASSERT_STREQ("frog", strip(" frog "));
}

TEST(IsClean, NoWhitespace) {
ASSERT_TRUE(is_clean("University of Minnesota Morris"));
}
```

The two arguments to `TEST` are arbitrary names. The first is the name of
the _suite_ this test is part of; we just used the name of the function
being tested by this test (`Strip` or `IsClean`). The second is the name
of this particular test, and should hopefully provide some useful information
on what's being tested here.

GoogleTest has quite a few assertions. Here we're using `ASSERT_STREQ`, which
asserts that two strings (`STR`) are equal (`EQ`), and `ASSERT_TRUE`.

To compile this is considerably more complicated because we have to use C++
and we need to include the GoogleTest library:

```text
g++ -Wall -g -o check_whitespace_test check_whitespace.c check_whitespace_test.cpp -lgtest
```

Here we're using `g++` instead of `gcc` to indicate that we want the C++ compiler.
We've also added the `-lgtest` at the end, telling the compiler to include the
`gtest` _library_ (hence the `-l`) when constructing the final executable. Also
note that we're _not_ including `main.c` when we compile the test code;
`check_whitespace_test.cpp` contains a `main()` function so we _can't_ include
`main.c` (which provides a `main()` function), or the system won't know which
one to call.

Assuming your program compiled correctly (again, **check the output!**) then you
should be able to run the test code:

```{bash}
./check_whitespace_test
```

If all the tests pass (and they should initially) you should get something like:

```text
$ ./check_whitespace_test
[==========] Running 10 tests from 2 test cases.
[----------] Global test environment set-up.
[----------] 5 tests from strip
[ RUN ] strip.EmptyString
[ OK ] strip.EmptyString (0 ms)
[ RUN ] strip.NoWhitespace
[ OK ] strip.NoWhitespace (0 ms)
[ RUN ] strip.WhitespaceOnFront
[ OK ] strip.WhitespaceOnFront (0 ms)
[ RUN ] strip.WhitespaceOnBack
[ OK ] strip.WhitespaceOnBack (0 ms)
[ RUN ] strip.WhitespaceOnBothEnds
[ OK ] strip.WhitespaceOnBothEnds (0 ms)
[----------] 5 tests from strip (0 ms total)

[----------] 5 tests from is_clean
[ RUN ] is_clean.EmptyString
[ OK ] is_clean.EmptyString (0 ms)
[ RUN ] is_clean.NoWhitespace
[ OK ] is_clean.NoWhitespace (0 ms)
[ RUN ] is_clean.WhitespaceOnFront
[ OK ] is_clean.WhitespaceOnFront (0 ms)
[ RUN ] is_clean.WhitespaceOnBack
[ OK ] is_clean.WhitespaceOnBack (0 ms)
[ RUN ] is_clean.WhitespaceOnBothEnds
[ OK ] is_clean.WhitespaceOnBothEnds (0 ms)
[----------] 5 tests from is_clean (0 ms total)

[----------] Global test environment tear-down
[==========] 10 tests from 2 test cases ran. (0 ms total)
[ PASSED ] 10 tests.
```

You should make sure you recompile and rerun the tests after you
make changes while fixing the memory leak problems down below. It's
not enough to just fix the memory leaks if you also break the code in
the process, and the tests should help ensure that you're good there.

### Using valgrind to find memory leaks

One of the more subtle problems with explicit memory management is that
Expand Down Expand Up @@ -163,7 +261,10 @@ Note that this tells you where the lost bytes were
be *freed*, as that's going to depend on how they're used after they're
allocated.

There are two types of memory leaks, one of which is frankly easier to
:warning: not all of these output blocks will be useful. Look for ones that
refer to some of _your_ code somewhere, like `strip` or `is_clean`.

There are two common types of memory leaks, one of which is frankly easier to
sort out than the other.

The easy ones are where function `f()` allocates _local_ memory (memory
Expand Down Expand Up @@ -194,11 +295,31 @@ test code (you could always just change the test code to say everything
passes!), but if the memory leaks to the test code, then that's where the
fix has to be made.

:raising_hand: **Tip:** If you need to free a value and you don't have a
name for it, _give it one_. E.g., add an assignment statement like
`s = value_to_free()` that gives that value (`value_to_free()`) a name
(`s`) so you can free it with something like `free(s)`. Also, don't forget
how to write clean code just because you're using C. If you find yourself
with multiple functions with the same structure, is there a way you can
write a helper function that captures that structure so you don't have to
repeat it over and over?

Once you have everything happy, you will hopefully get a line like:

```text
==357046== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

```

at the end indicating that you now have 0 errors and all is well.

## What to do

- [ ] Compile the program `check_whitespace.c`
- [ ] Compile the program `check_whitespace`
and run `valgrind` on it to find any leaks it may have (hint: it has at
least one).
- [ ] Also compile `check_whitespace_test` and run `valgrind` on the test code, to
find any leaks there (there are several).
- [ ] In `leak_report.md` describe why the memory errors happen, and how to fix them.
- [ ] Actually fix the code.
- [ ] Commit, push, etc.
Expand Down
32 changes: 4 additions & 28 deletions check_whitespace.c
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* Strips spaces from both the front and back of a string,
* leaving any internal spaces alone.
*/
char* strip(char* str) {
char const *strip(char const *str) {
int size = strlen(str);

// This counts the number of leading and trailing spaces
Expand All @@ -33,7 +33,7 @@ char* strip(char* str) {

// Allocate a slot for all the "saved" characters
// plus one extra for the null terminator.
char* result = calloc(size-num_spaces+1, sizeof(char));
char* result = (char*) calloc(size-num_spaces+1, sizeof(char));

// Copy in the "saved" characters.
int i;
Expand All @@ -50,10 +50,10 @@ char* strip(char* str) {
* 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) {
int is_clean(char const *str) {
// We check if it's clean by calling strip and seeing if the
// result is the same as the original string.
char* cleaned = strip(str);
char const *cleaned = strip(str);

// strcmp compares two strings, returning a negative value if
// the first is less than the second (in alphabetical order),
Expand All @@ -63,27 +63,3 @@ int is_clean(char* str) {

return result == 0;
}

int main() {
int NUM_STRINGS = 7;
// Makes an array of 7 string constants for testing.
char* strings[] = {
"Morris",
" stuff",
"Minnesota",
"nonsense ",
"USA",
" ",
" silliness "
};

for (int 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;
}
7 changes: 7 additions & 0 deletions check_whitespace.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#ifndef CHECK_WHITESPACE_H_GUARD
#define CHECK_WHITESPACE_H_GUARD

char* strip(char const *str);
int is_clean(char const *str);

#endif
48 changes: 48 additions & 0 deletions check_whitespace_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#include <gtest/gtest.h>

#include "check_whitespace.h"

TEST(strip, EmptyString) {
ASSERT_STREQ("", strip(""));
}

TEST(strip, NoWhitespace) {
ASSERT_STREQ("frog", strip("frog"));
}

TEST(strip, WhitespaceOnFront) {
ASSERT_STREQ("frog", strip(" frog"));
}

TEST(strip, WhitespaceOnBack) {
ASSERT_STREQ("frog", strip("frog "));
}

TEST(strip, WhitespaceOnBothEnds) {
ASSERT_STREQ("frog", strip(" frog "));
}

TEST(is_clean, EmptyString) {
ASSERT_TRUE(is_clean(""));
}

TEST(is_clean, NoWhitespace) {
ASSERT_TRUE(is_clean("University of Minnesota Morris"));
}

TEST(is_clean, WhitespaceOnFront) {
ASSERT_FALSE(is_clean(" University of Minnesota Morris"));
}

TEST(is_clean, WhitespaceOnBack) {
ASSERT_FALSE(is_clean("University of Minnesota Morris "));
}

TEST(is_clean, WhitespaceOnBothEnds) {
ASSERT_FALSE(is_clean(" University of Minnesota Morris" ));
}

int main(int argc, char *argv[]) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
27 changes: 27 additions & 0 deletions main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#include <stdio.h>

#include "check_whitespace.h"

int main() {
int NUM_STRINGS = 7;
// Makes an array of 7 string constants for testing.
char const *strings[] = {
"Morris",
" stuff",
"Minnesota",
"nonsense ",
"USA",
" ",
" silliness "
};

for (int 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;
}