Last active
August 29, 2015 14:01
-
-
Save REPOmAN2v2/4bb1df1553cad7a18126 to your computer and use it in GitHub Desktop.
C array NULL sorting program
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
#include <stdlib.h> | |
#include <stdio.h> | |
int main(void) | |
{ | |
const void * const t[] = { malloc(1), NULL, malloc(1), NULL, malloc(1)}; | |
const size_t size = sizeof t / sizeof *t; | |
const void ** it = (const void **) t; | |
const void * const * const end = (const void **) t + size; | |
for (unsigned i = 0; i < size; ++i) | |
printf("%p,", t[i]); | |
putchar('\n'); | |
while (it < end) | |
{ | |
if (NULL == *it) | |
{ | |
void const **p = it + 1; | |
while (p < end && NULL == *p) | |
++p; | |
if (p != end) | |
*it = *p, *p = NULL; | |
} | |
++it; | |
} | |
for (unsigned i = 0; i < size; ++i) | |
printf("%p,", t[i]); | |
putchar('\n'); | |
} | |
/* About that bit of code: | |
* malloc is only used because it returns a pointer on void, so it seemed like a reasonable way to fill a void* table. | |
I could also have hardcoded values (0xdeadbeef, for example), but then I would have had to recast in (void *) every | |
single one of them. That would not have been cool. Note that it would have compiled without the casts, but I like code | |
that does not warn, even with -Weverything. | |
* This problem, and, by extension, the code, is just a simple array traversal, with some swaps. Have you ever written | |
a Bubble Sort? It should be similar. | |
* Basically, the const qualifier is useless. I just like keeping my types as strict as possible (for example, the | |
choice of an unsigned in my for loops), because that tends to make the program "safer". | |
const void * const t[] = { malloc(1), NULL, malloc(1), NULL, malloc(1)}; | |
This builds an array of pointers, of size 7. The elements initialized by malloc(1) are non-NULL pointers, there others | |
obviously are. The "void *[]" is a void pointer array. The first const means that these pointers point to memory that | |
cannot be modifier; the second const means the pointer itself cannot be modifier: it can not be changed to point to | |
somewhere else. | |
const size_t size = sizeof t / sizeof *t; | |
Because this is not a (null-terminated) string, I need to keep the array's length somewhere in order not to overextend | |
when performing my traversal. Again, this variable will not change, so it is marked const. Its type is size_t, which is | |
basically an unsigned. The name is more explicite as to its nature as a type size. | |
I did not harcode the size to be 7, so the code is more flexible: I can just ad dor remove values from the array's | |
initialization, and the size will update automagically. How do I calculate it? The sizeof keyword gives the size of the | |
following type, in bytes. An array of 7 pointers has a size of 7 times the size of a pointer. So divide it by the size | |
of a pointer. | |
How does one iterate over an array? There are two ways: | |
* Use the index subscript notation, t[i], with a loop and a counter; | |
* Use iterators. | |
Depending on your algorithmic background, and your habits, one way might be more intuitive than the other. I use the | |
second one, here. | |
const void ** it = (const void **) t; | |
You just have to know that t[0], the first element of the array, can also be referred to as *t. Also, you should know | |
that t[i] is the same as *(t + i): it's the element at the addresse which is offset by i from the start of the array. | |
Therefore, *it is the first element of the array. Once I will have incremented this pointer, it will be pointing on the | |
second element of the array and so on, until the end. So, where is the end exactly? It is size positions after the start: | |
const void * const * const end = (const void **) t + size; | |
I had to cast t to (const void**), because it used to be a (const void[7]*), which isn't exactly the same (but you | |
shouldn't care, for now). The end iterator is a pointer, which will not change, on an other pointer (the elements of the | |
array *are* pointers, in this case), which also will not change, and pointing to memory that will not change; hence the | |
shitload of consts. Also, be noted that 'end' pointer *after* the last element, so we must pay extra attention not to | |
dereference it carelessly. | |
for (unsigned i = 0; i < size; ++i) | |
printf("%p,", t[i]); | |
putchar('\n'); | |
This is not really a part of the program, it's just some output to demonstrate it. This time, I iterate over it with a | |
way which you may be more familiar with. | |
"%p," is the format string for pointers (hexadecimal output, or "(nil)" for null pointers), followed by a comma. | |
putchar() outputs a single character, in this case the line feed. | |
I output each pointer of the array, separated by commas, and terminating the whole with a newline. | |
We have seen the declarations, and the output. | |
Now, we get to the important stuff, the main loop. | |
while (it < end) | |
{ | |
// stuff where *it is the n-th element of the array | |
++it; | |
} | |
The problem at hand is: for each element of the array, if it is NULL, replace it with the next non-NULL element. | |
So first, lets test the current element. If it is not NULL, nothing to do here, proceed to the next one. Hence, | |
everything that follows is enclosed in a conditional statement: | |
if (NULL == *it) | |
{ | |
// more stuff | |
} | |
Note that often beginners, and more advanced people, will write comparisions the other way around. The logic behind | |
this order is that in case of a typo, and a = is missing, there will be a compilation error: no value can be affected | |
to a constant such as NULL. In the other case, it would have become *it = NULL, an assignment, which the compiler | |
would have accepted. Of course, since I work with -Weverything, and pay attention and address any warning I see, and | |
since nowadays compiler always warn on such a mistake, it would not have been a problem. But habits die hard, | |
especially when they can be considered good practice. | |
So, lets say the current element is NULL. What do we do now? We search for the next non-NULL one. When we will have | |
found it, we will swap them. | |
void const **p = it + 1; | |
We will find that next element by iterating in a similar fashion. p is our new iterator. We start it on the next | |
element (because we don't want to swap with ourselves, do we?) (Note: since the current element is null, and we | |
are looking for a non-null one, omitting the + 1 would not have had any impact, but it makes sense to have it.) | |
while (p < end && NULL == *p) | |
++p; | |
This is a loop that does nothing. It moves p towards the end, stopping either when the end is reached, or when a | |
non-null is reached. | |
if (p != end) | |
So which one was it? Did we stop because we found a suitable replacement? Or was it just the end of the array | |
(a failure)? If it was a failure, there is nothing to be done. TBH, the loop will go on to the next iterations | |
in my code, which is not great, because the are guaranteed to be useless. I should have done | |
if (p == end) | |
break; | |
else | |
instead, but meh. | |
So, we have found our replacement! Lets swap! | |
*it = *p, *p = NULL; | |
Which, of course, is the same as: | |
*it = *p; | |
*p = NULL; | |
*it, which was NULL, is replaced by our non-null value. | |
The non-null value becomes null in our element's stead. */ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment