Description
1 Introduction and purpose
In this project you will revise your elephant functions from the previous project to use dynamic memory allocation. In
many situations we don’t know ahead of time how much data needs to be stored, which is one reason for using dynamic
memory allocation. In this project you will now dynamically allocate elephant structures, and elephants will also now have
names, which are character strings that will be dynamically allocated as well. This allows names of arbitrary size to be
created without wasting memory, because only the memory actually needed to store each name needs to be allocated.
The main purpose of the project is to get some practice with basic dynamic memory allocation, before you have to
use it for more complex data structures soon. But another purpose is to get some practice with a few of C’s string library
functions from Chapter 9 in the Reek text, which you were responsible for reading on your own. You are expected to use
the string library functions whenever there is one that can be used. Do not write code (using loops or recursion) that
duplicates the effects of any string library functions, or you will lose credit. Just use the string library functions instead. In
fact, it is not necessary to use any loops or recursion anywhere to write the project, so if you write any loops or use any
recursion in the project at all you will lose considerable credit.
Like the previous two projects this is a small project. It has mostly public tests, and will not be graded for style. So
you again have a shorter time to do it, so you will have more time for later projects that are more difficult.
You can submit this project three times. After that, every submission you make, for whatever reason, will result in
losing 1 point from your score. You are expected to compile, run, and test your code yourself, before submitting. (Even if
you change something as innocuous as a comment you may have made a typing mistake that would cause a syntax error–
recompile and check your results again before submitting.)
Due to the size of the course it is not feasible for us to be able to provide project information or help via email/ELMS
messages, so we will be unable to answer such questions. You are welcome to ask any questions verbally during the TAs’
office hours.
2 Functions to be written
The fields of the Elephant structure in elephant.h are the same as before except for the addition of a name field, which
is a pointer to a char. Functions that have to store a name for an elephant must dynamically allocate memory for the
name first. In all cases where functions have to store an elephant’s name they must create a string of exactly the
size needed to store the name– not longer (and certainly not any shorter). There is only one function now that creates
an elephant, new_elephant(), and it must dynamically allocate an Elephant structure, store its parameters’ values in its
fields, and return a pointer to the allocated structure. Some of the functions have been removed, and most of the remaining
ones now take a pointer to a Elephant structure as a parameter (or pointers to two Elephant structures).
Since most of the functions now take an Elephant pointer as a parameter, for brevity below we may write things like
“the function’s Elephant parameter” to mean “the Elephant structure variable that the function’s pointer parameter points
to”.
Note that to reduce duplicative code the public tests all call a function check_elephant() that we have provided. And
two of the public tests check whether some of your functions have memory leaks or cause problems in the heap. This is
explained further in Section 2.8 below.
2.1 Elephant *new_elephant(const char name[], enum type which_type, unsigned int id,
unsigned short weight, float trunk_length)
Project #5 discussed some approaches for writing structure functions in C. Another option not mentioned then is for a
function to return a pointer to a structure that the function has itself dynamically allocated. Using this approach a function
can return a pointer to an initialized structure without requiring the caller to declare a structure or to allocate memory for a
structure. It is common for functions to dynamically allocate a structure and return a pointer to it after suitably initializing
its fields. This function will use this approach.
The function should just return NULL without doing anything else if its array parameter is NULL. Otherwise it should
return a pointer to a dynamically–allocated Elephant structure that has the parameters’ values stored in its fields. The
name field of the newly created Elephant structure has no fixed or maximum length, so this function now must allocate
© 2023 L. Herman; all rights reserved 1
a string of exactly the right size to store the name parameter (however long it may be), copy the value of the name
parameter to the allocated string, and make the name field of the allocated structure point to it.
To emphasize: the new elephant’s name field must point to new memory that this function allocates, not just point to
the parameter name that was passed in. In other words, this function should not just be doing pointer aliasing.
Use string library functions for all manipulations of the elephant’s name, not loops (or recursion), or you will
lose considerable credit.
2.2 unsigned short get_type(const Elephant *const el_ptr, enum type *const type_ptr)
This function should “return” to the caller the type of its parameter elephant, but it should also return 0 if either parameter
is NULL. However, since the first constant in an enum has the numeric value 0, if the function only returned a value, the
caller would not be able to distinguish between it returning 0 to indicate that either parameter was NULL, or it returning the
enum value AFRICAN. What the function should do instead is to return 0 or 1 depending upon its parameters’ validity, and,
if the parameters are valid, to store the elephant’s type into the variable that type_ptr points to.
So if either parameter is NULL the function should return 0 without changing anything else, while otherwise it should
return 1 after storing its parameter elephant’s type into the variable that its second parameter points to.
2.3 const char *get_name(const Elephant *const el_ptr)
This function should return NULL if it parameter is NULL, or if its parameter is not NULL but its parameter’s name field is
NULL. Otherwise it should return its parameter elephant’s name, but it should not just return a pointer to the name field of
the Elephant structure itself. Instead, it should allocate enough memory to store the name, copy the parameter elephant’s
name to the newly–allocated memory, and return a pointer to this new copy of the name. The caller will be responsible
for freeing the returned memory later, if they want to avoid memory leaks. (This is their responsibility to do, which your
function cannot enforce.)
Use string library functions for all manipulations of the elephant’s name, not loops (or recursion), or you will
lose considerable credit.
2.4 void print_elephant(const Elephant *const el_ptr)
This function should have no effect if its parameter is NULL, or if its parameter is not NULL but its parameter’s name field is
NULL. Otherwise it should print its parameter elephant’s fields, in the same manner as print_elephant() did in Project #5,
except the elephant’s name is to be printed first. (One of the public tests illustrates the expected output format.)
2.5 short compare(const Elephant *const el_ptr1, const Elephant *const el_ptr2)
This function should return −1 if either of its parameters is NULL, or if either parameter is not NULL but the elephant’s name
field is NULL. (Note that its return type is now short, as opposed to unsigned short in the Project #5 version of this
function.) If its parameters are non–NULL it should return 1 if its two Elephant structure parameters have all the same
values, and 0 otherwise.
Use string library functions for all manipulations of the elephant’s name, not loops (or recursion), or you will
lose considerable credit.
2.6 unsigned short change_name(Elephant *const el_ptr, const char new_name[])
This function should return 0 without changing anything if either parameter is NULL. Otherwise it should change the name
field of its elephant parameter to be new_name and return 1. (It is not a problem if the elephant’s name field is initially NULL,
because the function is going to change the name anyway.) The function must change the name by allocating a new string
of exactly the right size to store the value of new_name, copy the contents of new_name to the allocated string, and make
the name field of the structure that el_ptr points to now point to the new allocated name.
Note that el_ptr’s name field should be set to point to new memory that the function allocates, of exactly the right
size needed, and copy the contents of new_name to the new memory. The function should not just set el_ptr’s name field
to point to new_name itself. In other words, this function should not just be doing pointer aliasing. But before reassigning
the structure’s name parameter the function should free the memory of the elephant’s current name, to avoid memory leaks.
© 2023 L. Herman; all rights reserved 2
Use string library functions for all manipulations of the elephant’s name, not loops (or recursion), or you will
lose considerable credit.
2.7 unsigned short copy(Elephant *const el_ptr1, const Elephant *const el_ptr2)
This function should return 0 without changing anything if either parameter is NULL, or if either parameter is not NULL but
the elephant’s name field is NULL. Otherwise it should return 1 after copying all of the fields of the structure that el_ptr2
points to into the structure that el_ptr1 points to.
This function should store new values into the fields of the Elephant structure that its first parameter points to. However, as in new_elephant() and change_name(), this function should cause its first parameter’s name field to point to new
memory that the function allocates that contains a copy of el_ptr2’s name, which is just the right size to store the name.
el_ptr1’s name should not just point to el_ptr2’s name field (the function should not just be doing pointer aliasing). But
before making el_ptr1’s name field point to a new string, the function should free the memory used for el_ptr1’s current
name, to avoid memory leaks.
Important: the caller is passing in the memory addresses of existing Elephant structures that were created before
calling this function. This function has to allocate memory for el_ptr1’s new name, but not for the structure that el_ptr1
points to. The Elephant structures that the parameters point to were created before this function is called.
Use string library functions for all manipulations of the elephant’s name, not loops (or recursion), or you will
lose considerable credit.
2.8 Checking elephants, memory functions, and memory checking
We are providing (in the project tarfile) two compiled object files named check-elephant.o and memory-functions.o,
and their associated header files check-elephant.h and memory-functions.h. We are not providing the source files for
check-elephant.c or memory-functions.c, just the object files. If you choose to write a makefile to compile your code,
see Section A.2 below regarding considerations regarding these object files.
check-elephant.o defines a single function check_elephant(), whose parameters can be seen in the header file
check-elephant.h. It simply returns 1 if all of the fields of its first Elephant parameter are equal to the remaining
parameters, and 0 otherwise. Since checking an elephant’s data is performed a number of times in the tests, it just avoids
repetitive code to use this helper function instead of duplicating the checks. (See the comment in the header file.)
memory-functions.o contains three compiled functions setup_memory_checking(), get_memory_in_use(), and
check_heap(), which have no parameters. get_memory_in_use() returns an int, while the others have no return value.
We use them in two public tests to detect memory leaks in some of your functions, and other possible errors in the heap.
Their effects are:
• setup_memory_checking() must be called once in a program before any memory is dynamically allocated; it sets
up things to check the consistency and correctness of the heap. If this function has been called initially, and the
program has any memory errors later (such as overwriting beyond the end of an allocated memory region in the
heap) the program will crash, consequently failing the test.
• get_memory_in_use() returns the amount of memory currently allocated (in use) in the heap.
• check_heap() performs certain consistency checks on all of the currently allocated memory in the heap.
Two tests initially call setup_memory_checking(), then call get_memory_in_use() before performing some operations on elephants. Later in the test, when the same amount of memory should be used, they call get_memory_in_use()
again and verify that the memory used is the same as it was earlier, causing the test to fail if not. At their end, these two
tests call check_heap() to ensure that the heap is valid.
When you compile these tests note that you will have to include the provided object file memory-functions.o in
linking rules to form their executables.
Lastly note that the public tests (even the two that call our memory checking functions) will unavoidably have memory
leaks. The tests are calling your new_elephant() function, which returns a dynamically–allocated Elephant structure,
but the tests are not freeing the structure anywhere. That is not your problem. That would be up to the user of your code
to do, not your functions. In other words we aren’t freeing any memory in the tests, but you should free memory that is no
longer needed in your functions in elephant.c. And these two tests just check that your functions don’t have memory
leaks, even though the tests themselves do cause memory leaks.
© 2023 L. Herman; all rights reserved 3
A Development procedure review
A.1 Obtaining the project files, compiling, checking your results, and submitting
Log into the Grace machines and use commands similar to those from before:
cd ~/216
tar -zxvf ~/216public/project06/project06.tgz
This will create a directory project06 that contains the files for the project, including the header files elephant.h,
check-elephant.h, and memory-functions.h, the two object files check-elephant.o and memory-functions.o, and
the public tests. You must have your coursework in your special course disk space for this class. Create a file in the
project06 directory named elephant.c (spelled exactly that way) that will #include the header file elephant.h, and in
it write the functions whose prototypes are in elephant.h.
A command like gcc public01.c elephant.c check-elephant.o -o public01.x will compile your program for
the first public test (or you can use Emacs to compile if desired); replace the 1s in the command with 2s for the second
public test, etc. You will have to add memory-functions.o to compilation commands for the two tests that use our heap
checking functions. You can also use separate compilation, compiling each source file to form object files, which are then
linked together. You are welcome to write a makefile to compile the public tests if you want (see the next section), but
if you do you are advised to compile your code at least once by hand before submitting, just in case any makefile errors
would result in your code compiling on Grace but not on the submit server.
A.2 Makefile considerations, checking your results, and submitting
If you do write your own makefile, remember that you will have to include the provided object file check-elephant.o
to all linking rules, and add memory-functions.o to linking rules to form the executables of the two tests that use our
memory checking functions. But since you aren’t being given the source files check-elephant.c or memory-functions.c
your makefile should not have rules to create check-elephant.o or memory-functions.o from them, because those rules
would fail. Just use check-elephant.o in all linking rules, and use memory-functions.o in the linking rules for the tests
that call the heap checking functions, without having rules creating these object files.
If you do write a makefile, also make sure that your clean target does not remove all object files, otherwise it would
also remove check-elephant.o and memory-functions.o, meaning you would just have to extract them from the project
tarfile again. (To prevent removing check-elephant.o and memory-functions.o in a clean target, just avoid using
wildcards when specifying the object files the clean target should delete– instead just explicitly list the names of all of the
object files that should be removed.)
As before, use diff to compare the tests’ output to the public test outputs that are in the project tarfile, for example
public01.x | diff – public01.output will test your code’s results on the first public test.
Running submit from the project directory will submit your project, but before you submit you must make sure you
have passed all the public tests, by compiling and running them yourself. Unless you have versions of all required functions
that will at least compile, your program will fail to compile at all on the submit server. (Suggestion– create skeleton versions
of all functions when starting to code, that just have an appropriate return statement if they are non–void functions.)
A.3 Grading criteria
Your grade for this project will be based on:
public tests 85 points
secret tests 15 points
B Project–specific requirements, suggestions, and other notes
• There are only a few secret tests. The observant student will notice that none of the public tests check that get_name()
works properly, so should be able to draw some conclusions about what may be tested in secret tests.
• A robust C program should always check that all memory allocations succeed, and take appropriate action if not.
(The appropriate action might just be gracefully exiting the program, but it at least should not be just crashing).
However, for simplicity in this project you may assume that all memory allocations always succeed.
© 2023 L. Herman; all rights reserved 4
• Be careful not to have any pointer aliasing in writing your functions.
• Do not write code using loops (or recursion) that has the same effect as any string library functions. If you
need to perform an operation on strings and there is a string library function already written that accomplishes that
task, you are expected to use it, otherwise you will lose credit. You are expected to use the string library functions
whenever there is one that can be used.
As mentioned above, the entire project can be written without any explicit user–written loops (or recursion), so if
your code has any loops at all that are operating upon strings (names) you should expect to lose significant credit.
• Do not cast the return value of the memory allocation functions or you will lose credit. Besides being completely
unnecessary, in some cases this can mask certain errors in code.
• Do not include the header files check-elephant.h or memory-functions.h in your elephant.c source file. A
source file should only include header files if it is using definitions that they contain. Our tests are calling the
functions whose prototypes are in check-elephant.h and memory-functions.h– your functions in elephant.c
do not call the functions in check-elephant.o or memory-functions.o, so your source file does not need to include
these header files.
• You cannot modify anything in the three provided header files, or add anything to them, because your submission
will be compiled on the submit server using our versions of them.
Your code may not comprise any source (.c) files other than elephant.c, so all your code must be in that file. You
cannot write any new header files of your own either.
Do not write a main() function in elephant.c, because your code won’t compile (our tests already have main()
functions). Write any tests in your own separate source files, and compile them together with elephant.c.
• You can only use the C language features that have been covered in class up through Chapter 11 in the Reek text.
(You can use the string library functions in Chapter 9 even though that chapter was not covered in class, because you
were told to read it on your own.)
• Keep in mind from Section 1 that you can submit this project only three times before losing credit.
If your code compiles on Grace but not on the submit server, you may have changed the provided header files, which
you were not supposed to do, or something in your account setup may be wrong. Run check-account-setup and
come to the TAs’ office hours for help if you can’t fix any problems that it identifies on your own. (Other causes of
this could be: you put your code in a subdirectory of the project06 directory, you added source or header files, or
you wrote a makefile but it is not fully correct and you did not try compiling your code by hand before submitting,
to check for problems.)
• If you have a problem with your code and have to come to the TAs’ office hours, you must come with tests you
have written yourself that illustrate the problem (not just the public tests), what cases it occurs in, and what cases
it doesn’t occur in. In particular you will need to show the smallest test you were able to write that illustrates the
problem, so whatever the cause is can be narrowed down as much as possible before the TAs even start helping you.
To emphasize: the TAs will not look at your program’s results on any of the public tests in office hours. You
must have written your own tests to receive any help with your program code.
You must also have used the gdb debugger, explained recently in discussion section, and be prepared to show the
TAs how you attempted to debug your program using it and what results you got.
C Academic integrity
Please carefully read the academic honesty section of the syllabus. Any evidence of impermissible cooperation on
projects, use of disallowed materials or resources, publicly providing others access to your project code online, or unauthorized use of computer accounts, will be submitted to the Office of Student Conduct, which could result in an XF for the
course, or suspension or expulsion from the University. Be sure you understand what you are and what you are not permitted to do in regards to academic integrity when it comes to projects. These policies apply to all students, and the Student
Honor Council does not consider lack of knowledge of the policies to be a defense for violating them. More information is
in the course syllabus– please review it now.
The academic integrity requirements also apply to any test data for projects, which must be your own original work.
Exchanging test data or working together to write test cases is also prohibited.
© 2023 L. Herman; all rights reserved 5