This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License
In this lesson, you will learn how to implement representations of complex objects, as in object-oriented programming. C is most definitely not an object-oriented programming language, but with a few basic coding patterns and some discipline, you can have many of the benefits of OOP in your C programs.
Each step of the lesson asks you to answer a question or write some code. Do the steps yourself before checking your work by revealing the answer.
If you have no idea, check out Chapter~7 of the C for Java Programmers guide.
struct
to define a “structured type” (a type composed of other types):
struct Employee {
char* name;
int id;
};
The only difference other than class
vs. struct
is that there is no builtin string type in C. Instead, strings in C are simply arrays of single-byte characters (char
s). Since arrays in C are simply pointers to the first element of the array, strings are type char*
in C.
By the way, if you skipped the C for Java Programmers document, now is probably the time to take a look.
Employee
class:
public Employee(char *name, int id) {
this.name = name;
this.id = id;
}
You create a new instance by calling the constructor:
Employee e = new Employee("Alan Turing", 123);
struct Employee
type, initializes it, and returns the pointer to it.
struct Employee* new_Employee(char *name, int id) {
struct Employee* this = (struct Employee*)malloc(sizeof(struct Employee));
if (this == NULL) {
return NULL; // Out of memory...
}
this->name = name;
this->id = id;
return this;
}
This pattern is described and explained in Section 8.7 of the C for Java Programmers document.
Note that you will need to #include <stdlib.h>
to use malloc
in your program.
Also note that since strings (char*
’s) are reference types (in both C and Java), and since C does NOT manage memory for you, there are some subtleties involved in storing strings passed as arguments inside objects. For now it’s ok and we will return to it at the end of this lesson.
Then just like in Java, you create a new instance by calling the “constructor”:
struct Employee* e = new_Employee("Alan Turing", 123);
The only differences are the explicit need to indicate “pointer to struct” in the type (which you could avoid with a typedef
; see the Reusable Code lesson), and the underscore that makes new
part of the function name rather than a reserved word (in Java) that invokes a constructor.
toString()
method to your Employee
class, something like:
public String toString() {
return "Employee[" + this.name + "]";
}
You could also write a method that just prints this to System.out
(or some other I/O stream).
#include <stdio.h>
to use printf
in your program.
A couple of things to note about this:
Employee
”) is included in the function name since we may have other types that need a print function, but there can only be one function with a given name. This applies to any function that is intended to be a “method” (that is, to work on instances of a type).e.toString()
, and the value of e
becomes the value of the special variable this
. This C code works the same way, but unmagically.printf
to output a newline. That allows whoever is calling the function to handle the layout as it sees fit. You could do otherwise.main
function to what we have already. Something like:
int main(int argc, char* argv[]) {
struct Employee* e1 = new_Employee("Alan Turing", 123);
Employee_print(e1);
struct Employee* e2 = new_Employee("Edsgar Dijkstra", 456);
Employee_print(e2);
struct Employee* e3 = new_Employee("Alan Kay", 789);
Employee_print(e3);
}
When you run this program, the employees will all print one after each other on the same line with no spacing or newlines. You could add calls to printf
in between if you wanted.
Download full program: employee1.c
Employee
object in Java?
private
or protected
. Then you would write getters and setters for them:
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public int getId() {
return this.id;
}
public void setId(int id) {
this.id = id;
}
private
and protected
(although there are ways to achieve similar effects). But you can still write getters and setters (a.k.a. accessors and mutators) for the properties of an Employee
. So do that.
#define
to generate the code for you. Macros are beyond the scope of this tutorial, but suffice to say that C++ started from the macros that programmers had developed for doing object-oriented programming in C.
Download full program: employee2.c
If you read the C for Java Programmers document, you know that Java looks after allocating and deallocating memory for you but C does not. We’ve already seen how to use malloc
to allocate memory for new instances of reference types, which is what new
does in Java.
What about deallocating instances when we no longer need them? For example, when a local variable goes out of scope, its value “vanishes.” If that value is a reference to an object, and there are no other references to the object, then there is no way for your program to refer to the object. Or consider what happens when an element is removed from a list, tree, or graph. The node storing the element is no longer part of the collection and almost certainly will never be refrenced again. If the memory used by objects like these is not released by to the system, your program may over time run out of memory.
In Java, the Java runtime system looks after recognizing and deallocating “dead” objects. In C, you the programmer must figure out when an object can be deallocated and call free
to release the memory back to the C runtime system. You may free
any pointer obtained from malloc
. Attempting to free a pointer that was not obtained from malloc
or attempting to dereference (use) a pointer that has already been free
’d will cause a range of problems. These are discussed briefly in the C for Java Programmers document.
For simple programs with simple data structures, you can just call free
on pointers when you know they are no longer needed. But for for more complex types, such as in object-oriented programming, it’s better to have an explicit function to free references to instances of the type.
For starters, write a function to free an Employee
instance.
What if the “object” you are freeing contains references to other objects? For example, an Employee
might have a member (“instance variable”) that refers to another Employee
that is their manager. A list node would have a reference to the next node in the list (and perhaps the previous node also). A tree node has references to its children. These are complicated cases and the last two are discussed in subsequent lessons.
But our struct Employee
does have a member that is of a reference type. What is it?
name
member is of type char*
, pointer to char
, which is a reference to a block of memory containing characters.
Employee_free
method so that it frees the name of the Employee
instance also.
struct Employee
instance). If you did it in the other order, you would be using something, namely the value of the name
member of the struct Employee
after the memory for the struct Employee
had been freed. That is not allowed, as noted above.
main
function to free one of the allocated Employee
s.
main
:
Employee_free(e1);
One of the rules (noted above) is that you may not free a pointer that was not obtained from malloc
. The value of the name
member of the struct Employee
is indeed a pointer. But did we get it from malloc
?
No we did not. It comes from the string literal "Alan Turing"
. Since we did not get it from malloc
, we cannot give it to free
. On the other hand, the pointer stored in the instance of the struct Employee
might have been obtained from malloc
. The fact is that the C compiler and runtime can’t know for sure either way.
struct Employee
. In that case it’s someone else’s problem to free the memory used by the name if and when it is appropriate to do so.struct Employee
always come from malloc
, and therefore always be ok to free.The first approach makes sense if names are being used in other places in your program. In other words, if the reference to the name can be stored in program variables or as the value of members of other reference types (other “objects”).
On the other hand, your program somehow needs to know when a name is no longer being used at all, in order to know when to free it. If the name is never freed, then its memory will never be returned to the system, even if the program no longer has any way to refer to it. This is called a “memory leak,” and eventually your program will run out of memory.
A key aspect of Java is that it automates the process of figuring out when to deallocate objects and free memory. This called “garbage collection.” You can look it up. There are pros and cons to it also. Welcome to Computer Science.
The second approach seems easier, but unfortunately C can’t enforce the requirement automatically. So it’s up to you the programmer to make sure that the name in a struct Employee
is always allocated by malloc
and that the reference is not shared with any other part of your program. The next step in this lesson shows you how.
Furthermore, if each reference to the name is unique, and the name is used in several places in the program, then there will be multiple copies of the same name. Not only is that probably a waste of memory, but it is possible to change the contents of one of the copies of the name without changing the others. Indeed, the code almost certainly doesn’t even know about the other copies.
Another important aspect of Java is that String
s are immutable (cannot be changed). This means that, in principle, only one copy of any string is ever needed and it can be shared between objects that need to refer to that string. And in fact, Java does try to minimize copies of strings, for example by prescribing that all string literals or strings computed by constant expressions, where the strings have the same content, refer to the same String
object. However you should know from a discussion of the difference between identity (“==
”) and equality (“equals
”) that in general, strings with the same contents are not always identical. See The Java Language Specification, 4th Edition, Example 3.10.5-1, page 38.
struct Employee
is always allocated by malloc
. First, write a function name strdup
(“string duplicate”) to allocate, initialize, and return a new copy of a given string.
#include <string.h>
to get strlen
and strcpy
. Or you could write them yourself. Try it!
And by the way, once you’re using the string functions of the C standard library, you may find that strdup
is already there for you. But it’s better to know how it works for yourself, right? Furthermore, it's not part of C Standard Library for the “C99” dialect of C, which is what we recommend you stick with until you know better.
strdup
to ensure that the name of an struct Employee
is always allocated by malloc
so that it can be safely freed.
new_Employee
to make a copy of the name and store the copy in the “object”:
this->name = strdup(name);
Note that you must also do this in any other place that sets the name of a struct Employee
, like for example in Employee_set_name
. Make both changes, recompile the program, and it should run without crashing.
Download full program: employee3.c