Skip to content

Commit

Permalink
Merge branch 'feature/remove_string_parts' into develop
Browse files Browse the repository at this point in the history
  • Loading branch information
NicMcPhee committed Sep 27, 2016
2 parents cac082c + c116df8 commit 485f64b
Show file tree
Hide file tree
Showing 11 changed files with 78 additions and 780 deletions.
195 changes: 75 additions & 120 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
* [Fixing memory problems](#fixing-memory-problems)
* [Getting started](#getting-started)
* [The problems](#the-problems)
* [Fixing palindromes](#fixing-palindromes)
* [Disemvowel](#disemvowel)
* [Mergesort](#mergesort)
* [Array merge](#array-merge)

Expand All @@ -13,44 +11,27 @@
# Background

This lab is a collection of several C programming exercises with an
emphasis on arrays, pointers, and memory management. The first is an
introduction to a tool for finding memory leaks, the second is a simple
exercise on 1-D arrays of characters, and the last is a more complex
array-of-array problem where I give you much less to begin with.
emphasis on arrays, pointers, and memory management.

For more information (including information on how to use ```valgrind```), see
the [Lab3 pre-lab](https://wiki.umn.edu/UMMCSci/CSci3401f13/Lab3Prelab).
the [C programming pre-lab](https://github.com/UMM-CSci-Systems/C-programming-pre-lab).

## Testing and the CMockery framework
## Testing and the Google Test framework

Each of these exercises comes with a set of tests implemented using [the
CMockery testing framework for C](http://code.google.com/p/cmockery/).
You won't have to learn anything about the CMockery framework, but you
will need to add some components to your compilation instructions to
include the CMockery library so the tests actually run.
The CMockery files in the `lib` directory
were built for 64-bit
Linux boxes (FC18). If you want to use these tests on a different architecture
you'll need to download and build the library for your gear.

We think the tests are pretty reasonable, but make **no** promises that
Google Test framework for C](https://github.com/google/googletest), aka
`gtest`. You won't have to learn anything about `gtest`, but you
will need to be able to compile and run the tests that we provide.

We think the tests are pretty reasonable, but make *no* promises that
they are in any way complete. Obviously you want your code to pass at
least these tests, but you shouldn't assume that passing these tests
guarantees any kind of correctness. You're welcome to read the tests
and extend them if
you'd like. You may even need to make changes to the
and extend them if you'd like. You may even need to make changes to the
test code to handle memory leaks
[(see below)](https://github.com/UMM-CSci-Systems/C-Lab-Starter#fixing-memory-problems).
Do be careful to not remove or weaken the tests, though; at a minimum you definitely
want to be able to pass the tests as given.

On a related note, don't over focus on the tests. CMockery frankly
doesn't give you super useful error messages or info when things fail.
You can get more information using the ```gdb``` debugger, but
that's not trivial to use. In many cases it will be as or more useful to
write little bits of code that print out useful information from your
code to help with debugging. You'll eventually want to remove all that,
but it may be *awfully* useful while you're exploring.
[(see "fixing memory problems" below)](#fixing-memory-problems).
Do be careful to not remove or weaken the tests, though; at a minimum
you definitely want to be able to pass the tests as given.

## Fixing memory problems

Expand All @@ -69,19 +50,22 @@ in the test code. If the test code calls some function `f()` that returns an
array or string that is allocated somewhere in `f` (or a function `f` calls),
then that memory is lost if the test code doesn't free up that returned array.
So if `valgrind` says there's a leak where some memory is allocated in a function
and then returned to the test code, then the fix is _in the test code_. In general
we _don't_ encourage/want you to be changing the 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.
and then returned to the test code, then the fix is _in the test code_. In general
we don't encourage you to fiddle with the
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.

## Getting started

You should then fork this repo to get the
starter code.
There are several directories there,
one for each project. We would recommend doing them in the order listed
below; there's no overwhelming reason that you need to do them in any
particular order, however, and it would be far better to move on to the
You should first fork this repository to get the
starter code, and remember to add any collaborators right away. You should
then clone the repository to whatever machine you're going to work on.

There are several directories here, one for each project.
We would recommend doing them in the order listed below; there's no
overwhelming reason that you need to do them in any particular order,
however, and it would be far better to move on to the
next one rather than get buried in one and not make any progress.

The basic structure for each project is (for an imaginary project
Expand All @@ -92,131 +76,107 @@ The basic structure for each project is (for an imaginary project
- In every case we wrote one or more helper functions, but these
don't have to be included in the `.h` file unless you
want to include them in the tests.
- `foo_test.c`, which is the test file we wrote using
CMockery.
- `foo.c`, which includes the initial stub (or an incorrect version)
of the program you're working with in that part.
- `main.c`, which gives you a "main" function that you can use to
run your code separate from the test code. You don't have to ever
do this, but you might find it useful in debugging.
- `foo_test.cpp`, which is the test file we wrote using `gtest`. The
`.cpp` ending is because this is actually a C++ file not a strict
C file. That will affect how you compile the test code, but you
won't have to know/learn anything about C++ for this lab.

Your job then is typically to complete or fix `foo.c`, which provides
the implementation of the function listed in `foo.h`.

To compile the `main` use the following:

```bash
gcc -Wall -g -o foo foo.c main.c
```

There are also top level `include` and `lib`
directories that contain:
(where you replace `foo` with the appropriate name for the project
you're working on). If all goes well, that should generate an executable
`foo` that you can run with `./foo`.

- `include/cmockery.h`, which is the CMockery include file
that the test file includes.
- `lib/libcmockery_la-cmockery.o`, which is the object file
containing the compiled versions of all the CMockery routines.
To compile the test code use the following:

Your job then is typically to write `foo.c`, which provides
the implementation of the function listed in `foo.h`. To
compile the test code (with all the CMockery stuff) use the following:
```bash
gcc -Wall -g -o foo_test foo.c foo_test.c ../lib/libcmockery_la-cmockery.o
```
(where you replace `foo` with the appropriate name for the project you're working
on). The `-g` flag is something we haven't talked about and
not strictly necessary; it causes a variety of useful debugging
information to be included in the executable, however, which can be
*extremely* helpful when using tools like `valgrind` or the
`gdb` debugger.
g++ -Wall -g -o foo_test foo.c foo_test.cpp -lgtest
```

_Notice that this uses `g++` instead of `gcc`. This because the `gtest`
is technically a C++ library, but it also works for "plain" C code, which
is all we need it for here. The `-g` flag isn't strictly necessary; it
causes a variety of useful debugging information to be included in
the executable, however, which can be *extremely* helpful when using
tools like `valgrind` or the `gdb` debugger. If you don't include it,
for example, then those tools won't be able to report accurate or useful
line numbers or function names. The `-lgtest` tells the compiler to include
the `gtest` library (that's the `-l` part) when generating the executable.

---

# The problems

:bangbang: Remember: For each problem you should at a minimum

* Pass our tests, and
* Have _no_ memory leaks, as confirmed by `valgrind`.
* Remove any print statements that you used to debug your code before you turn it in.
* Remove any print statements or other code that you used to debug your code before you turn it in.

Also, please don't lose your brains and forget good programming practices just because you're working in a new language. C can be quite difficult to read under the best of circumstances, and using miserable names like `res`, `res2`, and `res3` doesn't help. *Use functions* to break up complicated bits of logic; it's really not fun when a group turns in a solution that is one huge function, especially when there are several instances of repeated logic.

Some things to watch our for:

* In the past there has been strong inverse correlation between length
and correctness on these problem. If you find yourself wandering off into 2
or (especially!) 3 pages of code for any of these, you've likely lost the plot
and should probably ask for some help.
* Make sure you initialize all variables (including variables used to index arrays in loops). C won't give you an error if you fail to initialize something, and sometimes you can get lucky and your tests will accidentally pass because, at least that one time, you happened to get the "right" initial value. That doesn't mean your code is correct, though.
* Make sure you allocate space for the null terminator `\0` when allocating space for strings.

[There are more comprehensive tips and suggestions here.](https://github.umn.edu/UMM-CSci-Systems/C-Lab-Starter/blob/master/Tips_and_suggestions.md)

## Fixing palindromes

Before you start writing your own C code, we'll start by using valgrind
to identify memory leaks in an existing program. In the
`palindrome` directory there is a program that
determines (in sort of a dumb way) if a string is a palindrome. The file
`palindrome.c` has the code that checks for palindromes and (instead of
doing the more obvious thing of returning a boolean) returns the string
"Yes" or "No". The file `palindrome_test.c` uses the CMockery library
mentioned above to test that the `palindrome` function works. You should
go into that `palindrome` directory in your project and compile the
program:
```bash
gcc -Wall -g -o palindrome_test palindrome.c palindrome_test.c ../lib/libcmockery_la-cmockery.o
```
Run the resulting executable and
verify that all six tests pass.

Look at the code a little and see if you can spot any obvious memory
leaks. Then run `valgrind` on your executable and see what it tells you
about memory leaks in this code. Then go through and fix the memory
leaks so that `valgrind` is happy (and the tests still pass).

## Disemvowel

"Disemvoweling" is the act of removing all the vowels (a, e, i, o, and
u, both upper and lowercase) from a piece of text. Your task here is to
write a function
```C
char* disemvowel(char* str);
```
that takes a null-terminated string, and returns a new null-terminated
string (i.e., it doesn't mangle the original one) that contains the same
characters in the same order, minus all the vowels. Note that resulting
array of characters will need to be allocated, and will typically be
shorter than the input string. It would be desirable to not waste memory
and only allocate what you actually need for the return string; you
might find valgrind useful for helping check for leaks.
We've provided a `main.c` which you can compile instead of
`disemvowel_test.c` if you want to try out disemvoweling different
strings from the command line. :bangbang: You
need to make sure you only compile one of `main.c` and
`disemvowel_test.c`, otherwise you'll get a compiler error about trying to define `main()`
twice.
There are more comprehensive tips and suggestions in `Tips_and_suggestions.md` in the repository.

## Mergesort

Your task here is to implement a well known sorting algorithm in C,
e.g.,

```C
void mergesort(int size, int values[]);
```
This is a
destructive sorting operation, and should alter the array that it's
given. Note that since C doesn't know how large arrays are, we pass in
_destructive_ sorting operation, i.e., it should alter the array that it's
given by rearranging the elements in that that array. Note that since C
doesn't know how large arrays are, we pass in
the size as an argument.
To simplify the process, we've provided you with [Java implementations of
Quicksort and
Mergesort](https://github.umn.edu/gist/mcphee/83e9818b21ef9cb3cde4) that
you can use as models. We strongly
recommend you take advantage of these both because it will help ensure
that you focus on the C issues on these problems and because it'll make
recommend you take advantage of these; doing so will help ensure
that you focus on the C issues on these problems, and it'll make
them easier to grade. (Having to figure out some crazy, unexpected
approach is much more time consuming than reading a "standard"
solution.)
Common approaches to Mergesort require allocating temporary arrays; you
should make a point of freeing these up when they're no longer needed,
as you certainly wouldn't want a common operation like sorting to leak a
bunch of memory every time it's called. Again, valgrind should be your
bunch of memory every time it's called. Again, `valgrind` should be your
friend.
## Array merge
Your task here is to implement
```C
int* array_merge(int num_arrays, int* sizes, int** values);
```

Here `values` is an array of arrays
of integers, where each of the sub-arrays can have different length (so
it's not really a standard 2-D array). Because C doesn't know the size
Expand All @@ -232,7 +192,7 @@ information around, and how many wonderfully unpleasant errors can
result from doing this incorrectly. It's a **lot** safer if arrays know
how big they are._

`array_merge` should then generate a sorted list (small to large) of the
`array_merge` should then generate a single sorted list (small to large) of the
unique values (i.e., no duplicates) in `values`. Since we haven't yet
learned how to return multiple values in C, we're going to use a
slightly cheesy hack and return the `k` unique values in an array of
Expand All @@ -254,11 +214,6 @@ unique values is much smaller than the number of entries in `values`;
your solution should make a point of freeing up any unused memory.

You might also find your sorting algorithm from above useful in sorting
your results. With a little care, you can put the results in an unsorted
your results. With a little care, you can put the all the unique values in an unsorted
array, and then ask your sorting algorithm to sort the desired bit of
the array without messing with that important first element.
---
People that contributed to this write-up before the project was moved
to Github included Nic McPhee, Vincent Borchardt, and KK Lamberty.
14 changes: 3 additions & 11 deletions Tips_and_suggestions.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,9 @@ Several groups had off-by-one errors where they weren't allocating
where the null terminator would need to be, but which we never
allocated space for.

A number of groups had a subtle mistake where they
A number of groups had a subtle mistake where they went through something like the following sequence of steps:

1. Dynamically allocated an array that was potentially empty: `int *a = calloc(n, sizeof(int));`.
1. Dynamically allocated an array that was potentially empty (because _n=0_): `int *a = calloc(n, sizeof(int));`.
1. Copied some data into that array using a loop that (correctly) did nothing if *n=0*.
1. Accessed the first item in the array, which might not actually be there, e.g., `int i = a[0];`.
1. Protected the remainder of the code (through an `if` or loop with appropriate bounds) so that the value taken from the array was never *used* if n=0.
Expand Down Expand Up @@ -85,11 +85,6 @@ Use functions to break things up! Many of the submissions have
three loops in their `array_merge` function, with several people
getting as high as 6. I'd have a look at that if I were you.

On a related note, *many* people have their vowel check in
`disemvowel` as a huge long chain of `||` statements. Pull that
stuff out into a named function, *especially* if you end up
repeating it!

# Style and clarity: Odds and ends

You should remove printing code from "production" code (i.e.,
Expand Down Expand Up @@ -120,7 +115,4 @@ Several people did a variant of linear search to see if an item was
duplicated. Use the fact that your array is sorted (or go ahead and
sort it if it isn't) to simplify this (and speed it up, but the
simplicity is arguably the bigger issue).

---

These notes were started by Vincent Borchardt, 16 Aug 2012

7 changes: 0 additions & 7 deletions disemvowel/disemvowel.c

This file was deleted.

6 changes: 0 additions & 6 deletions disemvowel/disemvowel.h

This file was deleted.

Loading

0 comments on commit 485f64b

Please sign in to comment.