1.1 C Time functions from time.h
This exercise illustrates the frustrations that may occur if we (ab)use global variables and pointers. We will play with simple programs that use the standard C library time.h. Recently, a student in 'Imperative Programming' told me that he had used a lot of time on these functions, and he could not figure out why he got wrong results...
A very short explanation of the time functions in C: Time can be represented as 'unix time' (the the number of seconds elapsed since January 1, 1970) with use of the type time_t (just an unsigned integer). Time can also be encoded as a struct, of type struct tm (or in C++ just tm) with fields for seconds, minutes, hours, day, month, year (and more). The function time returns the current time, as a value of type time_t. The function localtime encodes (a pointer to) a unix time. The function asctime returns a textual presentation of (a pointer to) a tm struct. There are a few additional functions, which you may explore yourself.
The following C++ program works as expected:
#include <iostream> #include <ctime> #include <string> using namespace std; int main(){ time_t t1 = time(NULL), // Time now t2 = t1 - 7 * 24 * 60 * 60; // Same time last week. tm *tm1 = localtime(&t1); // Transforms t1 to tm struct char *tmstr1 = asctime(tm1); // Make an ASCII text string of the time cout << "Time now: "<< tmstr1 << endl; // Print it tm *tm2 = localtime(&t2); // Transforms t2 to tm struct char *tmstr2 = asctime(tm2); // Make an ASCII text string of the time cout << "Time a week ago: " << tmstr2 << endl; // Print it }
Try it out - and play with some variations if you want to explore the possiblities with time.h functions. It is a C++ program, so compile it with your C++ compiler.
The following program - where we have reordered some of the commands - does not work as expected:
#include <iostream> #include <ctime> #include <string> using namespace std; int main(){ time_t t1 = time(NULL), // Time now. t2 = t1 - 7 * 24 * 60 * 60; // Same time last week. tm *tm1 = localtime(&t1); // Transforms t1 to tm struct char *tmstr1 = asctime(tm1); // Make an ASCII text string of the time tm *tm2 = localtime(&t2); // Transforms t2 to tm struct char *tmstr2 = asctime(tm2); // Make an ASCII text string of the time cout << "Time now: "<< tmstr1 << endl; // Print time now cout << "Time a week ago: " << tmstr2 << endl; // Print time a week ago. Upps - what happened? }
Make sure you understand the problem(s). How does localtime deliver its result (a tm struct)? An similarly, how does asctime deliver its result (a C-style text string)? Consult the documentation of the time.h library.
Be sure to understand the problems in terms of global data, memory allocation, and pointers.
Why do you think the designers of time.h came up with the signatures of these functions, and the conventions for the use of the functions?
How would you design the functions in order to eliminate the frustrations?
Solution
Just a few words about the problems. Both localtime and asctime return pointers to 'static-duration' data. Thus, somehow there is allocated fixed space in memory to the results returned by these two functions. This saves memory space, and it saves time because we do not need to allocate and deallocate tm structs and text strings. But, it calls for a very disciplined use of the functions: Either you should use (print, for instance) the results of the functions right away, or the results should be copied to your own variables before they are overwritten by subsequent calls of the time functions.
1.2 Functions with struct input and struct output
Change the program from above such that also the new address of a person is passed as a pointer to a structure. I.e, in this version both parameters to move_person should be pointers.
Program yet another version that returns a moved person from the function (via return). The input person should not be changed. In this version of the program, you may choose to use pure call-by-value parameters (just relying on the value semantics of structs).
There is continuation of this exercise later in the material.
1.3 Specialization of persons with a union type
2014 and 2015: I do not recommend that you spend time on this exercise...
Introduce two variants of persons, as introduced in the C program on the accompanying slide.
Introduce two different person types, and a union that allows us to represent either a 'person with a home address' or a 'homeless person'.
You can read about unions in The C++ Prog. Lang. (3. edition) section C.8.2.
1.4 An array of functions
Make an array of structs. The struct should have two fields: A text string and a function pointer. The functions in the struct should accept an integer and a text string.
On top of this, program a function, activator, which activates one of the functions in the array. The function should accept the array (of structs) and a text string. Based on the text string, the activator should search the array for a suitable function. When found, the function should be called.
Relate the setup in this exercise to your knowledge of class hierarchies with virtual functions.
1.5 Dynamic allocation of persons and addresses
This is a continuation of an earlier exercise about structs (and pointer to structs).
We have on an earlier slides discussed struct person and struct address. More specifically, struct address appear as the location field in struct person.
Write a version of the program that allocates both persons and addresses dynamically. A person 'object' have a pointer to an address 'object'. And the rest of the program should operate on person pointers and address pointers instead on structs with value semantics.
Generated: Tuesday August 1, 2017, 13:24:59