48. Listen: Implementation II

Einen neuen Node erzeugen:

static Node *getNodeFromContent(char *item)
{
  char *buf = strdup(item);
  Node *newNode;

  if (!buf) {
    return NULL;
  }
  if (!(newNode = malloc(sizeof(Node)))) {
    free(buf); /* cave memory leak! */
    return NULL;
  }
  newNode->next = NULL;
  newNode->cont = buf;
  return newNode;
}

Der von der Benutzerin übergebene String wird in einen eigenen Speicher kopiert, weil man nicht weiß, wie lange der Speicher, auf den item zeigt, uns gehört (oder ob das aufrufende Programm daran rumpfuscht).

getNodeFromContent ist static: Der Name ist von außen nicht sichtbar.

Das ist eine Design-Entscheidung. Wir wollen grundsätzlich nicht, dass die Benutzerin weiß, wie wir unsere Liste implementieren, dass etwa jeder gespeicherte String einen eigenen Knoten hat.

Wir hätten uns hier auch anders entscheiden können: Wenn Benutzer Knoten haben können, sind beispielsweise effiziente insert_after-Funktionen möglich, die den Vorgängerknoten als Argument nehmen.

Abwägungen dieser Art sind im Software Engineering nicht selten. Man möchte zwar fast immer so weit wie möglich von der Implementation abstrahieren (da sich diese schnell ändern kann) – man bezahlt dafür aber mit Schnittstellen, die keine Rücksicht auf die Bedürfnisse und Beschränkungen der konkreten Implementation nehmen können. Die Abwägung der Ansprüche Abstraktheit und Effizienz gehört mit zu den Entscheidungen, die das Design eines Programms ausmachen.

Im Namen getNodeFromContent fehlt der Präfix list_. Das können wir uns hier leisten, weil die Funktion static ist und wir somit nur unseren eigenen “Namensraum verschmutzen” – den des nach außen opaken Moduls eben.

Freigeben eines Nodes:

static void freeNode(Node *n)
{
  free(n->cont);
  free(n);
}

Diese Funktion ist gefährlich, weil sie davon ausgeht, dass der Node schon aus der Liste ausgehängt ist und das nicht prüft – wird aber ein Node, der noch in der Liste ist, freigegeben, sind wüste und praktisch nicht zu findende Fehler quasi sicher. In einer einfach verketteten Liste ist eine Prüfung, ob der Node ausgehängt ist oder nicht aber relativ aufwändig (man muss die Liste komplett nach dem Node durchsuchen), und so verlassen wir uns darauf, dass die Funktion richtig verwendet wird – da sie static ist, kann das ohnehin nur aus unserem Modul heraus passieren.

Anhängen eines Eintrags:

int list_append(List *l, char *item)
{
  Node *newNode;

  if (!(newNode = getNodeFromContent(item))) {
    return -1;
  }
  if (!l->last) { /* Liste leer */
    l->last = l->first = newNode;
  } else {
    l->last->next = newNode;
    l->last = newNode;
  }
  return 0;
}

Hier wird eine negative Zahl zurückgegeben, wenn dabei was schief geht und 0 sonst. Auch das ist Konvention aus der C-Bibliothek.


Markus Demleitner

Copyright Notice