Object-Oriented Programming in C
Let’s do object-oriented programming in C. First of I am creating an easy example for an Vector2 type. To define a Vector2 in C we use a struct.
|
|
When you know C#, you are already familiar with a struct
. A struct
is a value-type in C. It always gets copied as a whole. In C# we have a class that is a reference-type but this doesn’t exist in C. But, we can make one. In C we always can create a Pointer to any value. A pointer is like a reference you are used in more “modern” languages.
In C we can do pointer-arithmetic and add, subtract or change a pointer, but here we will not do this. On a reference this is forbidden. But still the concept behind it is the same.
Creating an instance of Vector2
becomes:
|
|
Now I want to add an easy way to just print an vector. This is how the method vector2_show()
will look.
|
|
Now we can do:
|
|
it will print …
X=3; Y=4
… to the console.
Let’s add an setter to change X
value of an Vector2
. We implement it like this.
|
|
but somehow this doesn’t work as expected. When we write the following code.
|
|
no change has been happening. It will just print the same vector twice.
|
|
The reason for this is as you guessed. Because Vector2
is a value-type and not a reference type. So instead of directly passing a Vector2
as a copy, we just want to pass it a pointer to a Vector2
instead. A pointer-type in C are declared with an additional *
.
We can get a pointer to any variable by just adding a &
(address-operator) before it. So we can do the following.
|
|
Here pv1
is now a pointer to v1
. But we cannot pass a pointer to the vector2_set_x()
or vector2_show()
methods we implemented so far because vector2_show()
expected a Vector2
not a pointer to a Vector2
.
Additionaly when we pass a pointer, we need to de-reference the pointer to the actual value. How do we do this? Perl programmers are familiar with this. We de-reference a pointer with the arrow ->
operator.
|
|
Now we can do the following.
|
|
Running this code will print us.
|
|
But when I want to add a constructor this way, it will not work.
|
|
gcc
produces the following warning.
vector2.c: In function ‘vector2_new’:
vector2.c:11:12: warning: function returns address of local variable [-Wreturn-local-addr]
11 | return &v;
| ^~
Trying to run the compiled program produces a Segmentation Fault on my machine. But behavior could be different. C is actually not a very strongly typed language.
Why does the constructor not work? Because we are creating a local variable. And those local variables are created on the Stack not in the Heap. A Stack variable will be freed after the function finish. So we cannot return a pointer to a local variable. This will cause problems.
Instead we need to create a Vector2
in the Heap. Then we are allowed to return a pointer. Actually the function malloc
in C does that. It allocates memory on the Heap and return a pointer to the allocated memory. Here are all the corrected constructor and methods so far.
|
|
Now we can write:
|
|
and it will print:
X=1; Y=1
X=4; Y=1
But remember. C does not have automatic memory-management. It doesn’t automatically free any memory. You must explicitly free memory calling the free()
function. This is an example I prefer to be implicit instead of being explicit. Other programmers will have a different opinion about this.
If you don’t free the memory, you will have Memory Leaks in your program. Also be aware if you free()
memory and still try to access (Read/Write) to a pointer, bad things will happen.
Here is a full example.
|
|
You can compile this file with gcc vector2.c -o vector -lm
. When running, it will print.
|
|
What is object-orientation?
Object-Orientation was basically just a pattern in C. At some time people decided that instead of a Design Pattern it should be a language feature.
For better or for worse. I think object-orientation made programming a lot more worse. People acted as as if it would be something never seen. Fanatics all over the world started to came upon there graves telling that everything not object-oriented must be bad, and must be converted. The dark ages of programming started.
Object-orientation is useful and has it purpose. It fits the time perfectly. When you have limited memory and computational resources, it makes sense to pass pointers instead of copying structs over and over again.
And still, when you look to the C# community as an example, those programmers start to realize more and more the importance of allocating things on the Stack and why it is important to also be able to allocate classes on the Stack instead of always allocating them in the Heap. Bypassing the garbage collector for performance.
C# started by making the distinction not be seen by a user. But still the distinction exists. Starting programmers must learn the distinction between Value-Type and a Reference-type in C#. Otherwise there will have a really bad time.
C# nowadays adds back a lot of the stuff to create a class on the stack. Returning reference on structs and so on. After they dismissed the features from C
, decades later they started to re-implement then again. How ironic.
Doesn’t this make you think. Wasn’t it better how C
made it? I really think that making stuff explicit is at some cases better. Handling of pointers is such a case.
It also shows you one of the most important aspect of what object-oriented programming really is.
Object-oriented programming is nothing more than passing a data-structure to a function and directly mutate the structure.
“Modern” languages like C# just have a different syntax. Instead of
|
|
now it becomes
|
|
but under the hood, nothing really changed at all. Both are functions getting obj
as the first argument. That’s why in Perl or Python you explicitly have $self
as an argument on every method.
C#
hides it, but it gives you the special this
variable instead. Did you ever implemented a Extension Method in C#? For example.
|
|
Now you probably understand why you write this int i
as the first argument. In C, you don’t even need this feature.