Skip to content

Instantly share code, notes, and snippets.

@eric-tramel
Created May 28, 2016 19:37
Show Gist options
  • Save eric-tramel/688fdd9e1099c06f52f36101547edb92 to your computer and use it in GitHub Desktop.
Save eric-tramel/688fdd9e1099c06f52f36101547edb92 to your computer and use it in GitHub Desktop.
My weird way of practicing C...covers a bunch of bases.
/*
In this program we will review the queue data structre from the perspective
of C.
However, technically, the implementation here is for a *circular array*
which we can access from either end, as well as popping from either end.
The main feature of this implementation is the fact that the framework is
general to the type of variable to be stored within the circular array.
Obviously, one might attempt an implementation using linked lists, which
are amenable to growing and shrinking as needed. This implementation was
more of a practice for using `void*` and pointer math to achieve the same
result with full data type generality with static memory allocations via
circular arrays.
Current Functionality:
[*] Instantiate queues with arbitrary data types.
[*] Pushing to the front or rear of the queue.
[*] Poping entries from the front or rear of the queue.
[*] Ability to free/delete and reallocate queues.
[*] Ability to dynamically resize the queue to allow for more posssible
entries.
[-] Removing memory space from the queue is currently not supported.
[*] Debugging functions for displaying queue status.
Written By: Eric W. Tramel, 28/5/16.
Copyright: MIT... no warranty, code as is, etc.
*/
#include <stdlib.h>
#include <stdio.h>
/*
* Typed Structure: Queue
* ----------------------
* Contains all necessary elements for a queue. Maintains a pointer to the
* allocated memory space for the queue data objects. Also maintains indicies
* and pointers to the front and rear of the queue for easy access from the
* front or back.
*/
typedef struct {
void* data;
int front; // Index value
int rear; // Index value
void* pFront; // Pointer to leading element
void* pRear; // Pointer to rear element
int length; // Index value
int maxLength; // Maximum number of entries, according to width & allocation
size_t dataWidth; // width sizing
size_t allocatedSpace; // Total amount of memory space for circular array.
} Queue;
/* Prototypes */
void dump_queue(Queue*);
void* inc_ptr(void*,int);
void init_queue(Queue*);
void delete_queue(Queue*);
int allocate_queue(Queue*, unsigned int, size_t);
int extend_queue(Queue*, unsigned int);
int set_queue(Queue*, void*, size_t, size_t);
int pop_rear(Queue*);
int pop_front(Queue*);
void print_queue_asint(Queue*, const char []);
/*
* Function: inc_ptr
* -----------------
* Increment a pointer by the given number of bytes, returning the new pointer
* value. The value `numBytes` is signed, so we can increment by a negative
* value of bytes to decrement the pointer.
*/
void* inc_ptr(void* ptr, int numBytes){
// Cast pointer to character, advance pointer by the
// specified number of bytes, and then return as a general `void*`.
char* newPtr = (char*)ptr + numBytes;
return((void*) newPtr);
}
/*
* Function: allocate_queue
* -----------------
* Given a pointer to a queue structure, allocate space for the specified
* number of elements of the target byte width. If the space could not be
* allocated, the function returns 0.
*/
int allocate_queue(Queue *Q, unsigned int numElements, size_t dataWidth){
size_t allocationSize = (size_t) numElements * dataWidth;
// Does this queue already contain data?
if(Q->data != NULL){
delete_queue(Q);
// Re-call this function, giving the proper return value
if(allocate_queue(Q, numElements, dataWidth)){
return 1;
}else{
return 0;
}
}
Q->data = calloc(numElements,dataWidth);
if(Q->data == NULL){
printf("Failed to allocate %lu bytes of space.\n",allocationSize);
return 0;
}
Q->length = 0;
Q->front = 0;
Q->pFront = Q->data;
Q->rear = 0;
Q->pRear = Q->data;
Q->dataWidth = dataWidth;
Q->allocatedSpace = allocationSize;
Q->maxLength = numElements;
return 1;
}
/*
* Function: extend_queue
* -----------------
* Given a pointer to a queue structure, increase the amount of space by a
* number of elements of the byte width specified by the queue. If the space
* could not be allocated, the function returns 0.
* If we want to extend a queue which already contains nothing, then lets
* just allocate the space. Of course this only works if the dataWidth
* field is specified. The behavior is undefined if one attempts
*/
int extend_queue(Queue *Q, unsigned int elementsToAdd){
// An empty array? Allocate the space
if(Q->data == NULL){
if(allocate_queue(Q,elementsToAdd,Q->dataWidth)){
return 1;
}else{
return 0;
}
}
// Nothing to do if we don't have to.
if(elementsToAdd == 0)
return 1;
// Reallocate the array with additional entries. Content should remain
// the same.
Q->data = realloc(Q->data,(Q->maxLength + elementsToAdd)*Q->dataWidth);
Q->maxLength += elementsToAdd;
Q->allocatedSpace = Q->maxLength * Q->dataWidth;
// The indices `.front` and `.rear` should remain the same, but the
// pointers to these locations has changed.
Q->pFront = inc_ptr(Q->data, Q->front*Q->dataWidth);
Q->pRear = inc_ptr(Q->data, Q->rear*Q->dataWidth);
return 1;
}
/*
* Function: init_queue_empty
* --------------------------
* Takes a pointer to a queue structure and sets all entries to base initial
* values. In this case, `NULL` for all pointers, all indices to 0, and
* setting the dataWidth to 1 byte (char).
*/
void init_queue(Queue *Q){
Q->data = NULL;
Q->front = 0;
Q->pFront = NULL;
Q->rear = 0;
Q->pRear = NULL;
Q->length = 0;
Q->dataWidth = 1;
Q->allocatedSpace = 0;
Q->maxLength = 0;
}
/*
* Function: set_queue
* --------------------------
* Takes a pointer to a queue structure and sets its state according to the
* passed memory array. By specifying the dataWidth we are able to count the
* number of elements which exist inside the specified memory location.
*/
int set_queue(Queue *Q, void* initialData, size_t allocationSize,
size_t dataWidth){
// Check to see if data sent for initialization is actually empty.
// If it is empty, just make an empty queue...assumes a proper
// setting of `initialData = NULL` for an empty set. If it is improperly
// set, then, well, we can't really check or do anything about that.
int numElements = 0;
int i = 0;
// Check to see if we have an existing array allocation. If so, delete the
// the queue and start over.
if(Q->data != NULL){
delete_queue(Q);
// Re-call this function, giving the proper return value
if(set_queue(Q, initialData, allocationSize, dataWidth)){
return 1;
}else{
return 0;
}
}
if(initialData == NULL){
init_queue(Q);
}else{
numElements = allocationSize / dataWidth;
Q->data = malloc(allocationSize);
if(Q->data == NULL){
printf("Failed to allocate %lu bytes of space.\n",allocationSize);
return 0;
}
Q->length = numElements;
Q->front = 0;
Q->pFront = Q->data;
Q->rear = numElements - 1;
Q->pRear = (char*)Q->data + allocationSize - dataWidth;
Q->dataWidth = dataWidth;
Q->allocatedSpace = allocationSize;
Q->maxLength = numElements;
// Stuff this array of data into queue structure, but do so in a way
// that is agnostic to typing, and just uses the data width. Lets do
// this by casting the pointers as characters (bytes) and then stuffing.
// We can be agnostic to any kind of typing, etc, because we are just
// stuffing in bytes in the same way as they appear in the original
// dataset.
for(i = 0; i < allocationSize; i++){
*((char*) Q->data + i) = *((char*)initialData + i);
}
}
return 1;
}
/*
* Function: delete_queue
* --------------------------
* Takes a pointer to a Queue structure and frees the inner memory allocation.
* Also resets all properties of the queue and invalidates all pointers.
*/
void delete_queue(Queue *Q){
free(Q->data); // No memory leaks
Q->front = 0; // Convention
Q->pFront = NULL; // Convention
Q->rear = 0; // Convention
Q->pRear = NULL; // Convention
Q->length = 0; // Convention
Q->data = NULL; // Convention
}
/*
* Function: pop_front
* --------------------
* Remove the first element from the queue. If the element is successfully
* removed, then the function returns 1. If for some reason the first element
* could not be removed (e.g. an empty queue), then returns 0.
*/
int pop_front(Queue *Q){
if(Q->length == 0){
// Nothing in queue, failed pop.
return 0;
}
Q->length--; // Decrement queue length
if(Q->front == (Q->maxLength-1)){
Q->front = 0;
Q->pFront = Q->data;
}else{
Q->front++;
Q->pFront = inc_ptr(Q->pFront, (int)Q->dataWidth);
}
return 1;
}
/*
* Function: push_front
* -------------------
* Take in an arbitrary datum and push it into the front of the queue. If the
* queue is already full, then returns 0. If successfully included, returns 1.
* If the dataum has size larger than the `dataWidth` specified in the target
* Queue, then only the top `dataWidth` bytes are copied into the queue.
*/
int push_front(Queue* Q, void* datum){
int i;
if(Q->length == Q->maxLength){
return 0;
}
Q->length++; // Increment queue length
if(Q->front == 0){
Q->front = Q->maxLength - 1; // Wrapping around to rear
Q->pFront = inc_ptr(Q->data, Q->dataWidth*Q->front);
}else{
// If we have only one element, we don't need to change the pointer
if(Q->length > 1){
Q->front--;
Q->pFront = inc_ptr(Q->pFront, -(int)Q->dataWidth);
}
}
// Copy dataum
for(i = 0; i < Q->dataWidth; i++){
*((char*) Q->pFront + i) = *((char*)datum + i);
}
return 1;
}
/*
* Function: pop_rear
* ------------------
* Remove the last element from the queue. If the element is successfully
* removed then the function returns 1. If for some reason the last element
* could not be removed (e.g. an empty queue), then returns 0.
*/
int pop_rear(Queue *Q){
if(Q->length == 0){
// Nothing in queue, failed pop.
return 0;
}
Q->length--; // Decrement queue length
// Account for wrapping around on the circular array.
if(Q->rear == 0){
Q->rear = Q->maxLength - 1;
Q->pRear = inc_ptr(Q->data, Q->dataWidth*Q->rear);
}else{
Q->rear--;
Q->pRear = inc_ptr(Q->pRear, -(int)Q->dataWidth);
}
return 1;
}
/*
* Function: push_rear
* -------------------
* Take in an arbitrary datum and push it into the back of the queue. If the
* queue is already full, then returns 0. If successfully included, returns 1.
* If the dataum has size larger than the `dataWidth` specified in the target
* Queue, then only the top `dataWidth` bytes are copied into the queue.
*/
int push_rear(Queue* Q, void* datum){
int i;
if(Q->length == Q->maxLength){
return 0;
}
Q->length++; // Increment queue length
if(Q->rear == (Q->maxLength-1)){
Q->rear = 0; // Wrapping around to front of the circular array
Q->pRear = Q->data; // We should now point to the first entry
}else{
// Only increment the rear index/pointer if we aren't length 1.
// Otherwise, keep it the same.
if(Q->length > 1){
Q->rear++;
Q->pRear = inc_ptr(Q->pRear, (int)Q->dataWidth);
}
}
// Copy dataum
for(i = 0; i < Q->dataWidth; i++){
*((char*) Q->pRear + i) = *((char*)datum + i);
}
return 1;
}
/*
* Function: print_queue_asint
* --------------------
* A debug function to report all of the contents of `Q->data`. The queue is
* traversed in chunks of size `sizeof(int)` and printed to STDOUT as ints.
*/
void print_queue_asint(Queue *Q, const char format[]){
int i = Q->front;
if(Q->length == 0){
printf("[]\n");
}else{
printf("[");
while(i!= Q->rear){
printf(format,((int*)Q->data)[i] );
i = (i+1) % Q->maxLength; // Wrapping around
printf(",");
}
printf(format,((int*)Q->data)[Q->rear]); // make sure to print last val
printf("]\n");
}
}
/*
* Function: print_queue_aschar
* --------------------
* A debug function to report all of the contents of `Q->data`. The queue is
* traversed in chunks of size `sizeof(char)` and printed to STDOUT as chars.
*/
void print_queue_aschar(Queue *Q, const char format[]){
int i = Q->front;
if(Q->length == 0){
printf("[]\n");
}else{
printf("[");
while(i!= Q->rear){
printf(format,((char*)Q->data)[i] );
i = (i+1) % Q->maxLength; // Wrapping around
printf(",");
}
printf(format,((char*)Q->data)[Q->rear]); // make sure to print last val
printf("]\n");
}
}
/*
* Function: dump_queue
* --------------------
* A debug function to report all of the contents of the given queue
* structure. Also reports the de-ref values of the front and end points in
* hex (in order to support variable datum widths).
*/
void dump_queue(Queue *Q){
int i;
printf("&Q = %lu\n",(unsigned long)Q);
printf(" .length = %d\n",Q->length);
printf(" .front = %d\n",Q->front);
printf(" .rear = %d\n",Q->rear);
printf(" .dataWidth = %lu\n",Q->dataWidth);
printf(" .allocatedSpace = %lu\n",Q->allocatedSpace);
printf(" .maxLength = %d\n",Q->maxLength);
// Print front and rear data as hex
if(Q->pFront != NULL){
printf(" *(.pFront) = 0x");
for(i = 0; i < Q->dataWidth; i++){
printf("%02X",*((char*) Q->pFront + i));
}
printf("\n");
}
if(Q->pRear != NULL){ // As int for debug
printf(" *(.pRear) = 0x");
for(i = 0; i < Q->dataWidth; i++){
printf("%02X",*((char*) Q->pRear + i));
}
printf("\n");
}
}
/*
* Function: Main Program
* ----------------------
* In this program we test out some of the basic features of the queue
* system we have developed above.
*/
int main(int argc, char* argv[]){
Queue *Q = (Queue*)malloc(sizeof(Queue));
int someData[] = {1,2,3,4,5,6,7,8,9,10};
int a = 30, b;
char c;
if(Q == NULL){
printf("Failed to allocate space for a single structure...?\n");
abort();
}
printf("Orig. Elements: %lu\n",sizeof(someData)/sizeof(int));
init_queue(Q);
if(!set_queue(Q,someData,sizeof(someData),sizeof(int)))
abort();
print_queue_asint(Q,"%d");
dump_queue(Q);
printf("First Value in Queue : %d\n",*((int*)Q->pFront));
printf("Last Value in Queue : %d\n",*((int*) Q->pRear));
// Pop everything out
while(pop_rear(Q)){
print_queue_asint(Q,"%d");
}
// Fill back from rear
while(a <= 45){
if(push_rear(Q,&a)){
print_queue_asint(Q,"%d");
}else{
printf("Didn't push %d, no room.\n",a);
}
a++;
}
// Test resetting the queue
if(!set_queue(Q,someData,sizeof(someData),sizeof(int)))
abort();
print_queue_asint(Q,"%d");
// Pop everything out
while(pop_front(Q)){
print_queue_asint(Q,"%d");
dump_queue(Q);
}
// Fill Back
while(a <= 60){
if(push_rear(Q,&a)){
print_queue_asint(Q,"%d");
}else{
printf("Didn't push %d, no room.\n",a);
}
a++;
}
dump_queue(Q);
// Reset
if(!set_queue(Q,someData,sizeof(someData),sizeof(int)))
abort();
print_queue_asint(Q,"%d");
// Try a circular display
for(a = 0; a < 20; a++){
b = *((int*)Q->pFront); // Store front value
pop_front(Q); // Pop front value
push_rear(Q,&b); // push the front value into the back
print_queue_asint(Q,"%d");
}
// Try a reverse circular display
for(a = 0; a < 20; a++){
b = *((int*)Q->pRear); // Store front value
pop_rear(Q); // Pop front value
push_front(Q,&b); // push the front value into the back
print_queue_asint(Q,"%d");
}
// Test out reallocating the space in the array
if(!allocate_queue(Q,5,sizeof(char)))
abort();
print_queue_aschar(Q,"%c");
printf("Pushing into reallocated Queue...\n");
for(a = 0; a < 10; a++){
c = 'A' + a;
if(push_rear(Q,&c)){
print_queue_aschar(Q,"%c");
}else{
printf("Not enough room to push %c.\n",c);
}
}
// Can I extend the queue?
printf("Before Extending:\n");
dump_queue(Q);
printf("After Extending:\n");
if(!extend_queue(Q,5))
abort();
dump_queue(Q);
for(a = 0; a < 10; a++){
c = 'A' + a;
if(push_rear(Q,&c)){
print_queue_aschar(Q,"%c");
}else{
printf("Not enough room to push %c.\n",c);
}
}
delete_queue(Q);
return 0;
}
@eric-tramel
Copy link
Author

The output of this program is something like this:

Orig. Elements: 10
[1,2,3,4,5,6,7,8,9,10]
&Q = 140315664056320
  .length = 10
  .front = 0
  .rear = 9
  .dataWidth = 4
  .allocatedSpace = 40
  .maxLength = 10
  *(.pFront) = 0x01000000
  *(.pRear) = 0x0A000000
First Value in Queue : 1
Last Value in Queue : 10
[1,2,3,4,5,6,7,8,9]
[1,2,3,4,5,6,7,8]
[1,2,3,4,5,6,7]
[1,2,3,4,5,6]
[1,2,3,4,5]
[1,2,3,4]
[1,2,3]
[1,2]
[1]
[]
[30]
[30,31]
[30,31,32]
[30,31,32,33]
[30,31,32,33,34]
[30,31,32,33,34,35]
[30,31,32,33,34,35,36]
[30,31,32,33,34,35,36,37]
[30,31,32,33,34,35,36,37,38]
[30,31,32,33,34,35,36,37,38,39]
Didn't push 40, no room.
Didn't push 41, no room.
Didn't push 42, no room.
Didn't push 43, no room.
Didn't push 44, no room.
Didn't push 45, no room.
[1,2,3,4,5,6,7,8,9,10]
[2,3,4,5,6,7,8,9,10]
&Q = 140315664056320
  .length = 9
  .front = 1
  .rear = 9
  .dataWidth = 4
  .allocatedSpace = 40
  .maxLength = 10
  *(.pFront) = 0x02000000
  *(.pRear) = 0x0A000000
[3,4,5,6,7,8,9,10]
&Q = 140315664056320
  .length = 8
  .front = 2
  .rear = 9
  .dataWidth = 4
  .allocatedSpace = 40
  .maxLength = 10
  *(.pFront) = 0x03000000
  *(.pRear) = 0x0A000000
[4,5,6,7,8,9,10]
&Q = 140315664056320
  .length = 7
  .front = 3
  .rear = 9
  .dataWidth = 4
  .allocatedSpace = 40
  .maxLength = 10
  *(.pFront) = 0x04000000
  *(.pRear) = 0x0A000000
[5,6,7,8,9,10]
&Q = 140315664056320
  .length = 6
  .front = 4
  .rear = 9
  .dataWidth = 4
  .allocatedSpace = 40
  .maxLength = 10
  *(.pFront) = 0x05000000
  *(.pRear) = 0x0A000000
[6,7,8,9,10]
&Q = 140315664056320
  .length = 5
  .front = 5
  .rear = 9
  .dataWidth = 4
  .allocatedSpace = 40
  .maxLength = 10
  *(.pFront) = 0x06000000
  *(.pRear) = 0x0A000000
[7,8,9,10]
&Q = 140315664056320
  .length = 4
  .front = 6
  .rear = 9
  .dataWidth = 4
  .allocatedSpace = 40
  .maxLength = 10
  *(.pFront) = 0x07000000
  *(.pRear) = 0x0A000000
[8,9,10]
&Q = 140315664056320
  .length = 3
  .front = 7
  .rear = 9
  .dataWidth = 4
  .allocatedSpace = 40
  .maxLength = 10
  *(.pFront) = 0x08000000
  *(.pRear) = 0x0A000000
[9,10]
&Q = 140315664056320
  .length = 2
  .front = 8
  .rear = 9
  .dataWidth = 4
  .allocatedSpace = 40
  .maxLength = 10
  *(.pFront) = 0x09000000
  *(.pRear) = 0x0A000000
[10]
&Q = 140315664056320
  .length = 1
  .front = 9
  .rear = 9
  .dataWidth = 4
  .allocatedSpace = 40
  .maxLength = 10
  *(.pFront) = 0x0A000000
  *(.pRear) = 0x0A000000
[]
&Q = 140315664056320
  .length = 0
  .front = 0
  .rear = 9
  .dataWidth = 4
  .allocatedSpace = 40
  .maxLength = 10
  *(.pFront) = 0x01000000
  *(.pRear) = 0x0A000000
[46]
[46,47]
[46,47,48]
[46,47,48,49]
[46,47,48,49,50]
[46,47,48,49,50,51]
[46,47,48,49,50,51,52]
[46,47,48,49,50,51,52,53]
[46,47,48,49,50,51,52,53,54]
[46,47,48,49,50,51,52,53,54,55]
Didn't push 56, no room.
Didn't push 57, no room.
Didn't push 58, no room.
Didn't push 59, no room.
Didn't push 60, no room.
&Q = 140315664056320
  .length = 10
  .front = 0
  .rear = 9
  .dataWidth = 4
  .allocatedSpace = 40
  .maxLength = 10
  *(.pFront) = 0x2E000000
  *(.pRear) = 0x37000000
[1,2,3,4,5,6,7,8,9,10]
[2,3,4,5,6,7,8,9,10,1]
[3,4,5,6,7,8,9,10,1,2]
[4,5,6,7,8,9,10,1,2,3]
[5,6,7,8,9,10,1,2,3,4]
[6,7,8,9,10,1,2,3,4,5]
[7,8,9,10,1,2,3,4,5,6]
[8,9,10,1,2,3,4,5,6,7]
[9,10,1,2,3,4,5,6,7,8]
[10,1,2,3,4,5,6,7,8,9]
[1,2,3,4,5,6,7,8,9,10]
[2,3,4,5,6,7,8,9,10,1]
[3,4,5,6,7,8,9,10,1,2]
[4,5,6,7,8,9,10,1,2,3]
[5,6,7,8,9,10,1,2,3,4]
[6,7,8,9,10,1,2,3,4,5]
[7,8,9,10,1,2,3,4,5,6]
[8,9,10,1,2,3,4,5,6,7]
[9,10,1,2,3,4,5,6,7,8]
[10,1,2,3,4,5,6,7,8,9]
[1,2,3,4,5,6,7,8,9,10]
[10,1,2,3,4,5,6,7,8,9]
[9,10,1,2,3,4,5,6,7,8]
[8,9,10,1,2,3,4,5,6,7]
[7,8,9,10,1,2,3,4,5,6]
[6,7,8,9,10,1,2,3,4,5]
[5,6,7,8,9,10,1,2,3,4]
[4,5,6,7,8,9,10,1,2,3]
[3,4,5,6,7,8,9,10,1,2]
[2,3,4,5,6,7,8,9,10,1]
[1,2,3,4,5,6,7,8,9,10]
[10,1,2,3,4,5,6,7,8,9]
[9,10,1,2,3,4,5,6,7,8]
[8,9,10,1,2,3,4,5,6,7]
[7,8,9,10,1,2,3,4,5,6]
[6,7,8,9,10,1,2,3,4,5]
[5,6,7,8,9,10,1,2,3,4]
[4,5,6,7,8,9,10,1,2,3]
[3,4,5,6,7,8,9,10,1,2]
[2,3,4,5,6,7,8,9,10,1]
[1,2,3,4,5,6,7,8,9,10]
[]
Pushing into reallocated Queue...
[A]
[A,B]
[A,B,C]
[A,B,C,D]
[A,B,C,D,E]
Not enough room to push F.
Not enough room to push G.
Not enough room to push H.
Not enough room to push I.
Not enough room to push J.
Before Extending:
&Q = 140315664056320
  .length = 5
  .front = 0
  .rear = 4
  .dataWidth = 1
  .allocatedSpace = 5
  .maxLength = 5
  *(.pFront) = 0x41
  *(.pRear) = 0x45
After Extending:
&Q = 140315664056320
  .length = 5
  .front = 0
  .rear = 4
  .dataWidth = 1
  .allocatedSpace = 10
  .maxLength = 10
  *(.pFront) = 0x41
  *(.pRear) = 0x45
[A,B,C,D,E,A]
[A,B,C,D,E,A,B]
[A,B,C,D,E,A,B,C]
[A,B,C,D,E,A,B,C,D]
[A,B,C,D,E,A,B,C,D,E]
Not enough room to push F.
Not enough room to push G.
Not enough room to push H.
Not enough room to push I.
Not enough room to push J.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment