【问题标题】:How to use double pointers (pointer to pointer) for an array of structures properly in standard C?如何在标准C中正确使用双指针(指向指针的指针)作为结构数组?
【发布时间】:2021-08-25 16:33:32
【问题描述】:

我有一个结构数组作为函数参数,并且数组的大小是动态的。我的同事说我必须使用双指针,因为结构数组中包含的值将被覆盖。

将成为双指针的参数如下:

xPIDConfig_t **pxPIDConfig

这是 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;

pxPIDConfig 数组的大小会有所不同。

但我不确定如何分配双指针,甚至不知道如何使用包含双指针的函数。

我只是想知道是否有人有一个很好的代码示例,说明如何使用具有可变大小的双指针数组的函数?以及如何在函数内部正确更改数组本身包含的值?

现在这就是我在函数中更改值的方式:

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

当指针是双倍时,我必须使用双'->'吗?这让我很困惑。

谢谢大家的帮助

/********************** 编辑 **************************** ********

我的函数现在正在运行,但我被告知我需要使用内存分配,因为我的数组大小会根据我想要实现的循环数而变化。

这是我的函数的参数:

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 )

它们都是大小为 ucNumberOfLoops 的数组。它们都是只读数组,除了 pxPIDConfig 一个是只写的。该函数使用通过数组传递给函数的参数初始化数组中存在的所有xPIDConfig_t。

array[ 0 ] 包含正在初始化的第一个 PID 控制器的参数。

array[1] 包含正在初始化的第二个 PID 控制器的参数,依此类推...

函数中的所有参数都是这样的。

希望它能让我的问题更清楚吗?

【问题讨论】:

  • 如果被调用函数将修改指针值,例如通过(重新)为数据结构分配内存或通过更改指针以指向结构的不同实例,您需要一个双指针。如果您只想修改结构中的值,则不需要它。请edit您的问题并解释您的函数应该对结构和指针做什么。您想在哪里分配/更改/释放结构或数组的内存,可能使用伪代码。
  • 仅仅因为函数参数可能有一个双指针(即**name)并不意味着该变量需要使用双指针分配内存,即如果函数 eg 是这样调用的: void funcChangeParam(&pxPIDConfig) 它通常会被原型化为`void funcChangeParam(xPIDConfig_t **pxPIDConfig)
  • 对于xPIDConfig_t **pxPIDConfig,那么如果pxPIDConfig 指向一个有效的xPIDConfig_t * 并且*pxPIDConfig 指向一个有效的xPIDConfig_t,那么您可以使用pxPIDConfig 来访问成员其中xPIDConfig_t 像这样:(*pxPIDConfig)->ePIDType
  • 如果函数改变数组的大小(即重新分配),你只需要一个双指针。
  • @IanAbbott - OP 表示需要多个 struct 实例,因此必须定义为指向 struct 的指针,然后在函数原型中,需要一个双指针来允许更改对象。

标签: c pointers struct double structure


【解决方案1】:

这里有一个例子,说明如何使用双指针来改变函数中的指针:

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

【讨论】:

  • 它也确实需要一个成员访问示例,因为这是 OP 似乎正在努力解决的问题之一。
  • 如果我想更改 xPIDConfig_t 结构中的一个字段的值,它是否与它是单个指针时相同?数组->ePIDType = newValue;
  • @Lexie-fox 添加了一些示例
  • array[5](array + 5) 在这些示例中不正确。内存是xPIDConfig_t 的数组,而不是xPIDConfig_t* 的数组。
【解决方案2】:

如果函数将数组重新分配为不同的大小,您只需要一个双指针。如果大小没有改变,你可以只传递一个指向数组的(通常是第一个)元素的指针,以及函数所需的任何大小或索引。例如:

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

调用代码示例:

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

另一方面,如果函数需要重新分配数组并初始化任何新元素,可以使用这样的双指针来完成:

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

调用代码示例:

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

【讨论】:

  • 大小将保持不变,这是否意味着我不必使用双指针?将 PID 设置为具有 3 个回路后,它将保持 3 个回路 PID。如果我们想改变大小,那么我们删除旧的并使用 init 函数创建一个新的。
  • @Lexie-fox 正确。在这种情况下,您不需要双指针。
  • 应该把(*pidConfigs + i)-&gt;bAntiWindupEnable = true;写成:*(pidConfigs + i)-&gt;bAntiWindupEnable = true;
  • @ryyker 编号*pidConfigs 指向重新分配的数组的第一个(第零个)元素,(*pidConfigs + i) 指向第i 个元素(从0 开始计数)。
  • OP 表示需要多个 struct 实例,建议将其定义为数组或指针。如果是指针,则需要为其分配内存。然后,如果在函数调用中需要更改对象的任何内容,则需要使用指针的地址对其进行原型化,即两个指针。
【解决方案3】:

仅限于解决有关您的标题问题的假设和问题:

“如何在标准 C 中正确地为结构数组使用双指针(指向指针的指针)”

首先,仅仅因为函数参数可能有一个双指针(即xPIDConfig_t **pxPIDConfig)并不意味着该变量需要使用双指针分配内存,即如果函数eg被这样调用:funcChangeParam(&amp;pxPIDConfig);这通常意味着被传递的对象需要以某种方式进行更改,需要传递 的地址,而不是对象本身。此外,如果对象本身是一个指针(例如指向 struct 对象的多个实例的指针。)那么用于传递对象以进行修改的函数将使用诸如 void funcChangeParam(xPIDConfig_t **pxPIDConfig); 之类的参数进行原型化(注意双精度指针。)

所以用这个函数原型使内存分配看起来像这样:

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

调用函数内部对对象成员的引用可以使用以下符号。例如:

//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 
    }
}

【讨论】:

  • 只有 pxPIDConfig 中的数据会改变,我有一个函数来初始化我的 PIDConfig 结构,还有一个函数来计算我的 PID 控制器(我在 HVAC 中工作)。我只为结构的字段分配不同的值。所以我还是想知道我现在是否真的需要使用双指针
  • 是的,如果函数需要改变结构成员的值,那么结构的地址需要改变,所以你定义它的方式,它会需要传递一个指针,也许不是双指针。您想要多个结构实例,还是只需要一个?
  • 既然它将是该结构的数组,我假设它不止一个实例?
  • 那么是的,一个指向分配内存的指针,如上面的示例代码所示,但函数原型需要使用两个指针来允许更改对象。
  • 我在我的帖子中添加了关于函数作用的精确度,希望它有所帮助。我用一个指针没有问题,但是这个双指针的东西让我头晕哈哈:')
猜你喜欢
  • 2017-06-03
  • 2019-01-18
  • 2010-09-25
  • 1970-01-01
  • 2011-11-30
  • 2021-07-14
  • 1970-01-01
  • 2021-05-29
相关资源
最近更新 更多