构造函数和析构函数

我们可以重写 lval 的构造函数,使其返回 lval 的指针,而不是其本身。这样做会使得对 lval 变量进行跟踪更加简单。为此,我们需要用到 malloc 库函数以及 sizeof 操作符为 lval 结构体在堆上申请足够大的内存区域,然后使用 -> 操作符填充结构体中的相关字段。

当我们构造一个 lval 时,它的某些指针字段可能会包含其他的在堆上申请的内存,所以我们应该小心行事。当某个 lval 完成使命之后,我们不仅需要删除它本身所指向的堆内存,还要删除它的字段所指向的堆内存。

  1. /* Construct a pointer to a new Number lval */
  2. lval* lval_num(long x) {
  3. lval* v = malloc(sizeof(lval));
  4. v->type = LVAL_NUM;
  5. v->num = x;
  6. return v;
  7. }
  1. /* Construct a pointer to a new Error lval */
  2. lval* lval_err(char* m) {
  3. lval* v = malloc(sizeof(lval));
  4. v->type = LVAL_ERR;
  5. v->err = malloc(strlen(m) + 1);
  6. strcpy(v->err, m);
  7. return v;
  8. }
  1. /* Construct a pointer to a new Symbol lval */
  2. lval* lval_sym(char* s) {
  3. lval* v = malloc(sizeof(lval));
  4. v->type = LVAL_SYM;
  5. v->sym = malloc(strlen(s) + 1);
  6. strcpy(v->sym, s);
  7. return v;
  8. }
  1. /* A pointer to a new empty Sexpr lval */
  2. lval* lval_sexpr(void) {
  3. lval* v = malloc(sizeof(lval));
  4. v->type = LVAL_SEXPR;
  5. v->count = 0;
  6. v->cell = NULL;
  7. return v;
  8. }

NULL 是什么?

NULL 是一个指向内存地址 0 的特殊常量。按照惯例,它通常被用来表示空值或无数据。在上面的代码中,我们使用 NULL 来表示虽然我们有一个数据指针,但它目前还没有指向任何内容。在本书的后续章节中你讲经常性地遇到这个特殊的常量,所以,请眼熟它。


为什么要使用 strlen(s) + 1

在 C 语言中,字符串是以空字符做为终止标记的。所以,C 语言字符串的最后一个字符一定是 \0。请确保所有的字符串都是按照这个约定来存储的,不然程序就会因为莫名其妙的错误退出。strlen 函数返回的是字符串的实际长度(所以不包括结尾的 \0 终止符)。所以为了保证有足够的空间存储所有字符,我们需要在额外 +1。

现在,我们需要一个定制的函数来删除 lval*。这个函数应该调用 free 函数来释放本身所指向的由 malloc 函数所申请的内存。但更重要的是,它应该根据自身的类型,释放所有它的字段指向的内存。所以我们只需按照上方介绍的那些构造函数对应释放相应的字段,就不会有内存的泄露了。

  1. void lval_del(lval* v) {
  2. switch (v->type) {
  3. /* Do nothing special for number type */
  4. case LVAL_NUM: break;
  5. /* For Err or Sym free the string data */
  6. case LVAL_ERR: free(v->err); break;
  7. case LVAL_SYM: free(v->sym); break;
  8. /* If Sexpr then delete all elements inside */
  9. case LVAL_SEXPR:
  10. for (int i = 0; i < v->count; i++) {
  11. lval_del(v->cell[i]);
  12. }
  13. /* Also free the memory allocated to contain the pointers */
  14. free(v->cell);
  15. break;
  16. }
  17. /* Free the memory allocated for the "lval" struct itself */
  18. free(v);
  19. }