Constructors & Destructors
We can change our lval
construction functions to return pointers to an lval
, rather than one directly. This will make keeping track of lval
variables easier. For this we need to use malloc
with the sizeof
function to allocate enough space for the lval
struct, and then to fill in the fields with the relevant information using the arrow operator ->
.
When we construct an lval
its fields may contain pointers to other things that have been allocated on the heap. This means we need to be careful. Whenever we are finished with an lval
we also need to delete the things it points to on the heap. We will have to make a rule for ourselves. Whenever we free the memory allocated for an lval
, we also free all the things it points to.
/* Construct a pointer to a new Number lval */
lval* lval_num(long x) {
lval* v = malloc(sizeof(lval));
v->type = LVAL_NUM;
v->num = x;
return v;
}
/* Construct a pointer to a new Error lval */
lval* lval_err(char* m) {
lval* v = malloc(sizeof(lval));
v->type = LVAL_ERR;
v->err = malloc(strlen(m) + 1);
strcpy(v->err, m);
return v;
}
/* Construct a pointer to a new Symbol lval */
lval* lval_sym(char* s) {
lval* v = malloc(sizeof(lval));
v->type = LVAL_SYM;
v->sym = malloc(strlen(s) + 1);
strcpy(v->sym, s);
return v;
}
/* A pointer to a new empty Sexpr lval */
lval* lval_sexpr(void) {
lval* v = malloc(sizeof(lval));
v->type = LVAL_SEXPR;
v->count = 0;
v->cell = NULL;
return v;
}
What is NULL
?
NULL
is a special constant that points to memory location 0
. In many places it is used as a convention to signify some non-value or empty data. Above we use it to specify that we have a data pointer, but that it doesn’t point to any number of data items. You will see NULL
crop up a lot more later on so always remember to look out for it.
Why are you using strlen(s) + 1
?
In C strings are null terminated. This means that the final character of them is always the zero character \0
. This is a convention in C to signal the end of a string. It is important that all strings are stored this way otherwise programs will break in nasty ways.
The strlen
function only returns the number of bytes in a string excluding the null terminator. This is why we need to add one, to ensure there is enough allocated space for it all!
We now need a special function to delete lval*
. This should call free
on the pointer itself to release the memory acquired from malloc
, but more importantly it should inspect the type of the lval
, and release any memory pointed to by its fields. If we match every call to one of the above construction functions with lval_del
we can ensure we will get no memory leaks.
void lval_del(lval* v) {
switch (v->type) {
/* Do nothing special for number type */
case LVAL_NUM: break;
/* For Err or Sym free the string data */
case LVAL_ERR: free(v->err); break;
case LVAL_SYM: free(v->sym); break;
/* If Sexpr then delete all elements inside */
case LVAL_SEXPR:
for (int i = 0; i < v->count; i++) {
lval_del(v->cell[i]);
}
/* Also free the memory allocated to contain the pointers */
free(v->cell);
break;
}
/* Free the memory allocated for the "lval" struct itself */
free(v);
}