Programming Language Concepts Using C and C++/Object Orientation

From Wikibooks, open books for an open world
Jump to navigation Jump to search

Success of a business depends on its competitiveness, which generally translates to offering easily-upgradeable, high quality products at a moderate price in a reasonable time. This is not any different in software industry: the faster, the less costly, the more flexible the better. Key to achieving this is avoidance of doing the same thing more than once: reuse.

[1]Reusing code[2], for instance, will save us the development time of the reused module; it will generally lead to faster and/or smaller code because it has been profiled and fine tuned; it will be more reliable because it has been debugged.

In a procedural programming language such as C or Pascal, where subprogram is the natural unit of reuse, composition is the way to achieve reuse: One builds complex subprograms by composing it from simpler ones. Object-oriented programming languages offer an alternative: inheritance.[3] In addition to composing objects from sub-objects, one can use inheritance to derive a new class from an existing one[4], which means objects of the new class will contain a sub-object of the base one. In other words, composition and inheritance basically boil down to the same thing. The difference lies in the agent that carries out the process: programmer and compiler, respectively.

In this chapter, a very simplistic simulation of object-orientation—with hopes of giving an insight—is provided in C. While perusing through the code keep in mind that this should not be taken as an authoritative source. There is certainly much more to it than is offered in this code.

Additionally, static checker, Splint, is introduced. It can be utilized to check source code for semantic errors, such as memory leaks, infinite loops, use of uninitialized data, and so on. Such a tool and the compiler can be used in tandem to make C a safer programming language.

Modules[edit]

Superclass[edit]

Header files ending with the _Private suffix contain information that normally should not have been revealed to the user of a module. This apparent mistake is due to the compilation model of C [and C++], which dictates that in order to define a variable a compiler must have access to the definition of relevant types. That doesn’t hurt much if you use pointers to structure instead of plain structures. However, inheritance requires composing objects from sub-objects, which means the derived type needs to have access to the structure of the base type. Now, it is very likely that implementers of the base type and the derived type are different [groups of] programmers. There must be a way to transmit the required information between these parties. The only way this can be done in C is through placing the type definitions in a header file, which is what we do in the files whose names end with the _Private suffix.

Employee_Private.h
1 #ifndef EMPLOYEE_PRIVATE_H
2 #define EMPLOYEE_PRIVATE_H
3 
4 #include “misc/inheritance/Employee.h”

Next structure defines the members of an Employee object. Among these are two pointers-to-function, which can be made to point to different locations in the code segment. In other words different Employee objects can have different values in these pointers, which would mean they can react differently to the same requests. In our case, we will say that all Employees get a salary and a bonus, calculation method of which depend on whether a particular Employee is in fact an Engineer or a Manager.

Note that we include details of the structure in the header file, which is against our usual practice of deferring to the implementation file. This could still be done in such a manner by turning the structure definition (struct _EMPLOYEE) into a pointer to an incomplete type. However, it would not have reflected the true nature of inheritance: inheritance is composition carried out by the compiler; an object of the subclass containsmdash;in addition to its own data members—a sub-object of the superclass.[5]

 6 #define EMPLOYEE_PART \
 7   CALC_SALARY calc_salary; \
 8   CALC_BONUS calc_bonus; \
 9   char* name; \
10   Dept_Type department;
11 
12 struct _EMPLOYEE {
13   EMPLOYEE_PART
14 };
15 
16 #endif


Employee.h
 1 #ifndef EMPLOYEE_H
 2 #define EMPLOYEE_H
 3 
 4 struct _EMPLOYEE;
 5 typedef struct _EMPLOYEE* Employee;
 6 
 7 typedef long (*CALC_SALARY)(const Employee);
 8 typedef long (*CALC_BONUS)(const Employee);
 9 
10 typedef struct _ALL_EMP_FUNCS {
11   CALC_SALARY calc_salary;
12   CALC_BONUS calc_bonus;
13 } Employee_Functions; 
14 
15 typedef enum _DEPT { RESEARCH, SALES, MANAGEMENT, PRODUCTION } Dept_Type;

C, in the name of improving performance, is a very permissive programming language. This usually means most of the bookkeeping stuff that is deferred to compilers in many other programming languages fall to the share of the programmer. That’s okay when the program is not big and/or the programmer is a competent one. She can take the initiative and skip some of the aforementioned stuff, which would generally mean faster and leaner code. Or ...

One way to deal with the ominous alternative is to use a tool that detects programming anomalies in the source code. One such tool is Splint, which statically checks C programs for potential security vulnerabilities and programming mistakes.[6] Doing this may sometimes require annotating the source code. These annotations are stylized comments that start with /*@ and end with @*/.[7] Following prototypes are examples to this.

/*@null@*/ before the return type indicates that the function may return—in addition to a pointer to some memory region—a NULL value. Such a possibility means a careless programmer may end up trying to use non-existent Employee objects. Something we would not like to see happening and our friend, Splint, will not let it happen. The programmer must either make sure the returned value is not NULL or put up with the complaints of Splint. For different ways of assuring Splint that the returned pointer will never be NULL, take a look at here.

The preceding paragraph is actually a rephrase of the "declare before use" rule we stated in the Sample C Programs chapter. The compiler is contented with declaration of identifiers and [in the case of external entities when the corresponding definition cannot be found in the current preprocessed file] defers the control for the existence of a corresponding definition to the linker, which will not let you use an undefined identifier. In other words, compiler-linker duo is responsible for ensuring undefined identifiers are not used. However, since the heap region is managed by the programmer—in other words, objects are dynamically allocated [defined] by the programmer—it is not possible for the compiler-linker duo, which complete their job before run-time and are therefore static in nature, to enforce this rule for objects allocated in the heap. Programmer must walk the extra mile! In a programming language such as Java, this translates to guarding against the relevant exception, whereas in C it means a check against NULL.

A complementary annotation is /*@notnull@*/, which states that related identifier cannot have NULL as its value.[8] For instance, destruction can take place only when it is applied on an existing object, which implies the argument passed to the destructor function must be non-NULL. Using this as an assurance, Splint will not let you pass some variable that can possibly have NULL as its value.

As for the /*@reldef@*/ annotation, it is used to relax definition checking for the relevant declaration. Storage annotated as such is assumed to be defined when it is used; no error is reported if it is not defined before it is returned or passed as a parameter.

Definition: An object [that is, a memory region] is said to be defined if storage required for it has been allocated.[9] An object is completely defined if all storage that may be reached from it is defined.

typedef struct _STUDENT { char* name; int no; } *Student; ... Student std1 = (Student) malloc(sizeof(struct _STUDENT)); /* At this point, std1 is defined. However, it is not completely defined. */ std1->name = (char*) malloc(20); /* std1 is now completely defined. */

In our example, construction of an Employee-derived object—Engineer or Manager—is completed in two different constructor functions. After allocating memory in either one of Engineer_Create or Manager_Create, control is passed to Employee_Create to initialize certain fields. Initialization is later completed in the caller of Employee_Create. This means there will be uninitialized fields on entry to Employee_Create, which further implies there may be undefined fields.[10]

17 extern /*@null@*/ Employee Employee_Create
18   (/*@reldef@*/ Employee, /*@notnull@*/ char*, Dept_Type, Employee_Functions);
19 extern void Employee_Destroy(/*@notnull@*/ /*@reldef@*/ Employee);

The /*@in@*/ annotation is used to denote a parameter that corresponds to an input argument and therefore must be completely defined. In our case, successfully constructing an Employee-derived object guarantees its complete definition.

Note, unless otherwise specified, Splint assumes all unannotated references—storage reachable from global variables, arguments, and return values—to be completely defined. One can relax this requirement by turning impouts flag off.

21 extern Dept_Type Employee_GetDepartment(/*@in@*/ const Employee);
22 extern /*@null@*/ char* Employee_GetName(/*@in@*/ const Employee);
23 
24 #include “misc/inheritance/Employee_Private.h”
25 
26 #endif


Employee.c
1 #include <assert.h>
2 #include <stddef.h>
3 #include <stdio.h>
4 #include <stdlib.h>
5 #include <string.h>
6 
7 #include “misc/inheritance/Employee.h”

An Employee object can exist only as a sub-object of another object. This containing object is passed as the first argument to our constructor-like function. If this argument value is NULL, which means there is no containing object, we return without creating an Employee object. After all the notion of an Employee is an abstract one and cannot be concretized.

However, the concept of an Engineer is a concrete one and we can therefore create its object, which in turn contains an Employee part. The following constructor-like function serves to make allocations and initializations for this part.

 9 Employee Employee_Create(Employee this, char* name, Dept_Type dept, Employee_Functions funcs) {
10   if (!this) {
11     fprintf(stderr, Cannot create Employee object...\n);
12     return NULL;
13   } /* end of if(!this) */
14 
15   this->name = (char *) malloc(strlen(name) + 1);

Following if statement is here to convince Splint about our awareness of the possibility of a NULL return value from malloc. Thanks to this control we will not be harassed by Splint’s annoying warnings.[11]

In case you may be sure the result will never be NULL—or you simply don’t care—and you don’t want to write this code fragment you may turn this control off by annotating every use of this->name with /*@-nullpass@*/.[12]

16   if(this->name == NULL) {
17     fprintf(stderr, Out of memory...\n);
18     return NULL;
19   } /* end of if (this->name == NULL) */
20   strcpy(this->name, name);
21 
22   this->department = dept;
23   this->calc_salary = funcs.calc_salary;
24   this->calc_bonus = funcs.calc_bonus;

If the value returned by the following statement is assigned to a global variable the underlying memory region will be shared between this [global] variable and the actual parameter.

... Employee global_emp; void func(...) { ... global_emp = Employee_Create(receiver_obj, ...); /* global_emp and receiver_obj is sharing the same underlying object. */ ... } /* end of void func(...)

Considering the programmer may well forget this and go on to free it through the argument, Splint steps in to give us a warning. Not concerned about it, we remove the check by means of the /*@-temptrans@*/ flag.

26   /*@-temptrans@*/ return this;
27 } /* end of Employee Employee_Create(Employee, char*, int, Employee_Functions) */

Remember the object being destroyed may be anything that derives from Employee. That is, if needed, we can extend the definition of Employee and come up with a new type, such as Manager. How we extend the base type is however unknown to the base type itself. For this reason, our destructor-like function frees only those regions common to all Employees.[13]

29 void Employee_Destroy(Employee this) { free(this->name); }

Returning the department an Engineer works in is no different than returning the department of a Manager. Same can be said about returning her name. Regardless of which type of Employee object we deal with—be that an Engineer, Manager, or even a type that is yet to be defined—the way the answer is provided will always be the same. Therefore, instead of duplicating this unchanging behavior in all modules, it’ll be wise to put such functionality in a central repository, which in our case happens to be the base type, Employee.

Thanks to this property of constant behavior—unlike salary and bonus calculation functions—these functions need not be invoked through pointers-to function, which implies binding of a call to either one of these functions can be performed before run-time.

Definition: A function call bound before run-time is said to be statically dispatched. For such a function, one can find out which function definition will be executed upon making the function call by simply reading the source code.

31 int Employee_GetDepartment(const Employee this) {
32   return(this->department);
33 } /* end of int Employee_GetDepartment(const Employee) */
34 
35 char* Employee_GetName(const Employee this) {
36   char* ret_str = (char *) malloc(strlen(this->name) + 1);
37 
38   if(!ret_str) {
39     fprintf(stderr, Out of memory...\n);
40     return NULL;
41   } /* end of if(!ret_str) */
42   strcpy(ret_str, this->name);
43 
44   return(ret_str);
45 } /* end of char* Employee_GetName(const Employee) */

Subclasses[edit]

Engineer_Private.h
1 #ifndef ENGINEER_PRIVATE_H
2 #define ENGINEER_PRIVATE_H
3 
4 #include “misc/inheritance/Engineer.h”
5 
6 #define ENGINEER_PART \
7   Discipline disc;

An Engineer object is made up of two parts: its Employee sub-object and Engineer sub-object.

 9 struct _ENGINEER {
10   EMPLOYEE_PART
11   ENGINEER_PART
12 };
13 
14 #endif


Engineer.h
 1 #ifndef ENGINEER_H 
 2 #define ENGINEER_H
 3 
 4 #include “misc/inheritance/Employee.h”
 5 
 6 struct _ENGINEER;
 7 typedef struct _ENGINEER* Engineer;
 8 typedef enum _DISCIPLINE { CSE, EE, IE, ME } Discipline;
 9 
10 extern /*@null@*/ Engineer Engineer_Create
11   (/*@null@*/ Engineer, char*, Dept_Type, Discipline, Employee_Functions);
12 extern void Engineer_Destroy(/*@reldef@*/ Engineer);
13 
14 extern long Engineer_CalcSalary(const Employee);
15 extern long Engineer_CalcBonus(const Employee);
16 extern Discipline Engineer_GetDiscipline(const Engineer);
17 
18 #include “misc/inheritance/Engineer_Private.h”
19 
20 #endif


Engineer.c
 1 #include <stddef.h>
 2 #include <stdio.h>
 3 #include <stdlib.h>
 4 
 5 #include "misc/inheritance/Employee.h"
 6 #include "misc/inheritance/Engineer.h"
 7 
 8 Engineer Engineer_Create(Engineer this, char* name, Dept_Type dept, Discipline disc, Employee_Functions funcs) {
 9   Engineer res_obj, temp;
10 
11   if (!this) {

Note the following allocation reserves memory big enough to hold the Employee and Engineer sub-objects. Next thing we do, after memory allocation, is calling the constructor-like function of the base type, Employee_Create. Realize we do this before we initialize the Engineer-specific part of the object. The logic behind this is we may make use of certain information from the Employee part initialization for completing the Engineer-part initialization. However, it never happens the other way around: you don’t make use of Engineer-part information to complete the Employee-part initialization.

12     res_obj = (Engineer) malloc(sizeof(struct _ENGINEER));
13     if (!res_obj) {
14       fprintf(stderr,  Cannot create Engineer object…\n );
15       return NULL;
16     } /* end of if(!res_obj) */
17   } else res_obj = this;
18 
19   temp = res_obj;
20   res_obj = (Engineer) Employee_Create((Employee) res_obj, name, dept, funcs);
21   if (res_obj == NULL) {
22     free(/*@-temptrans@*/ temp);
23     return NULL;
24   } /* end of if (res_obj == NULL)*/
25   res_obj->disc = disc;
26 
27   return res_obj;
28 } /* end of Engineer Engineer_Create(Engineer, char*, Dept_Type, Discipline, Employee_Functions) */
29 
30 void Engineer_Destroy(Engineer this) {
31   Employee_Destroy((Employee) this);
32   free(this);
33 } /* end of void Engineer_Destroy(Engineer) */

Now that Engineer is a concrete type we must provide implementations for salary and bonus calculations.

Note that the sole parameter in the function header is not used in the function body. A rather peculiar situation! Not surprisingly, Splint agrees with us on that and reports the potential problem source. Not caring a bit about the advice in this occasion, we get rid of the annoying warning by lifting the particular requirement. This is done by the /*@-paramuse@*/ flag.

35 long Engineer_CalcSalary(/*@-paramuse@*/ const Employee this) {
36   return(1000);
37 } /* end of long Engineer_CalcSalary(const Employee) */
38 
39 long Engineer_CalcBonus(/*@-paramuse@*/ const Employee this) {
40   return(300);
41 } /* end of long Engineer_CalcBonus(const Employee) */
42 
43 Discipline Engineer_GetDiscipline(const Engineer this) {
44   return(this->disc);
45 } /* end of Discipline Engineer_GetDiscipline(const Engineer) */


Manager_Private.h
 1 #ifndef MANAGER_PRIVATE_H
 2 #define MANAGER_PRIVATE_H
 3 
 4 #include "misc/inheritance/Manager.h"
 5 
 6 #define MANAGER_PART \
 7   Bool bribes;
 8 
 9 struct _MANAGER {
10   EMPLOYEE_PART
11   MANAGER_PART
12 };
13 
14 #endif


Manager.h
1 #ifndef MANAGER_H 
2 #define MANAGER_H
3 
4 #include "misc/inheritance/Employee.h"
5 
6 struct _MANAGER;
7 typedef struct _MANAGER* Manager;

Next annotation marks the type declaration as that of a Boolean type. Identifiers defined to belong to this type (Bool) can only be used—unless otherwise specified with some other annotation—in a Boolean context.

/*@-likelybool@*/ typedef enum _BOOL { FALSE, TRUE } Bool;

extern /*@null@*/ Manager Manager_Create
  (/*@null@*/ Manager, char*, Dept_Type, Bool, Employee_Functions);
extern void Manager_Destroy(/*@reldef@*/ Manager);

extern long Manager_CalcSalary(const Employee);
extern long Manager_CalcBonus(const Employee);
extern Bool Manager_GetBribing(const Manager);
extern void Manager_SetBribing(const Manager);

#include "misc/inheritance/Manager_Private.h"

#endif


Manager.c
 1 #include <stddef.h>
 2 #include <stdio.h>
 3 #include <stdlib.h>
 4 
 5 #include "misc/inheritance/Employee.h"
 6 #include "misc/inheritance/Manager.h"
 7 
 8 Manager Manager_Create(Manager this, char* name, Dept_Type dept, Bool bribes, Employee_Functions funcs) {
 9   Manager res_obj, temp;
10 
11   if (!this) {
12     res_obj = (Manager) malloc(sizeof(struct _MANAGER));
13     if (!res_obj) {
14       fprintf(stderr,  Cannot create Manager object…\n );
15       return NULL;
16     } /* end of if(!res_obj) */
17   } else res_obj = this;
18 
19   temp = res_obj;
20   res_obj = (Manager) Employee_Create((Employee) res_obj, name, dept, funcs);
21   if (!res_obj) {
22     free(/*@-temptrans@*/ temp);
23     return NULL;
24   } /* end of if(!res_obj) */
25   res_obj->bribes = bribes;
26 
27   return res_obj;
28 } /* Manager Manager_Create(Manager, char*, Dept_Type, Bool, Employee_Functions) */
29 
30 void Manager_Destroy(Manager this) {
31   Employee_Destroy((Employee) this);
32   free(this);
33 } /* end of void Manager_Destroy(Manager) */
34 
35 long Manager_CalcSalary(/*@-paramuse@*/ const Employee this) {
36   return(1500);
37 } /* end of long Manager_CalcSalary(const Employee) */
38 
39 long Manager_CalcBonus(const Employee this) {

Indulging ourselves in some genuine C programming, we find ourselves using a Boolean value as an integer. That is not against the rules of C but Splint sees it as a potential bug and issues a warning. We should either take some corrective action, if there is any mistake, or relax the rules of Splint. We take the second path and state this using the /*@+boolint@*/ annotation.

40   long tot_bonus = 1000 * (2 * /*@+boolint@*/ ((Manager) this)->bribes + 1);
41 
42   return(tot_bonus);
43 } /* end of long Manager_CalcBonus(const Employee) */
44 
45 Bool Manager_GetBribing(const Manager this) { return(this->bribes); }
46 
47 void Manager_SetBribing(const Manager this) { this->bribes = TRUE; }

Test Program[edit]

Inheritance_Test.c
1 #include <assert.h>
2 #include <stddef.h>
3 #include <stdio.h>
4 #include <stdlib.h>
5 
6 #include "misc/inheritance/Employee.h"
7 #include "misc/inheritance/Engineer.h"
8 #include "misc/inheritance/Manager.h"

Following annotation changes the special character that is used to mark the start and end of the stylized comments. From this point on, annotations will start with /*! and end with !*/.

10 /*@-commentchar !@*/

print_tot_salary calculates the total payment [that is, bonus plus base salary] made to the Employees listed in the array passed to it. Each component of the array can be either an Engineer or a Manager. We cannot be sure of which component will belong to which group. But we can be certain about one thing: regardless of its real type, each component can be treated as an Employee. However, this has a limitation of its own: an Employee can answer questions that can be asked to all objects of the derived types.[14] And indeed, that’s what we do: get the name of the Employee, calculate her salary and her bonus. We do not, for instance, inquire her discipline. Neither do we ask whether she accepts bribes or not. Because, the former is special to Engineers while the latter is special to Managers.

12 void print_tot_salary(Employee payroll[], int no_of_emps) {
13   int i;
14   long tot_payment = 0, bonus, salary;
15   char* nm;
16 
17   if (no_of_emps) printf("\nNAME\tSALARY\tBONUS\tTOTAL\n");
18   for(i = 0; i < no_of_emps; i++) {

Next function call will be resolved before run-time—at link-time, to be exact—while the following two will be resolved at run-time. This is due to the fact that pointer values stored in calc_salary and calc_bonus are determined at the time of object creation, which happens during run-time.

Definition: Resolution of a function’s address at run-time is called dynamic dispatch. The ability of the same call resolving to different functions is called polymorphism.

Since Employee_GetName returns a possibly NULL value, which is later passed to printf, Splint raises an objection: How can you be so sure about printing the contents of a possibly non-existing character string? Well, I cannot but in this context it doesn’t seem very likely. So, I relax null-checking and let this one slip.[15]

19     printf("%s\t", /*!-nullpass!*/ (nm = Employee_GetName(payroll[i])));
20     free(nm);
21     printf("%d\t", (salary = payroll[i]->calc_salary(payroll[i])));
22     printf("%d\t", (bonus = payroll[i]->calc_bonus(payroll[i])));
23     printf("%d\n", salary + bonus);
24     tot_payment += bonus + salary;
25   } /* end of for (i = 0; i < no_of_emps; i++)
26   printf("\nTotal payment: %d\n", tot_payment);
27 } /* end of void print_tot_salary(Employee**) */
28 
29 int main(void) {
30   Employee_Functions eng_funcs = { &Engineer_CalcSalary, &Engineer_CalcBonus };
31   Employee_Functions mng_funcs = { &Manager_CalcSalary, &Manager_CalcBonus };
32   Employee emps[4];

Note we cast each Engineer/Manager to an Employee. This will allow us to treat them the same way. Well, almost! The list of functions invoked using these objects will be limited to those that can be invoked using an Employee. However, the function invoked will depend on the type of the underlying object.[16]

The Employee type here [that is, the type of the pointer variable] is called the static type—because it can be determined by reading the source code—whereas the type of the underlying object [that is, the type of the region pointed to by the pointer] is referred to as the dynamic type.[17]

C++ equivalent of the next line is given below, which reflects the compiler's behind-the-scenes effort. Thanks to the inheritance relationship between Employee and Engineer, which is relayed to the compiler by means of the ':' operator, the cast is now implicit. Similarly, NULL argument meaning an Engineer object—not an object of a class inheriting from the Engineer class—is being created is not needed anymore. Finally, pointer values needed in the process of dynamic dispatch are gathered by the compiler—upon seeing the virtual keyword before the function signature—and inserted into a structure called vtable, which in turn is pointed to by an implicit field in the object being created, as a result of executing the compiler-synthesized code fragment.

emps[0] = new Engineer("Eng 1", RESEARCH, EE);
34   emps[0] = (Employee) Engineer_Create(NULL, "Eng 1", RESEARCH , EE, eng_funcs);

Notice the different ways of convincing Splint about our awareness of the probable NULL value returned from the constructor function. All of these guarantee that a NULL pointer will never be dereferenced. In the first technique, we accomplish this by making an assertion that returned pointer is not null, which means the program will terminate upon receiving a NULL from Engineer_Create. Second and third techniques basically boil down to the same thing: by means of an if-statement the goal is achieved by explicitly checking the returned value against NULL.

35   assert(emps[0] != NULL);
36 
37   emps[1] = (Employee) Manager_Create(NULL, "Mng 1", MANAGEMENT, TRUE, mng_funcs);
38   if (emps[1] == NULL) {
39     fprintf(stderr, "Cannot create emps[1]...\n");
40     exit(EXIT_FAILURE);
41   } /* end of if(emps[1] == NULL) */
42 
43   emps[2] = (Employee) Manager_Create(NULL, "Mng 2", MANAGEMENT, FALSE, mng_funcs);
44   if (!emps[2]) {
45     fprintf(stderr, "Cannot create emps[2]...\n");
46     exit(EXIT_FAILURE);
47   } /* end of if(!emps[2]) */
48 
49   emps[3] = (Employee) Engineer_Create(NULL, "Eng 2", RESEARCH, CSE, eng_funcs);
50   assert(emps[3] != NULL);
51   print_tot_salary(emps, 4);
52 
53   exit(0);
54 } /* end of int main(void) */

Running the Test Program[edit]

splint -ID:/include Employee.c↵ # In Cygwin
Splint 3.1.1 --- 02 May 2003
Finished checking --- no warnings
splint -ID:/include Engineer.c↵
Splint 3.1.1 --- 02 May 2003
Finished checking --- no warnings
splint -ID:/include Manager.c↵
Splint 3.1.1 --- 02 May 2003
Finished checking --- no warnings
splint -ID:/include Inheritance_Test.c↵
Splint 3.1.1 --- 02 May 2003
Finished checking --- no warnings
gcc -ID:/include –o InheritanceTest.exe Inheritance_Test.c Employee.c Engineer.c Manager.c↵
Inheritance_Test↵
NAME SALARY BONUS TOTAL
Eng 1 1000 300 1300
Mng 1 1500 3000 4500
Mng 2 1500 1000 2500
Eng 2 1000 300 1300

Total payment: 9600

Notes[edit]

  1. Reuse for the sake of reuse will not always save you precious resources. For this to be possible, it must be made part of the organization’s policies.
  2. Similar arguments can be made for reusing analysis and design documents.
  3. In this chapter, when we say inheritance we actually mean "inheritance together with polymorphism", which is what we understand from the natural application of the concept. However, as we will see in the Inheritance chapter, C++ allows inheritance to be used without polymorphism.
  4. In the case of multiple inheritance, derivation can be from multiple classes.
  5. We will have to rephrase this when we come to virtual inheritance in C++.
  6. ref!!!
  7. You can choose another character to play the role of “@”. For an example, check the test program.
  8. Unless otherwise told Splint assumes all pointer variables are annotated with /*@notnull@*/.
  9. A pointer with a NULL value is completely defined.
  10. s a matter of fact, name field will be undefined and therefore uninitialized.
  11. This is not the only way to convince Splint. For alternatives, check Inheritance_Test.c.
  12. Flags are supported to modify the behavior of Splint. Preceding a flag with + means it is on; preceding it with - means it is off.
  13. How can it possibly free regions that it doesn’t know about? After all, all Managers are also Employees, but all Employees are not Managers.
  14. This is another way of saying that messages sent to an object are filtered through its handle.
  15. In a larger program, doing so would not be the best of all choices. Making an assertion about the value of nm or checking it against NULL would be much wiser.
  16. Remember "The messages sent to an object is limited to ..."
  17. Read the "pointer variable" as "handle", "region pointed to by the pointer" as "object" and it will start to get more object-oriented.