While there is nothing wrong with keeping an array (or for that matter a pointer) that points to the head and tail of your list, if you use an array, after assigning the address keep your array references out of your list operations. Mixing &array[x] in with your list operations does nothing but cause confusion. When working with the list, treat it as a list and forget about the array.
Your primary problem is you iterate one node to far looking for where to insert the new_node resulting in you iterating to tail before you stop. Stop your iteration on the node before you insert new_node. You do this by testing for:
/* test curr->qnext->data > key to stop before tail */
while (curr->qnext && curr->qnext->data > key)
curr = curr->qnext;
(note: masking levels of indirection with a variable like you do next with prev = curr->qprev; just hides details -- which can add to confusion later on. It's perfectly legal, but use with discretion...)
Now you can concentrate on inserting new_node between &head and &tail where it needs to go.
In any list insertion, you are simply re-wiring the pointer->next of the current node to point to the new_node and the pointer->prev of the next node to point to new_node. To finish the insertion your new_node->qprev points to curr and new_node->qnext points to curr->next, e.g.
new_node->qprev = curr; /* rewire pointers */
new_node->qnext = curr->qnext;
curr->qnext->qprev = new_node;
curr->qnext = new_node;
(note: the easy way to figure it out is to pull at a piece of paper and a No. 2 pencil and draw a block for curr a block for new_node and a block for tail and then draw lines for prev/next pointers (for both the list without the new_node and with it). Then, with the logic straight, sit down to the keyboard and pecking it out.)
Further, you must always validate your allocations, e.g.
/* allocate and VALIDATE! */
if (!(new_node = malloc (sizeof *new_node))) {
perror ("malloc - new_node");
exit (EXIT_FAILURE);
}
In any code you write that dynamically allocates memory, you have 2 responsibilities regarding any block of memory allocated: (1) always preserve a pointer to the starting address for the block of memory so, (2) it can be freed when it is no longer needed. So if you allocate it, keep track of a pointer to the block and free when you are done with it. For example, when done outputting the list values (or in a dedicated loop), you can free the memory you allocate similar to:
curr = &head; /* output list */
while (curr) {
printf ("%d\n", curr->data);
struct Node *victim = curr; /* self-explanatory */
curr = curr->qnext;
/* do not forget to free allocated memory */
if (victim != &head && victim != &tail) {
free (victim);
}
}
Putting it altogether, you can do something like the following:
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
struct Node {
int data;
struct Node *qprev;
struct Node *qnext;
} Node;
struct Node qllentry[2];
int main (void) {
struct Node head = { .data = INT_MAX },
tail = { .data = INT_MIN },
*curr,
*new_node;
qllentry[0] = head; /* keep your array and list operations separate */
qllentry[1] = tail;
head.qnext = &tail; /* begin list operations */
tail.qprev = &head;
int key = 20;
curr = &head;
/* test curr->qnext->data > key to stop before tail */
while (curr->qnext && curr->qnext->data > key)
curr = curr->qnext;
/* allocate and VALIDATE! */
if (!(new_node = malloc (sizeof *new_node))) {
perror ("malloc - new_node");
exit (EXIT_FAILURE);
}
new_node->data = key; /* assign value to new_node */
new_node->qprev = curr; /* rewire pointers */
new_node->qnext = curr->qnext;
curr->qnext->qprev = new_node;
curr->qnext = new_node;
curr = &head; /* output list */
while (curr) {
printf ("%d\n", curr->data);
struct Node *victim = curr; /* self-explanatory */
curr = curr->qnext;
/* do not forget to free allocated memory */
if (victim != &head && victim != &tail) {
free (victim);
}
}
return 0;
}
Example Use/Output
$ ./bin/llarray
2147483647
20
-2147483648
Memory Use/Error Check
It is imperative that you use a memory error checking program to insure you do not attempt to access memory or write beyond/outside the bounds of your allocated block, attempt to read or base a conditional jump on an uninitialized value, and finally, to confirm that you free all the memory you have allocated.
For Linux valgrind is the normal choice. There are similar memory checkers for every platform. They are all simple to use, just run your program through it.
$ valgrind ./bin/llarray
==8665== Memcheck, a memory error detector
==8665== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==8665== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
==8665== Command: ./bin/llarray
==8665==
2147483647
20
-2147483648
==8665==
==8665== HEAP SUMMARY:
==8665== in use at exit: 0 bytes in 0 blocks
==8665== total heap usage: 1 allocs, 1 frees, 24 bytes allocated
==8665==
==8665== All heap blocks were freed -- no leaks are possible
==8665==
==8665== For counts of detected and suppressed errors, rerun with: -v
==8665== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
Always confirm that you have freed all memory you have allocated and that there are no memory errors.
Simple Pointer Dump/Check
Lastly, in addition to stepping though the addresses with a debugger, you can always write a short debug routing to help you pick out if, and where, you have any problems with your pointer handling. (you don't have to output anything at all, you can just check the addresses with an equality if you like) This lets you look at all pointers at once. Just a simple routing to output your node pointers is often helpful. All you need is, e.g.
void debugptrs (struct Node *list)
{
printf ("list pointers:\n\n");
for (struct Node *iter = list; iter; iter = iter->qnext)
printf ("prev: %16p curr: %16p next: %16p\n",
(void*)iter->qprev, (void*)iter, (void*)iter->qnext);
putchar ('\n');
}
Which would provide output similar to:
$ ./bin/llarray
list pointers:
prev: (nil) curr: 0x7ffd56371910 next: 0x1038010
prev: 0x7ffd56371910 curr: 0x1038010 next: 0x7ffd56371930
prev: 0x1038010 curr: 0x7ffd56371930 next: (nil)
I always found it helpful just to visually traverse the address from head to tail and back. If any prev or next for a node isn't what is output as the address for that node on the previous (or next) line, you know where you problem is.
Look things over and let me know if you have further questions.