I am reading OOP in C, and there is this header:
#ifndef _NEW_H
#define _NEW_H
#include <stdarg.h>
#include <stddef.h>
#include <assert.h>
void *new (const void *type, ...);
void delete (void *item);
typedef struct
{
size_t size;
void *(*ctor)(void *self, va_list *app);
void *(*dtor)(void *self);
void *(*clone)(const void *self);
int (*differ)(const void *self, const void *x);
} class_t;
inline void *new (const void *_class, ...)
{
const class_t *class = _class;
void *p = calloc(1, class->size);
assert(p);
*(const class_t **)p = class; //why would you cast to double pointer, when you immediately dereference it?
if (class->ctor)
{
va_list args;
va_start(args, _class);
p = class->ctor(p, &args);
va_end(args);
}
return p;
}
#endif //_NEW_H
Now I don't understand this expression:
*(const class_t **)p = class;
What does it mean const class_t **
type? it is like an array of arrays? but if I want to have custom class (that is, not only pointer to the struct class_t
, but more "public" methods), the overall class is not an array of types class_t
. So Why would I cast a void pointer to double pointer and immediately dereference it? How should I understand it?
from the book about that statement:
* (const struct Class **) p = class;
p points to the beginning of the new memory area for the object. We force a conversion of p which treats the beginning of the object as a pointer to a struct class_t and set the argument class as the value of this pointer.
The call to calloc()
is being used to allocate an array of 1 element of an unspecified "class" type, where the first member of that type is expected to be a class_t*
pointer (thus class->size
must be at least sizeof(class_t*)
, but can be higher). calloc()
is likely being used instead of malloc()
just so that any additional data members represented by class->size
will be zero-initialized, otherwise an explicit memset()
would be needed.
The weird cast+dereference is just so that the code can store the input class
pointer directly into that 1st class_t*
member of that allocated object.
An array can be accessed using a double-pointer. Dereferencing such a pointer gives you the address of the 1st element in the array. Which in this case happens to also be the same address as the class_t*
member.
In OOP terms, the layout of an object in memory typically starts with a pointer to the object class's vtable, which contains a list of function pointers to the class's "virtual" methods. When a class is "derived" from, descendants "override" virtual methods by simply setting the object's vtable pointer to a new list of function pointers. This concept of OOP doesn't really exist in C, but it is fundamental to C++. In C, it has to be implemented manually, which is what this code is doing.
Basically, the code is allocating this memory layout for the allocated object:
------------ -------------------- void *p -> | class_t* | -> | size_t size | ------------ -------------------- | ... | | void (*ctor)() | ------------ -------------------- | void (*dtor)() | -------------------- | void (*clone)() | -------------------- | void (*differ)() | --------------------
Another way to accomplish the same assignment would be to use a typedef for the "class" type for easier access, eg the original code is equivalent to this:
typedef struct
{
class_t *vtable;
// other data members, if class->size > sizeof(class_t*) ...
} class_info_t;
inline void *new (const void *_class, ...)
{
const class_t *class = _class;
class_info_t *p = (class_info_t*) calloc(1, class->size);
assert(p);
p->vtable = class;
// other data members are implicitly zeroed by calloc() ...
...
}
Without using any typedefs or casting at all, memcpy()
can be used to accomplish the same thing, eg:
inline void *new (const void *_class, ...)
{
const class_t *class = _class;
void *p = calloc(1, class->size);
assert(p);
memcpy(p, &class, sizeof(class));
...
}