2

I am currently working on a ZigBee WSNDemo project and I am stuck on this part of code. Basically, I have to use this macro for queue purposes in appInitMsgSender function.

void appInitMsgSender(void)
{
    txState = APP_MSG_TX_FREE_STATE;

    resetQueue(&appToSendQueue);
    resetQueue(&appFreeQueue);
    resetQueue(&appSentQueue);
    resetQueue(&appDoneQueue);

    for (uint8_t i = 0; i < ARRAY_SIZE(appTxBuffers); i++)
    {
        putQueueElem(&appFreeQueue, &appTxBuffers[i].next);
    }
}

Above is the application's message send initialization function. The following are macros used for it. I want to know how both are connected. I mean how to understand the working of this code.

#define DECLARE_QUEUE(queue) QueueDescriptor_t queue = {.head = NULL,}

// Type of queue element
typedef struct _QueueElement_t
{
    struct _QueueElement_t *next;
} QueueElement_t;

// Queue descriptor
typedef struct
{
    QueueElement_t *head;
} QueueDescriptor_t;

INLINE void resetQueue(QueueDescriptor_t *queue)
{
    queue->head = NULL;
}

I am really confused with use of pointer here. I am aware about how pointer works and theory behind it. But in the above context I am bewildered.

tomlogic
  • 11,489
  • 3
  • 33
  • 59
DarshanJoshi
  • 121
  • 2
  • 7

2 Answers2

3

What's happening here is a lot of abstraction. Basically hiding the details to make the main body of the code more readable. Of course, you need to understand all the underlying details implicitly in your head in order for what you're reading to make any sense.

To declare a queue, would simply be:

DECLARE_QUEUE(appFreeQueue);

If we expand the macro, this is the same as writing:

QueueDescriptor_t appFreeQueue = {.head = NULL,};

If we then expand the QueueDescriptor_t typedef, it would be the same as writing:

struct appFreeQueue
{
    QueueElement_t *head = NULL;
};

And if we expand the QueueElement_t typedef, we can see that each element points to another element of the same type:

struct appFreeQueue
{
    typedef struct _QueueElement_t
    {
        struct _QueueElement_t *next = NULL;
    } *head;
};

But rather than write that out every time you want to declare a queue, you can just use the DECLARE_QUEUE macro instead. That will create an empty queue in the form of a linked list. A linked list usually takes the form of each link holding two things, some data and a pointer to the next link. Like so:

   Element1             Element2             Element3
[data][*Element2]->  [data][*Element3]->  [data][NULL]

What's great about this is that you don't have to block off a section of memory when you start and leave it empty and unused until it is needed like you would with an array. Instead each link can be dynamically allocated when it's needed and a pointer to it's location in memory given to the previous link.

The INLINE function acts very similar to the macro. Where ever you see resetQueue, in your head you should replace it with the function defined there. In this case it's setting the very first link's next pointer to NULL resulting in an empty queue.

There are slight differences between a macro and an INLINE function. These differences are described in detail in the answer to this question: Inline functions vs Preprocessor macros

Community
  • 1
  • 1
embedded.kyle
  • 10,976
  • 5
  • 37
  • 56
0

The queue is implemented with a data structure known as a linked list. The QueueDescriptor_t contains a pointer to the first QueueElement_t in the list. Each QueueElement_t contains a pointer to the next QueueElement_t in the list. When the QueueDescriptor_t pointer is NULL then the list is empty. When the QueueElement_t pointer is NULL then it is the last item in the list. A linked list allows you to create a collection of items but the items don't have to be contiguous in memory (the next pointer links to the next item in the collection). For comparison, an array is a collection of items that are contiguous in memory (no next pointer is required).

kkrambo
  • 6,643
  • 1
  • 17
  • 30