660 lines
28 KiB
Markdown
660 lines
28 KiB
Markdown
---
|
||
title: Michael Abrash's Graphics Programming Black Book, Special Edition
|
||
author: Michael Abrash
|
||
date: '1997-07-01'
|
||
identifier:
|
||
- scheme: ISBN
|
||
text: 1576101746
|
||
publisher: The Coriolis Group
|
||
category: 'Web and Software Development: Game Development,Web and Software Development:
|
||
Graphics and Multimedia Development'
|
||
chapter: '15'
|
||
pages: 279-293
|
||
---
|
||
|
||
## Chapter 15 -- Linked Lists and plain Unintended Challenges
|
||
|
||
### Unfamiliar Problems with Familiar Data Structures
|
||
|
||
After 21 years, this story still makes me wince. Oh, the humiliations I
|
||
suffer for your enlightenment....
|
||
|
||
It wasn't until ninth grade that I had my first real girlfriend. Okay,
|
||
maybe I was a little socially challenged as a kid, but hey, show me a
|
||
good programmer who wasn't; it goes with the territory. Her name was
|
||
Jeannie Schweigert, and she was about four feet tall, pretty enough, and
|
||
female—and willing to go out with me, which made her approximately as
|
||
attractive as Cheryl Tiegs, in my book.
|
||
|
||
Jeannie and I hung out together at school, and went to basketball games
|
||
and a few parties together, but somehow the two of us were never alone.
|
||
Being 14, neither of us could drive, so her parents tended to end up
|
||
chauffeuring us. That's a next-to-ideal arrangement, I now realize,
|
||
having a daughter of my own (ideal being exiling all males between the
|
||
ages of 12 and 18 to Tasmania), but at the time, it drove me nuts. You
|
||
see...ahem...I had never actually kissed Jeannie—or anyone, for that
|
||
matter, unless you count maiden aunts and the like—and I was dying to.
|
||
At the same time, I was terrified at the prospect. What if I turned out
|
||
to be no good at it? It wasn't as if I could go to Kisses ‘R' Us and
|
||
take lessons.
|
||
|
||
My long-awaited opportunity finally came after a basketball game. For a
|
||
change, *my* father was driving, and when we dropped her off at her
|
||
house, I walked her to the door. This was my big chance. I put my arms
|
||
around her, bent over with my eyes closed, just like in the movies....
|
||
|
||
And whacked her on the top of the head with my chin. (As I said, she was
|
||
only about four feet tall.) And I do mean whacked. Jeannie burst into
|
||
hysterical laughter, tried to calm herself down, said goodnight, and
|
||
went inside, still giggling. No kiss.
|
||
|
||
I was a pretty mature teenager, so this was only slightly more traumatic
|
||
than leading the Tournament of Roses parade in my underwear. On the next
|
||
try, though, I did manage to get the hang of this kissing business, and
|
||
eventually even went on to have a child. (Not with Jeannie, I might add;
|
||
the mind boggles at the mess I could have made of *that* with her.) As
|
||
it turns out, none of that stuff is particularly difficult; in fact,
|
||
it's kind of enjoyable, wink, wink, say no more.
|
||
|
||
When you're dealing with something new, a little knowledge goes a long
|
||
way. When it comes to kissing, we have to fumble along the learning
|
||
curve on our own, but there are all sorts of resources to help speed up
|
||
the learning process when it comes to programming. The basic mechanisms
|
||
of programming—searches, sorts, parsing, and the like—are
|
||
well-understood and superbly well-documented. Treat yourself to a book
|
||
like *Algorithms,* by Robert Sedgewick (Addison Wesley), or Knuth's *The
|
||
Art of Computer Programming* series (also from Addison Wesley; and where
|
||
was Knuth with *The Art of Kissing* when I needed him?), or practically
|
||
anything by Jon Bentley, and when you tackle a new area, give yourself a
|
||
head start. There's still plenty of room for inventiveness and
|
||
creativity on your part, but why not apply that energy on top of the
|
||
knowledge that's already been gained, instead of reinventing the wheel?
|
||
I know, reinventing the wheel is just the kind of challenge programmers
|
||
love—but can you really afford to waste the time? And do you honestly
|
||
think that you're so smart that you can out-think Knuth, who's spent a
|
||
lifetime at this stuff and happens to be a genius?
|
||
|
||
Maybe you can—but I sure can't. For example, consider the evolution of
|
||
my understanding of linked lists.
|
||
|
||
### Linked Lists
|
||
|
||
Linked lists are data structures composed of discrete elements, or
|
||
nodes, joined together with links. In C, the links are typically
|
||
pointers. Like all data structures, linked lists have their strengths
|
||
and their weaknesses. Primary among the strengths are: simplicity;
|
||
speedy sequential processing; ease and speed of insertion and deletion;
|
||
the ability to mix nodes of various sizes and types; and the ability to
|
||
handle variable amounts of data, especially when the total amount of
|
||
data changes dynamically or is not always known beforehand. Weaknesses
|
||
include: greater memory requirements than arrays (the pointers take up
|
||
space); slow non-sequential processing, including finding arbitrary
|
||
nodes; and an inability to backtrack, unless doubly-linked lists are
|
||
used. Unfortunately, doubly linked lists need more memory, as well as
|
||
processing time to maintain the backward links.
|
||
|
||
Linked lists aren't very good for most types of sorts. Insertion and
|
||
bubble sorts work fine, but more sophisticated sorts depend on efficient
|
||
random access, which linked lists don't provide. Likewise, you wouldn't
|
||
want to do a binary search on a linked list. On the other hand, linked
|
||
lists are ideal for applications where nothing more than sequential
|
||
access is needed to data that's always sorted or nearly sorted.
|
||
|
||
Consider a polygon fill function, for example. Polygon edges are added
|
||
to the active edge list in x-sorted order, and tend to stay pretty
|
||
nearly x-sorted, so sophisticated sorting is never needed. Edges are
|
||
read out of the list in sorted order, just the way linked lists work
|
||
best. Moreover, linked lists are straightforward to implement, and with
|
||
linked lists an arbitrary number of polygon edges can be handled with no
|
||
fuss. All in all, linked lists work beautifully for filling polygons.
|
||
For an example of the use of linked lists in polygon filling, see my
|
||
column in the May 1991 issue of *Dr. Dobb's Journal.* Be warned, though,
|
||
that none of the following optimizations are to be found in that column.
|
||
|
||
You see, that column was my first heavy-duty use of linked lists, and
|
||
they seemed so simple that I didn't even open Sedgewick or Knuth. For
|
||
hashing or Boyer-Moore searching, sure, I'd have done my homework first;
|
||
but linked lists seemed too obvious to bother. I was much more concerned
|
||
with the polygon-related aspects of the implementation, and, in truth, I
|
||
gave the linked list implementation not a moment's thought before I
|
||
began coding. Heck, I had handled *much* tougher programming problems in
|
||
the past; surely it would be faster to figure this one out on my own
|
||
than to look it up.
|
||
|
||
Not!
|
||
|
||
The basic concept of a linked list—the one I came up with for that *DDJ*
|
||
column—is straightforward, as shown in Figure 15.1. A head pointer
|
||
points to the first node in the list, which points to the next node,
|
||
which points to the next, and so on, until the last node in the list is
|
||
reached (typically denoted by a `NULL` next-node pointer).
|
||
Conceptually, nothing could be simpler. From an implementation
|
||
perspective, however, there are serious flaws with this model.
|
||
|
||
The fundamental problem is that the model of Figure 15.1 unnecessarily
|
||
complicates link manipulation. In order to delete a node, for example,
|
||
you must change the preceding node's `NextNode` pointer to point to
|
||
the following node, as shown in Listing 15.1. (Listing 15.2 is the
|
||
header file LLIST.H, which is `#include`d by all the linked list
|
||
listings in this chapter.) Easy enough—unless the preceding node happens
|
||
to be the head pointer, which doesn't *have* a `NextNode` field,
|
||
because it's not a node, so Listing 15.1 won't work. Cumbersome special
|
||
code and extra information (a pointer to the head of the list) are
|
||
required to handle the head-pointer case, as shown in Listing 15.3.
|
||
(I'll grant you that if you make the next-node pointer the first field
|
||
in the `LinkNode` structure, at offset 0, then you could successfully
|
||
point to the head pointer and pretend it was a `LinkNode`
|
||
structure—but that's an ugly and potentially dangerous trick, and we'll
|
||
see a better approach next.)
|
||
|
||

|
||
|
||
**LISTING 15.1 L15-1.C**
|
||
|
||
```c
|
||
/* Deletes the node in a linked list that follows the indicated node.
|
||
Assumes list is headed by a dummy node, so no special testing for
|
||
the head-of-list pointer is required. Returns the same pointer
|
||
that was passed in. */
|
||
|
||
#include "llist.h"
|
||
struct LinkNode *DeleteNodeAfter(struct LinkNode *NodeToDeleteAfter)
|
||
{
|
||
NodeToDeleteAfter->NextNode =
|
||
NodeToDeleteAfter->NextNode->NextNode;
|
||
return(NodeToDeleteAfter);
|
||
}
|
||
```
|
||
|
||
**LISTING 15.2 LLIST.H**
|
||
|
||
```c
|
||
/* Linked list header file. */
|
||
#define MAX_TEXT_LENGTH 100 /* longest allowed Text field */
|
||
#define SENTINEL 32767 /* largest possible Value field */
|
||
|
||
struct LinkNode {
|
||
struct LinkNode *NextNode;
|
||
int Value;
|
||
char Text[MAX_TEXT_LENGTH+1];
|
||
/* Any number of additional data fields may by present */
|
||
};
|
||
struct LinkNode *DeleteNodeAfter(struct LinkNode *);
|
||
struct LinkNode *FindNodeBeforeValue(struct LinkNode *, int);
|
||
struct LinkNode *InitLinkedList(void);
|
||
struct LinkNode *InsertNodeSorted(struct LinkNode *,
|
||
struct LinkNode *);
|
||
```
|
||
|
||
**LISTING 15.3 L15-3.C**
|
||
|
||
```c
|
||
/* Deletes the node in the specified linked list that follows the
|
||
indicated node. List is headed by a head-of-list pointer; if the
|
||
pointer to the node to delete after points to the head-of-list
|
||
pointer, special handling is performed. */
|
||
#include "llist.h"
|
||
struct LinkNode *DeleteNodeAfter(struct LinkNode **HeadOfListPtr,
|
||
struct LinkNode *NodeToDeleteAfter)
|
||
{
|
||
/* Handle specially if the node to delete after is actually the
|
||
head of the list (delete the first element in the list) */
|
||
if (NodeToDeleteAfter == (struct LinkNode *)HeadOfListPtr) {
|
||
*HeadOfListPtr = (*HeadOfListPtr)->NextNode;
|
||
} else {
|
||
NodeToDeleteAfter->NextNode =
|
||
NodeToDeleteAfter->NextNode->NextNode;
|
||
}
|
||
return(NodeToDeleteAfter);
|
||
}
|
||
```
|
||
|
||
However, it is true that if you're going to store a variety of types of
|
||
structures in your linked lists, you should start each node with the
|
||
`LinkNode` field. That way, the link pointer is in the same place in
|
||
*every* structure, and the same linked list code can handle all of the
|
||
structure types by casting them to the base link-node structure type.
|
||
This is a less than elegant approach, but it works. C++ can handle data
|
||
mixing more cleanly than C, via derivation from a base link-node class.
|
||
|
||
Note that Listings 15.1 and 15.3 have to specify the linked-list delete
|
||
operation as "delete the *next* node," rather than "delete this node,"
|
||
because in order to relink it's necessary to access the `NextNode`
|
||
field of the node preceding the node to be deleted, and it's impossible
|
||
to backtrack in a singly linked list. For this reason, singly-linked
|
||
list operations tend to work with the structure preceding the one of
|
||
interest—and that makes the problem of having to special-case the head
|
||
pointer all the more acute.
|
||
|
||
Similar problems with the head pointer crop up when you're inserting
|
||
nodes, and in fact in all link manipulation code. It's easy to end up
|
||
working with either pointers to pointers or lots of special-case code,
|
||
and while those approaches work, they're inelegant and inefficient.
|
||
|
||
### Dummies and Sentinels
|
||
|
||
A far better approach is to use a *dummy node* for the head of the list,
|
||
as shown in Figure 15.2. I invented this one for myself the next time I
|
||
encountered linked lists, while designing a seed fill function for
|
||
MetaWindows, back during my tenure at Metagraphics Corp. But I could
|
||
have learned it by spending five minutes with Sedgewick's book.
|
||
|
||

|
||
|
||
> 
|
||
> The next-node pointer of the head node, which points to the first real
|
||
> node, is the only part of the head node that's actually used. This way
|
||
> the same code works on the head node as on the rest of the list, so
|
||
> there are no special cases.
|
||
|
||
Likewise, there should be a separate node for the tail of the list, so
|
||
that every node that contains real data is guaranteed to have a node on
|
||
either side of it. In this scheme, an empty list contains two nodes, as
|
||
shown in Figure 15.3. Although it is not necessary, the tail node may
|
||
point to itself as its own next node, rather than contain a `NULL`
|
||
pointer. This way, a deletion operation on an empty list will have no
|
||
effect—quite unlike the same operation performed on a list terminated
|
||
with a `NULL` pointer. The tail node of a list terminated like this
|
||
can be detected because it will be the only node for which the next-node
|
||
pointer equals the current-node pointer.
|
||
|
||
Figure 15.3 is a giant step in the right direction, but we can still
|
||
make a few refinements. The inner loop of any code that scans through
|
||
such a list has to perform a special test on each node to determine
|
||
whether the tail has been reached. So, for example, code to find the
|
||
first node containing a value field greater than or equal to a certain
|
||
value has to perform two tests in the inner loop, as shown in Listing
|
||
15.4.
|
||
|
||
**LISTING 15.4 L15-4.C**
|
||
|
||
```c
|
||
/* Finds the first node in a linked list with a value field greater
|
||
than or equal to a key value, and returns a pointer to the node
|
||
preceding that node (to facilitate insertion and deletion), or a
|
||
NULL pointer if no such value was found. Assumes the list is
|
||
terminated with a tail node pointing to itself as the next node. */
|
||
#include <stdio.h>
|
||
#include "llist.h"
|
||
struct LinkNode *FindNodeBeforeValueNotLess(
|
||
struct LinkNode *HeadOfListNode, int SearchValue)
|
||
{
|
||
struct LinkNode *NodePtr = HeadOfListNode;
|
||
|
||
while ( (NodePtr->NextNode->NextNode != NodePtr->NextNode) &&
|
||
(NodePtr->NextNode->Value < SearchValue) )
|
||
NodePtr = NodePtr->NextNode;
|
||
|
||
if (NodePtr->NextNode->NextNode == NodePtr->NextNode)
|
||
return(NULL); /* we found the sentinel; failed search */
|
||
else
|
||
return(NodePtr); /* success; return pointer to node preceding
|
||
node that was >= */
|
||
}
|
||
```
|
||
|
||
Suppose, however, that we make the tail node a *sentinel* by giving it a
|
||
value that is guaranteed to terminate the search, as shown in Figure
|
||
15.4. The list in Figure 15.4 has a sentinel with a value field of
|
||
32,767; since we're working with integers, that's the highest possible
|
||
search value, and is guaranteed to satisfy any search that comes down
|
||
the pike. The success or failure of the search can then be determined
|
||
outside the loop, if necessary, by checking for the tail node's special
|
||
pointer—but the inside of the loop is streamlined to just one test, as
|
||
shown in Listing 15.5. Not all linked lists lend themselves to
|
||
sentinels, but the performance benefits are considerable for those lend
|
||
themselves to sentinels, but the performance benefits are considerable
|
||
for those that do.
|
||
|
||

|
||
|
||
**LISTING 15.5 L15-5.C**
|
||
|
||
```c
|
||
/* Finds the first node in a value-sorted linked list that
|
||
has a Value field greater than or equal to a key value, and
|
||
returns a pointer to the node preceding that node (to facilitate
|
||
insertion and deletion), or a NULL pointer if no such value was
|
||
found. Assumes the list is terminated with a sentinel tail node
|
||
containing the largest possible Value field setting and pointing
|
||
to itself as the next node. */
|
||
#include <stdio.h>
|
||
#include "llist.h"
|
||
struct LinkNode *FindNodeBeforeValueNotLess(
|
||
struct LinkNode *HeadOfListNode, int SearchValue)
|
||
{
|
||
struct LinkNode *NodePtr = HeadOfListNode;
|
||
while (NodePtr->NextNode->Value < SearchValue)
|
||
NodePtr = NodePtr->NextNode;
|
||
if (NodePtr->NextNode->NextNode == NodePtr->NextNode)
|
||
return(NULL); /* we found the sentinel; failed search */
|
||
else
|
||
return(NodePtr); /* success; return pointer to node preceding
|
||
node that was >= */
|
||
}
|
||
```
|
||
|
||

|
||
|
||
### Circular Lists
|
||
|
||
One minor but elegant refinement yet remains: Use a single node as both
|
||
the head *and* the tail of the list. We can do this by connecting the
|
||
last node back to the first through the head/tail node in a circular
|
||
fashion, as shown in Figure 15.5. This head/tail node can also, of
|
||
course, be a sentinel; when it's necessary to check for the end of the
|
||
list explicitly, that can be done by comparing the current node pointer
|
||
to the head pointer. If they're equal, you're at the head/tail node.
|
||
|
||
Why am I so fond of this circular list architecture? For one thing, it
|
||
saves a node, and most of my linked list programming has been done in
|
||
severely memory-constrained environments. Mostly, though, it's just so
|
||
*neat;* with this setup, there's not a single node or inner-loop
|
||
instruction wasted. Perfect economy of programming, if you ask me.
|
||
|
||
I must admit that I racked my brains for quite a while to come up with
|
||
the circular list, simple as it may seem. Shortly after coming up with
|
||
it, I happened to look in Sedgewick's book, only to find my nifty
|
||
optimization described plain as day; and a little while after *that,* I
|
||
came across a thread in the algorithms/computer.sci topic on BIX that
|
||
described it in considerable detail. Folks, the information is out
|
||
there. Look it up *before* turning on your optimizer afterburners!
|
||
|
||
Listings 15.1 and 15.6 together form a suite of C functions for
|
||
maintaining a circular linked list sorted by ascending value. (Listing
|
||
15.5 requires modification before it will work with circular lists.)
|
||
Listing 15.7 is an assembly language version of `InsertNodeSorted()`;
|
||
note the tremendous efficiency of the scanning loop in
|
||
`InsertNodeSorted()`-four instructions per node!—thanks to the dummy
|
||
head/tail/sentinel node. Listing 15.8 is a simple application that
|
||
illustrates the use of the linked-list functions in Listings 15.1 and
|
||
15.6.
|
||
|
||
Contrast Figure 15.5 with Figure 15.1, and Listings 15.1, 15.5, 15.6,
|
||
and 15.7 with Listings 15.3 and 15.4. Yes, linked lists are simple, but
|
||
not so simple that a little knowledge doesn't make a substantial
|
||
difference. Make it a habit to read Knuth or Sedgewick or the like
|
||
before you write a single line of code.
|
||
|
||

|
||
|
||
**LISTING 15.6 L15-6.C**
|
||
|
||
```c
|
||
/* Suite of functions for maintaining a linked list sorted by
|
||
ascending order of the Value field. The list is circular; that
|
||
is,it has a dummy node as both the head and the tail of the list.
|
||
The dummy node is a sentinel, containing the largest possible
|
||
Value field setting. Tested with Borland C++ in C mode. */
|
||
#include <stdlib.h>
|
||
#include <stdio.h>
|
||
#include <string.h>
|
||
#include "llist.h"
|
||
/* Initializes an empty linked list of LinkNode structures,
|
||
consisting of a single head/tail/sentinel node, and returns a
|
||
pointer to the list. Returns NULL for failure. */
|
||
struct LinkNode *InitLinkedList()
|
||
{
|
||
struct LinkNode *Sentinel;
|
||
|
||
if ((Sentinel = malloc(sizeof(struct LinkNode))) == NULL)
|
||
return(NULL);
|
||
Sentinel->NextNode = Sentinel;
|
||
Sentinel->Value = SENTINEL;
|
||
strcpy(Sentinel->Text, "*** sentinel ***");
|
||
return(Sentinel);
|
||
}
|
||
|
||
/* Finds the first node in a value-sorted linked list with a value
|
||
field equal to a key value, and returns a pointer to the node
|
||
preceding that node (to facilitate insertion and deletion), or a
|
||
NULL pointer if no value was found. Assumes list is terminated
|
||
with a sentinel node containing the largest possible value. */
|
||
|
||
struct LinkNode *FindNodeBeforeValue(struct LinkNode *HeadOfListNode,
|
||
int SearchValue)
|
||
{
|
||
struct LinkNode *NodePtr = HeadOfListNode;
|
||
|
||
while (NodePtr->NextNode->Value < SearchValue)
|
||
NodePtr = NodePtr->NextNode;
|
||
if (NodePtr->NextNode->Value == SearchValue) {
|
||
/* Found the search value; success unless we found the
|
||
sentinel (can happen only if SearchValue == SENTINEL) */
|
||
if (NodePtr->NextNode == HeadOfListNode) {
|
||
return(NULL); /* failure; we found the sentinel */
|
||
} else {
|
||
return(NodePtr); /* success; return pointer to node
|
||
preceding the node that was equal */
|
||
}
|
||
} else {
|
||
return(NULL); /* No match; return failure status */
|
||
}
|
||
}
|
||
|
||
/* Inserts the specified node into a value-sorted linked list, such
|
||
that value-sorting is maintained. Returns a pointer to the node
|
||
after which the new node is inserted. */
|
||
struct LinkNode *InsertNodeSorted(struct LinkNode *HeadOfListNode,
|
||
struct LinkNode *NodeToInsert)
|
||
{
|
||
struct LinkNode *NodePtr = HeadOfListNode;
|
||
int SearchValue = NodeToInsert->Value;
|
||
while (NodePtr->NextNode->Value < SearchValue)
|
||
NodePtr = NodePtr->NextNode;
|
||
NodeToInsert->NextNode = NodePtr->NextNode;
|
||
NodePtr->NextNode = NodeToInsert;
|
||
return(NodePtr);
|
||
}
|
||
```
|
||
|
||
**LISTING 15.7 L15-7.ASM**
|
||
|
||
```nasm
|
||
; C near-callable assembly function for inserting a new node in a
|
||
; linked list sorted by ascending order of the Value field. The list
|
||
; is circular; that is, it has a dummy node as both the head and the
|
||
; tail of the list. The dummy node is a sentinel, containing the
|
||
; largest possible Value field setting. Tested with TASM.
|
||
MAX_TEXT_LENGTH equ 100 ;longest allowed Text field
|
||
SENTINEL equ 32767 ;largest possible Value field
|
||
LinkNode struc
|
||
NextNode dw ?
|
||
Value dw ?
|
||
Text db MAX_TEXT_LENGTH+1 dup(?)
|
||
;*** Any number of additional data fields may by present ***
|
||
LinkNode ends
|
||
|
||
.model small
|
||
.code
|
||
|
||
; Inserts the specified node into a ascending-value-sorted linked
|
||
; list, such that value-sorting is maintained. Returns a pointer to
|
||
; the node after which the new node is inserted.
|
||
; C near-callable as:
|
||
; struct LinkNode *InsertNodeSorted(struct LinkNode *HeadOfListNode,
|
||
; struct LinkNode *NodeToInsert)
|
||
parms struc
|
||
dw 2 dup (?) ;pushed return address & BP
|
||
HeadOfListNode dw ? ;pointer to head node of list
|
||
NodeToInsert dw ? ;pointer to node to insert
|
||
parms ends
|
||
|
||
public _InsertNodeSorted
|
||
_InsertNodeSorted proc near
|
||
push bp
|
||
mov bp,sp ;point to stack frame
|
||
push si ;preserve register vars
|
||
push di
|
||
mov si,[bp].NodeToInsert ;point to node to insert
|
||
mov ax,[si].Value ;search value
|
||
mov di,[bp].HeadOfListNode ;point to linked list in
|
||
; which to insert
|
||
SearchLoop:
|
||
mov bx,di ;advance to the next node
|
||
mov di,[bx].NextNode ;point to following node
|
||
cmp [di].Value,ax ;is the following node's
|
||
; value less than the value
|
||
; from the node to insert?
|
||
jl SearchLoop ;yes, so continue searching
|
||
;no, so we have found our
|
||
; insert point
|
||
mov ax,[bx].NextNode ;link the new node between
|
||
mov [si].NextNode,ax ; the current node and the
|
||
mov [bx].NextNode,si ; following node
|
||
mov ax,bx ;return pointer to node
|
||
; after which we inserted
|
||
pop di ;restore register vars
|
||
pop si
|
||
pop bp
|
||
ret
|
||
_InsertNodeSorted endp
|
||
end
|
||
```
|
||
|
||
**LISTING 15.8 L15-8.C**
|
||
|
||
```c
|
||
/* Sample linked list program. Tested with Borland C++. */
|
||
#include <stdlib.h>
|
||
#include <stdio.h>
|
||
#include <conio.h>
|
||
#include <ctype.h>
|
||
#include <string.h>
|
||
#include "llist.h"
|
||
|
||
void main()
|
||
{ int Done = 0, Char, TempValue;
|
||
struct LinkNode *TempPtr, *ListPtr, *TempPtr2;
|
||
char TempBuffer[MAX_TEXT_LENGTH+3];
|
||
|
||
if ((ListPtr = InitLinkedList()) == NULL) {
|
||
printf("Out of memory\n");
|
||
exit(1);
|
||
}
|
||
while (!Done) {
|
||
printf("\nA=add; D=delete; F=find; L=list all; Q=quit\n>");
|
||
Char = toupper(getche());
|
||
printf("\n");
|
||
switch (Char) {
|
||
case 'A': /* add a node */
|
||
if ((TempPtr = malloc(sizeof(struct LinkNode))) == NULL)
|
||
{
|
||
printf("Out of memory\n );
|
||
exit(1);
|
||
}
|
||
printf("Node value: ");
|
||
scanf("%d", &TempPtr->Value);
|
||
if ((FindNodeBeforeValue(ListPtr,TempPtr->Value))!=NULL)
|
||
{ printf("*** value already in list; try again ***\n");
|
||
free(TempPtr);
|
||
} else {printf("Node text: ");
|
||
TempBuffer[0] = MAX_TEXT_LENGTH;
|
||
cgets(TempBuffer);
|
||
strcpy(TempPtr->Text, &TempBuffer[2]);
|
||
InsertNodeSorted(ListPtr, TempPtr);
|
||
printf("\n");
|
||
}
|
||
break;
|
||
case 'D': /* delete a node */
|
||
printf("Value field of node to delete: ");
|
||
scanf("%d", &TempValue);
|
||
if ((TempPtr = FindNodeBeforeValue(ListPtr, TempValue))
|
||
!= NULL) {
|
||
TempPtr2 = TempPtr->NextNode; /* -> node to delete */
|
||
DeleteNodeAfter(TempPtr); /* delete it */
|
||
free(TempPtr2); /* free its memory */
|
||
} else {
|
||
printf("*** no such value field in list ***\n")
|
||
break;
|
||
case 'F': /* find a node */
|
||
printf("Value field of node to find: ");
|
||
scanf("%d", &TempValue);
|
||
if ((TempPtr = FindNodeBeforeValue(ListPtr, TempValue))
|
||
!= NULL)
|
||
printf("Value: %d\nText: %s\n",
|
||
TempPtr->NextNode->Value, TempPtr->NextNode->Text);
|
||
else
|
||
printf("*** no such value field in list ***\n");
|
||
break;
|
||
case 'L': /* list all nodes */
|
||
TempPtr = ListPtr->NextNode; /* point to first node */
|
||
if (TempPtr == ListPtr) { /* empty if at sentinel */
|
||
printf("*** List is empty ***\n");
|
||
} else {
|
||
do {printf("Value: %d\n Text: %s\n", TempPtr->Value,
|
||
TempPtr->Text);
|
||
TempPtr = TempPtr->NextNode;
|
||
} while (TempPtr != ListPtr);
|
||
}
|
||
break;
|
||
case 'Q':
|
||
Done = 1;
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### Hi/Lo in 24 Bytes
|
||
|
||
In one of my *PC TECHNIQUES* "Pushing the Envelope" columns, I passed
|
||
along one of David Stafford's fiendish programming puzzles: Write a
|
||
C-callable function to find the greatest or smallest unsigned `int`.
|
||
Not a big deal—except that David had *already* done it in 24 bytes, so
|
||
the challenge was to do it in 24 bytes or less.
|
||
|
||
Such routines soon began coming at me from all angles. However (and I
|
||
hate to say this because some of my correspondents were *very* pleased
|
||
with the thought that they had bested David), no one has yet met the
|
||
challenge—because most of you folks missed a key point. When David said,
|
||
"Write a function to find the greatest or smallest unsigned `int` in
|
||
24 bytes or less," he meant, "Write the `hi` and the `lo` functions
|
||
in 24 bytes or less—*combined*."
|
||
|
||
Oh.
|
||
|
||
Yes, a 24-byte hi/lo function is possible, anatomically improbable as it
|
||
might seem. Which I guess goes to show that when one of David's puzzles
|
||
seems less than impossible, odds are you're missing something. Listing
|
||
15.9 is David's 24-byte solution, from which a lot may be learned if one
|
||
reads closely enough.
|
||
|
||
**LISTING 15.9 L15-9.ASM**
|
||
|
||
```nasm
|
||
; Find the greatest or smallest unsigned int.
|
||
; C callable (small model); 24 bytes.
|
||
; By David Stafford.
|
||
; unsigned hi( int num, unsigned a[] );
|
||
; unsigned lo( int num, unsigned a[] );
|
||
|
||
public _hi, _lo
|
||
|
||
_hi: db 0b9h ;mov cx,immediate
|
||
_lo: xor cx,cx
|
||
pop ax ;get return address
|
||
pop dx ;get count
|
||
pop bx ;get pointer
|
||
push bx ;restore pointer
|
||
push dx ;restore count
|
||
push ax ;restore return address
|
||
save: mov ax,[bx]
|
||
top: cmp ax,[bx]
|
||
jcxz around
|
||
cmc
|
||
around: ja save
|
||
inc bx
|
||
inc bx
|
||
dec dx
|
||
jnz top
|
||
|
||
ret
|
||
```
|
||
|
||
Before I end this chapter, let me say that I get a lot of feedback from
|
||
my readers, and it's much appreciated. Keep those cards, letters, and
|
||
email messages coming. And if any of you know Jeannie Schweigert, have
|
||
her drop me a line and let me know how she's doing these days....
|