3

Recently I was pondering over this question: how to make an easier way to iterate over an array of pointer in C.

If I create an array of string in C, it should look like this right?

int size = 5;
char ** strArr = (char **) malloc(sizeof(char *) * size);
if (strArr == NULL) return;

But the problem is, when you want to iterate over this array for some reason (like printing all values inside it), you have to keep track of its current size, storing in another variable.

That's not a problem, but if you create lots of arrays, you have to keep track of every single one of their sizes inside the code. If you pass this array to another function, you must pass its size as well.

void PrintValues (char ** arr, int size) {
    for (int i = 0; i < size; i++)
        printf("%s\n", arr[i]);
}

But when iterating over a string, it's different. You have the '\0' character, which specifies the end of the string. So, you could iterate over a string like this, with not need to keep its size value:

char * str = (char *) malloc(sizeof(char) * 4);
str[0] = 'a';
str[1] = 'b';
str[2] = 'c';
str[3] = '\0';

for (int i = 0; str[i] != '\0'; i++)
    printf("%c", str[i]);
printf("\n");

Now my question: Is it ok or morally right to allocate +1 unit in an array of pointers to maintain its tail as NULL?

char ** strArr = (char **) malloc(sizeof(char *) * (5   +1);
if (strArr == NULL) return;
strArr[0] = PseudoFunc_NewString("Car");
strArr[1] = PseudoFunc_NewString("Car#1");
strArr[2] = PseudoFunc_NewString("Car#2");
strArr[3] = PseudoFunc_NewString("Tree");
strArr[4] = PseudoFunc_NewString("Tree#1");
strArr[5] = NULL; // Stop iteration here as next element is not allocated

Then I could use the NULL pointer to control the iterator:

void PrintValues (char ** arr) {
    for (int i = 0; arr[i] != NULL; i++)
        printf("%s\n", arr[i]);
}

This would help me to keep the code cleaner, though it would consume more memory as a pointer size is larger than a integer size.

Also, when programming with event-based libraries, like Gtk, the size values would be released from the stack at some point, so I would have to create a pointer to dynamically store the size value for example.

In cases like this, it ok to do this? Or is it considered something bad?

Is this technique only used with char pointers because char type has a size of only 1 byte?

I miss having a foreach iterator in C...

Carl HR
  • 776
  • 5
  • 12
  • 3
    It seems good for me. Side note: Casting results of `malloc()` in C is [discouraged](https://stackoverflow.com/questions/605845/do-i-cast-the-result-of-malloc). – MikeCAT Feb 19 '21 at 13:36
  • 1
    `NULL` terminated arrays are common enough - that's exactly how `argv` works. – Oka Feb 19 '21 at 13:36
  • What you allocate with `malloc(sizeof(char *) * size)` is just some memory, a contiguous area of bytes. The compiler doesn't actually know how much memory you allocate, especially if the value of `size` is set at run-time. The pointer returned is a pointer to the first byte of the memory that was allocated. And that pointer is really all the compiler have. So it's up to you to keep track of the size of the memory you allocate, and pass it along to the functions that need it. If you do it using a special terminator or using "number of elements" value doesn't matter, as long as you keep track. – Some programmer dude Feb 19 '21 at 13:38
  • @Oka Except in case of argv you also have argc and a loop iterating `for(size_t i=0; i – Lundin Feb 19 '21 at 13:40
  • @MikeCAT, didn't knew about that.. In my CS course I learned that this was common practice. Thanks! – Carl HR Feb 19 '21 at 13:43
  • 1
    @CarlHR It's not really discouraged, it is merely pointless, given that you are using a C compiler made this millennium. There's some style war here on SO specifically about whether to cast or not. – Lundin Feb 19 '21 at 14:07

3 Answers3

2

Now my question: Is it ok or morally right to allocate +1 unit in an array of pointers to maintain its tail as NULL?

This is ok, the final NULL is called a sentinel value and using one is somewhat common practice. This is most often used when you don't even know the size of the data for some reason.

It is however, not the best solution, because you have to iterate over all the data to find the size. Solutions that store the size separately are much faster. An arrays of structs for example, containing both size and data in the same place.

Lundin
  • 195,001
  • 40
  • 254
  • 396
  • On some programming languages arrays are allocated with twice of size you need. That will allow get significant speed boost because a lot of time can be spend on memory allocation. To prevent iteration over used by you array size often iterator programming pattern is used. – Patryk Gawroński Feb 19 '21 at 14:14
  • @PatrykGawroński Well... C offers the option not to use allocated storage at all, which is even faster yet. – Lundin Feb 19 '21 at 14:19
1

Now my question: Is it ok or morally right to allocate +1 unit in an array of pointers to maintain its tail as NULL?

In C this is quite a common pattern, and it has a name. You're simply using a sentinel value.

As long as your list can not contain null pointers normally this is fine. It is a bit error-prone in general however, then again, that's C for you.

orlp
  • 112,504
  • 36
  • 218
  • 315
0

It's ok, and is a commonly used pattern.

As an alternative you can use a struct, in there you can create a size variable where you can store the current size of the array, and pass the struct as argument. The advantage is that you don't need to iterate through the entire array to know its size.

Example:

Live demo

#include <stdlib.h>
#include <stdio.h>

typedef struct
{
    char **strArr;
    int size;
} MyStruct;

void PrintValues(MyStruct arr) //pass the struct as an argument
{
    for (int i = 0; i < arr.size; i++) //use the size passed in the struct
        printf("%s\n", arr.strArr[i]);
}

int main()
{
    // using the variable to extract the size, to avoid silent errors 
    // also removed the cast for the same reason
    char **strArr = malloc(sizeof *strArr * 5); 

    if (strArr == NULL) return EXIT_FAILURE;

    strArr[0] = "Car";
    strArr[1] = "Car#1";
    strArr[2] = "Car#2";
    strArr[3] = "Tree";
    strArr[4] = "Tree#1";
    
    MyStruct strt = { strArr, 5 }; // initialize the struct
    PrintValues(strt); //voila
    free(strArr); // don't forget to free the allacated memory
    return EXIT_SUCCESS;
}  

This allows for direct access to an index with error checking:

// here if the array index exists, it will be printed
// otherwise no, allows for O(1) access error free
if(arr.size > 6){
    printf("%s\n", arr.strArr[6]);
}
anastaciu
  • 23,467
  • 7
  • 28
  • 53