How to use double pointers (pointer to pointer) for an array of structures properly in standard C?

Viewed 535

I have an array of structures as a function parameter and the size of the array is dynamic. My coworker said that I'll have to use a double pointer since the values contained in the array of struct will be overwritten.

The parameter that will become a double pointer is the following :

xPIDConfig_t **pxPIDConfig

Here is what the structure looks like for the xPIDConfig_t :

typedef struct
{
    ePIDType_t ePIDType;
    /* Common fields for the different types of PID */
    float fLowerSaturationLimit;
    float fUpperSaturationLimit;
    float fOldInput;
    float fIError;
    uint32_t ulDeltaTime;
    eBool_t bSaturationEnable;
    eBool_t bAntiWindupEnable;
    eBool_t bNegativeErrorEmptyIError;
    
    union
    {
        /* Parallel PID fields */
        struct
        {
            float fProportionalGain;
            float fIntegralGain;
            float fDerivativeGain;
        }xParallelPID;
        /* Non-interactive PID fields */
        struct
        {
            float fControllerGain;
            uint32_t ulIntegralTime;
            uint32_t ulDerivativeTime;
        }xNonInteractivePID;
    }xUniqueFields;
}xPIDConfig_t;

The size of the array of pxPIDConfig will vary.

But I am not sure how to malloc that double pointer or even how to use the function containing the double pointer.

I was just wondering if anyone had a good example of code of how to use a function with a double pointer array of variating size? and how to properly change the values contained in the array itself inside a function?

Right now this is how I change the values within the function :

pxPIDConfig->ePIDType = ePIDType;
pxPIDConfig->fOldInput = 0;
pxPIDConfig->fIError   = 0;
pxPIDConfig->ulDeltaTime       = ulDeltaTime;
pxPIDConfig->bSaturationEnable = bIsSaturationEnable;
pxPIDConfig->bAntiWindupEnable = bIsAntiWindupEnable;
pxPIDConfig->bNegativeErrorEmptyIError = bNegativeErrorEmptyIError; 

when the pointer is double do I have to use double '->'? This is very confusing for me.

Thank you all for the help

/***************** EDIT ************************************

My function is working right now, but I got told I need to use memory allocation since the size of my arrays varies according to the number of loops I want to implement.

Here are the parameters of my function :

eError_t eControlCascadeInit( uint8_t ucNumberOfLoops, ePIDType_t *pePIDType, xPIDConfig_t **pxPIDConfig, float *pfLowerLimit, float *pfUpperLimit, uint32_t *pulDeltaTime, \
                            eBool_t *pbIsSaturationEnable, eBool_t *pbIsAntiWindupEnable, eBool_t *pbNegativeErrorEmptyIError, \
                            float *pfPGain, float *pfIGain, float *pfDGain, float *pfCGain, uint32_t *pulITime, uint32_t *pulDTime )

They're all arrays of size ucNumberOfLoops. All of them are read-only arrays, except for the pxPIDConfig one that is write-only. The function initializes all the xPIDConfig_t present in the array with the parameters passed to the function through array.

array[ 0 ] contains the parameters for the first PID controller being initialized.

array[ 1 ] contains the parameters for the second PID controller being initialized and so on...

It's like that for all the parameters in the function.

Hope it makes my question more clear?

3 Answers

Here you have an example of how to use double-pointer, to change the pointer in the function:

void allocate(xPIDConfig_t **array, size_t size)
{
    *array = malloc(sizeof(**array) * size);

    /* some examples how to access the struct members vi double pointer -*

    (*array) -> ulDeltaTime = 100;
    (**array).ulDeltaTime = 100;
    (*(array + 5)) -> ulDeltaTime = 100;
    array[5] -> ulDeltaTime = 100;
    (*array[5]).ulDeltaTime = 100;

}

int main(void)
{
    xPIDConfig_t *array;

    allocate(&array, 100);
    printf("%s\n", array ? "success" : "failure");
    free(array);
}

Limited to addressing assumptions and questions regarding your title question:

"How to use double pointers (pointer to pointer) for an array of structures properly in standard C"

First, just because the function argument might have a double pointer (i.e. xPIDConfig_t **pxPIDConfig) does not mean that the variable need to be allocated memory with a double pointer, i.e. if the function eg is called like this: funcChangeParam(&pxPIDConfig); this often means that the object being passed needs to be changed in some way, requiring that the address of be passed, not the object itself. Also, if the object itself is a pointer, (such as a pointer to several instances of a struct object.) then the function used to pass the object for modification will be prototyped with arguments such as void funcChangeParam(xPIDConfig_t **pxPIDConfig); (Note the double pointer here.)

So with this function prototype Making the allocation of memory look like this:

void funcChangeParam(xPIDConfig_t **pxPIDConfig);

//allocate memory for multiple instances of struct
xPIDConfig_t *pxPIDConfig = malloc(countOfInstances * sizeof(*pxPIDConfig);
if(pxPIDConfig)
{
    //use pxPIDConfig
    funcChangeParam(&pxPIDConfig);pass pointer to multiple instances of struct

And references to the object members inside the calling function could use the following notation. Eg:

//in a loop or other construct where i is defined from 0 to countOfInstances - 1
(*pxPIDConfig)[i].ePIDType = ePIDType;//modification of assignment per your example
//etc. 

//The following is a trivial example for illustration purposes.
//Code here uses a simplified struct, function 
//prototype, and simple calling example, the concept 
//of which easily translates to what you are
//asking about.  

typedef struct {
    int num;
}test_s;

void change(test_s **new);    

int main(){
    
    test_s *test = malloc(10*sizeof *test);
    
    change(&test);
    return 0;
}

void change(test_s **new)
{
    for(int i=0;i<10;i++)
    {
       (*new)[i].num = (i+1)*3; //init all instances to some value 
    }
}

You would only need a double pointer if the function reallocates the array to a different size. If the size isn't changing, you can just pass a pointer to (usually the first) element of the array, along with any size or index required by the function. For example:

extern void frobPidConfig(xPIDConfig_t *);

// 'frob' the xPIDConfig_t array elements from index a to b
void frobSomePidConfigs(xPIDConfig_t *pidconfigs, unsigned int a, unsigned int b)
{
    unsigned int i;
    for (i = a; i <= b; i++)
    {
        frobPidConfig(&pidConfigs[i]);
        // Example of member access:
        pidConfigs[i].ulDeltaTime = 42;
    }
}

Example of calling code:

    xPIDConfig_t *pidConfigs;
    unsigned int n = 10; // or whatever...
    pidConfigs = calloc(sizeof *pidConfigs, n);
    if (!pidConfigs)
    {
        // Allocation error
        exit(1);
    }
    /* ... */
    frobSomePidConfigs(pidConfigs, 2, 5);

On the other hand, if the function needs to reallocate the array and initialize any new elements, it could be done using a double pointer like this:

extern void initPidConfig(xPIDConfig_t *);

void reallocPidConfigs(xPIDConfig_t **pidConfigs, unsigned int oldSize, unsigned int newSize)
{
    unsigned int i;
    // Reallocate to new size
    xPIDConfig_t *realloced = realloc(*pidConfigs, sizeof **pidConfigs * newSize);
    if (newSize && !realloced)
    {
        // allocation error
        exit(EXIT_FAILURE);
    }
    *pidConfigs = realloced;
    // Initialize any additional elements
    for (i = oldSize; i < newSize; i++)
    {
        initPidConfig(*pidConfigs + i); // or: initPidConfig(&(*pidConfigs)[i]);
        // Examples of member access:
        (*pidConfigs)[i].bSaturationEnable = true;
        (*pidConfigs + i)->bAntiWindupEnable = true;
    }
}

Example of calling code:

    xPIDConfig_t *pidConfigs = NULL;
    // Note: realloc of the NULL pointer in *pidConfigs is OK.
    reallocPidConfigs(&pidConfigs, 0, 10);
    frobSomePidConfigs(pidConfigs, 2, 5);
Related