Reputation:
I am learning C, mainly by K&R, but now I have found an Object Oriented C pdf tutorial and am fascinated. I'm going through it, but my C skills/knowledge may not be up to the task. This is the tutorial: http://www.planetpdf.com/codecuts/pdfs/ooc.pdf
My question comes from looking at many different functions in the first couple of chapters of the pdf. Below is one of them. (page 14 of pdf)
void delete(void * self){
const struct Class ** cp = self;
if (self&&*cp&&(*cp)->dtor)
self = (*cp)->dtor(self);
free(self);
}
dtor is a destructor function pointer. But knowledge of this isn't really necessary for my questions.
Thanks in advance
Upvotes: 7
Views: 1887
Reputation: 11536
Interesting pdf.
My first question is, why is **cp constant? Is it necessary or just being thorough so the code writer doesn't do anything damaging by accident?
It's necessary so the writer doesn't do anything by accident, yes, and to communicate something about the nature of the pointer and its use to the reader of the code.
Secondly, why is cp a pointer-to-a-pointer (double asterisk?). The struct class was defined on page 12 of the pdf. I don't understand why it can't be a single pointer, since we are casting the self pointer to a Class pointer, it seems.
Take a look at the definition of new()
(pg 13) where the pointer p
is created (the same pointer that's passed as self
to delete()
):
void * new (const void * _class, ...)
{
const struct Class * class = _class;
void * p = calloc(1, class —> size);
* (const struct Class **) p = class;
So, 'p' is allocated space, then dereferenced and assigned a pointer value (the address in class; this is like dereferencing and assigning to an int pointer, but instead of an int, we're assigning an address). This means the first thing in p is a pointer to its class definition. However, p was allocated space for more than just that (it will also hold the object's instance data). Now consider delete()
again:
const struct Class ** cp = self;
if (self&&*cp&&(*cp)->dtor)
When cp is dereferenced, since it was a pointer to a pointer, it's now a pointer. What does a pointer contain? An address. What address? The pointer to the class definition that's at the beginning of the block pointed to by p.
This is sort of clever, because p's not really a pointer to a pointer -- it has a larger chunk of memory allocated which contains the specific object data. However, at the very beginning of that block is an address (the address of the class definition), so if p is dereferenced into a pointer (via casting or cp), you have access to that definition. So, the class definition exists only in one place, but each instance of that class contains a reference to the definition. Make sense? It would be clearer if p were typed as a struct like this:
struct object {
struct class *class;
[...]
};
Then you could just use something like p->class->dtor()
instead of the existing code in delete()
. However, this would mess up and complicate the larger picture.
Thirdly, how is a void pointer being changed to a Class pointer (or pointer-to-a-Class-pointer)? I think this question most shows my lack of understanding of C. What I imagine in my head is a void pointer taking up a set amount of memory, but it must be less than Class pointer, because a Class has a lot of "stuff" in it.
A pointer is like an int -- it has a small, set size for holding a value. That value is a memory address. When you dereference a pointer (via *
or ->
) what you are accessing is the memory at that address. But since memory addresses are all the same length (eg, 8 bytes on a 64-bit system) pointers themselves are all the same size regardless of type. This is how the magic of the object pointer 'p' worked. To re-iterate: the first thing in the block of memory p
points to is an address, which allows it to function as a pointer to a pointer, and when that is dereferenced, you get the block of memory containing the class definition, which is separate from the instance data in p
.
Upvotes: 3
Reputation:
In this case, that's just a precaution. The function shouldn't be modifying the class (in fact, nothing should probably), so casting to const struct Class *
makes sure that the class is more difficult to inadvertently change.
I'm not super-familiar with the Object-Oriented C library being used here, but I suspect this is a nasty trick. The first pointer in self
is probably a reference to the class, so dereferencing self
will give a pointer to the class. In effect, self
can always be treated as a struct Class **
.
A diagram may help here:
+--------+
self -> | *class | -> [Class]
| .... |
| .... |
+--------+
Remember that all pointers are just addresses.* The type of a pointer has no bearing on the size of the pointer; they're all 32 or 64 bits wide, depending on your system, so you can convert from one type to another at any time. The compiler will warn you if you try to convert between types of pointer without a cast, but void *
pointers can always be converted to anything without a cast, as they're used throughout C to indicate a "generic" pointer.
*: There are some odd platforms where this isn't true, and different types of pointers are in fact sometimes different sizes. If you're using one of them, though, you'd know about it. In all probability, you aren't.
Upvotes: 2
Reputation: 57774
const
is used to cause a compilation error if the code attempts to change anything within the object pointed to. This is a safety feature when the programmer intends only to read the object and does not intend to change it.
**
is used because that must be what was passed to the function. It would be a grave programming error to re-declare it as something it is not.
A pointer is simply an address. On almost all modern CPUs, all addresses are the same size (32 bit or 64 bit). Changing a pointer from one type to another doesn't actually change the value. It says to regard what is at that address as a different layout of data.
Upvotes: 0