# A Memory-Efficient Doubly Linked List

In the quest to make small devices cost effective, manufacturers often need to think about reducing the memory size. One option is to find alternative implementations of the abstract data types (ADTs) we are used to for our day-to-day implementations. One such ADT is a doubly linked list structure.

In this article, I present a conventional implementation and an alternative implementation of the doubly linked list ADT, with insertion, traversal and deletion operations. I also provide the time and memory measurements of each to compare the pros and cons. The alternative implementation is based on pointer distance, so I call it the pointer distance implementation for this discussion. Each node would carry only one pointer field to traverse the list back and forth. In a conventional implementation, we need to keep a forward pointer to the next item on the list and a backward pointer to the previous item. The overhead is 66% for a conventional node and 50% for the pointer distance implementation. If we use multidimensional doubly linked lists, such as a dynamic grid, the savings would be even greater.

A detailed discussion of the conventional implementation of doubly linked lists is not offered here, because they are discussed in almost every data structure and algorithm book. The conventional and the distance pointer implementations are even used in the same fashion to have comparable memory and time usage statistics.

We define a node of pointer distance implementation like this:

typedef int T; typedef struct listNode{ T elm; struct listNode * ptrdiff; };

The ptrdiff pointer field holds the difference between the pointer to the next node and the pointer to the previous node. Pointer difference is captured by using exclusive OR. Any instance of such a list has a StartNode and an EndNode. StartNode points to the head of the list, and EndNode points to the tail of the list. By definition, the previous node of the StartNode is a NULL node; the next node of the EndNode also is a NULL node. For a singleton list, both the previous node and next node are NULL nodes, so the ptrdiff field holds the NULL pointer. In a two-node list, the previous node to the StartNode is NULL and the next node is the EndNode. The ptrdiff of the StartNode is the exclusive OR of EndNode and NULL node: EndNode. And, the ptrdiff of the EndNode is StartNode.

The insertion and deletion of a specific node depends on traversal. We need only one simple routine to traverse back and forth. If we provide the StartNode as an argument and because the previous node is NULL, our direction of traversal implicitly is defined to be left to right. On the other hand, if we provide the EndNode as an argument, the implicitly defined direction of traversal is right to left. The present implementation does not support traversal from the middle of the list, but it should be an easy enhancement. The NextNode is defined as follows:

typedef listNode * plistNode; plistNode NextNode( plistNode pNode, plistNode pPrevNode){ return ((plistNode) ((int) pNode->ptrdiff ^ ( int)pPrevNode) ); }

Given an element, we keep the pointer difference of the element by exclusive ORing of the next node and previous node. Therefore, if we perform another exclusive OR with the previous node, we get the pointer to the next node.

Given a new node and the element of an existing node, we would like to insert the new node after the first node in the direction of traversal that has the given element (Listing 1). Inserting a node in an existing doubly linked list requires pointer fixing of three nodes: the current node, the next node of the current node and the new node. When we provide the element of the last node as an argument, this insertion degenerates into insertion at the end of the list. We build the list this way to obtain our timing statistics. If the InsertAfter() routine does not find the given element, it would not insert the new element.

**Listing 1. Function to Insert a New Node
**

void insertAfter(plistNode pNew, T theElm) { plistNode pPrev, pCurrent, pNext; pPrev = NULL; pCurrent = pStart; while (pCurrent) { pNext = NextNode(pCurrent, pPrev); if (pCurrent->elm == theElm) { /* traversal is done */ if (pNext) { /* fix the existing next node */ pNext->ptrdiff = (plistNode) ((int) pNext->ptrdiff ^ (int) pCurrent ^ (int) pNew); /* fix the current node */ pCurrent->ptrdiff = (plistNode) ((int) pNew ^ (int) pNext ^ (int) pCurrent->ptrdiff); /* fix the new node */ pNew->ptrdiff = (plistNode) ((int) pCurrent ^ (int) pNext); break; } pPrev = pCurrent; pCurrent = pNext; } }

First, we traverse the list up to the node containing the given element by using the NextNode() routine. If we find it, we then place the node after this found node. Because the next node has pointer difference, we dissolve it by exclusive ORing with the found node. Next, we do exclusive ORing with the new node, as the new node would be its previous node. Fixing the current node by following the same logic, we first dissolve the pointer difference by exclusive ORing with the next current node. We then do another exclusive ORing with the new node, which provides us with the correct pointer difference. Finally, since the new node would sit between the found current node and the next node, we get the pointer difference of it by exclusively ORing them.

## Trending Topics

## Webinar

### Fast/Flexible Linux OS Recovery

On Demand Now

In this live one-hour webinar, learn how to enhance your existing backup strategies for complete disaster recovery preparedness using Storix System Backup Administrator (SBAdmin), a highly flexible full-system recovery solution for UNIX and Linux systems.

Join *Linux Journal*'s Shawn Powers and David Huffman, President/CEO, Storix, Inc.

Free to *Linux Journal* readers.

Astronomy for KDE | Jun 23, 2016 |

Profiles and RC Files | Jun 22, 2016 |

Understanding Ceph and Its Place in the Market | Jun 21, 2016 |

SoftMaker FreeOffice | Jun 20, 2016 |

Git 2.9 Released | Jun 17, 2016 |

The Giant Zero, Part 0.x | Jun 16, 2016 |

- Download "Linux Management with Red Hat Satellite: Measuring Business Impact and ROI"
- Profiles and RC Files
- Astronomy for KDE
- Understanding Ceph and Its Place in the Market
- Maru OS Brings Debian to Your Phone
- OpenSwitch Finds a New Home
- Git 2.9 Released
- SoftMaker FreeOffice
- What's Our Next Fight?
- The Giant Zero, Part 0.x

## Geek Guides

With all the industry talk about the benefits of Linux on Power and all the performance advantages offered by its open architecture, you may be considering a move in that direction. If you are thinking about analytics, big data and cloud computing, you would be right to evaluate Power. The idea of using commodity x86 hardware and replacing it every three years is an outdated cost model. It doesn’t consider the total cost of ownership, and it doesn’t consider the advantage of real processing power, high-availability and multithreading like a demon.

This ebook takes a look at some of the practical applications of the Linux on Power platform and ways you might bring all the performance power of this open architecture to bear for your organization. There are no smoke and mirrors here—just hard, cold, empirical evidence provided by independent sources. I also consider some innovative ways Linux on Power will be used in the future.

Get the Guide
## Comments

## Worst. Linked List. Ever.

Worst. Linked List. Ever.

## This is an interesting

This is an interesting article. A criteria of a linked list is that any node by be unlinked from the list in O(1). Exclusive-or method requires history to move ahead similar to how parity is calculated. Thus, we must call this a memory efficient queue instead. The memory efficient queue is a nice technique to use in embedded systems.