Created
May 28, 2016 19:37
-
-
Save eric-tramel/688fdd9e1099c06f52f36101547edb92 to your computer and use it in GitHub Desktop.
My weird way of practicing C...covers a bunch of bases.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
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; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
The output of this program is something like this: