简体   繁体   中英

Array of pointers in C with easy iteration

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...

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.

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.

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]);
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM