Please complete the required studio exercises listed below, along with any of the (optional) enrichment exercises that interest you.
As you work through the exercises, please record your answers in a file, and upon completion please e-mail your answers to the email@example.com course e-mail account with Testing and Debugging Studio in the subject line.
Please make sure that the name of each person who worked on these exercises is listed in the first answer, and that you number your answers so they are easy for us to match up with the appropriate exercise.
Add a separate source and header file (for a thread event recorder class and its helper code) to your project, and in the header file declare an enumerated type for the different kinds of events that may be of interest in debugging concurrent code (feel free to add any other kinds of events that you'd like to record):
In those files also declare and define a stand-alone (i.e., not a member of any class or struct)
overloaded insertion operator (
operator<<) that takes a reference to an
ostream and a value of that enumerated type, and inserts a short string corresponding
to the value that was passed (e.g., "calling", "called", "returning", "returned from", etc.) into
the ostream and then returns a reference to the ostream.
In the main C++ source code file for the project (which should be named something like
testing_and_debugging.cpp) please modify the main function signature so that it
looks like the standard (i.e., portable between Windows and Linux) main function entry point
int main (int, char * )
In your main function, use the enumerated type and insertion operator to print out messages marking when the main function has just been called and when it is just about to return. Build and run your program, and as the answer to this exercise, please show your code and the output your program produced.
void *) pointer to the object on which the currently executing function was called (which should be non-zero for class/struct methods/operators, or zero for stand-alone functions/operators)
void *) pointer to the currently executing function
void *) pointer to an additional object to which the event pertains, which you could either zero out if there is no additional object, or use to store the address of a mutex or condition variable, etc.
Declare and define appropriate constructors for that struct, which zero out fields that will not be used and takes values for the fields that will be used (depending on the kind of event being recorded), and initializes the struct accordingly. Please provide a default constructor that sets the event type to unknown and zeroes out all the other fields of the struct.
Also declare and define a stand-alone overloaded insertion operator (
takes a reference to an
ostream and a reference to a const instance of that struct type, uses
the ostream to print out the contents of the passed object, and then returns a reference to the ostream.
In your main function, use the struct type and insertion operator to print out additional information when the main function has just been called and when it is just about to return (i.e., fill in the thread id and the address of the main function as the pointer to the currently executing function). Build and run your program, and as the answer to this exercise, please show your code and the output your program produced.
Declare and define public
unregister methods of the event logger
class, through which
output text for any kind of entity in the program can be registered (and deregistered) with the event
register method should take a
void * pointer and a reference to a
style string, and should store it in a container member variable (e.g., a map or multimap depending
on whether you want to allow registration of
multiple strings of text for each entity). The
unregister method should take a
pointer and if it is in the container remove it (for a multimap or other similar data structure you should
decide whether or not you want an unregister call to remove just one or all of the matching entries).
Please feel free to declare and define additional overloads of those methods as appropriate, according to
the different variations for how the fields of the struct from the previous exercise can be used.
Write an overloaded member insertion operator (
operator<<) for the event logger class that
takes a reference to a const instance of the struct type, looks up whether each of the
fields of the struct are registered with the event logger, and prints out the contents of the struct using
the registered string for the value of a field if text was registered or just the pointer value if not.
Essentially, this ostream operator should behave like the ostream operator from the previous exercise
if none of the fields of the passed struct had strings registered for their values, but for fields that have
strings registered for their values the operator should print out the registered string instead of (or perhaps
in addition to) their values.
Since the event logger is intended for use by multiple threads, please synchronize any of its methods as necessary, but also try to keep the degree of synchronization to a minimum so that it can operate in a mostly concurrent (and thus minimally interfering) manner with respect to the concurrency of the threads that may use it.
In your main function, declare an event logger object, register the string "main" for the address of the main function, fill in a struct and then use the event logger object print out the contents of the struct (including the registered string for main) when the main function has just been called and when it is just about to return. Build and run your program, and as the answer to this exercise, please show your code and the output your program produced.
registermethod that takes a thread id and an optional event count (default the event count to something large like 1024) as a parameter, and unless that thread id is already registered (1) remembers the thread id and a current event counter (set to 0 initially) for that thread, and (2) dynamically allocates and remembers the address of a buffer of event record structs (sized per the passed or defaulted event count to record). Also provide an
unregistermethod that takes a thread id and removes the thread id and counter and removes and deletes its current event record buffer. Optionally, you can provide a reset method that takes a thread id and an event count and replaces the thread's current event record buffer with a new one of the specified size (and deletes the old buffer), and also resets the thread's current event counter to 0.
Extend your event logger object with a
record method that is overloaded to take thread id along
with different combinations of other event record fields
that may be relevant, and uses the passed parameters to construct (using the placement
version of the new operator) or assign their values to the current position in that thread's event record
buffer (according to the current event count) and then increment the event count (be careful not to exceed
the end of the buffer - for example you may want to increment modulo the size of the buffer).
Since the event logger is intended for use by multiple threads, again please synchronize any of its
methods as necessary, but also try to keep the degree of synchronization to a minimum so that it can operate
in a mostly concurrent (and thus minimally interfering) manner with respect to the concurrency of the threads
that may use it - in particular you may or may not need to synchronize the
record method depending
on your design.
Write and instrument a recursive function that computes the factorial of a passed number, which is called from your main function. Register the main thread, the main function, the recursive function, etc. in the event logger object and collect relevant events during its execution. After it finishes print out the events that were collected. Build and run your program, and as the answer to this exercise show the output traces from the events your main function recorded.