C 函数内部结构下数组的动态分配

C dynamic allocation of an array under struct inside a function

提问人:Sangjun Lee 提问时间:1/23/2023 最后编辑:Vlad from MoscowSangjun Lee 更新时间:1/24/2023 访问量:155

问:

我有一个结构,它将包含一些动态分配的数组。

我已经编写了以下代码,它可以工作,但我不明白为什么它会起作用。

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

struct Tray {
  int *parr;
};

int allocateTray(int n, struct Tray *tray) {
  tray->parr = calloc(n, sizeof(*tray->parr));

  for (int i = 0; i<n; i++) {
    tray->parr[i] = i+1;
  }
}

int main() {
  struct Tray tray = {NULL}, *ptray;
  int         n;

  n = 5;

  ptray = &tray;

  allocateTray(n, ptray);

  for (int i = 0; i<n; i++) {
    printf("ptray->parr[%d] = %d \n", i, ptray->parr[i]);
  }

  return 0;
}

使用数组(不在结构体内),即使我在带有参数的函数中分配,它也不会给出分配的数组,并且会强制人们在那里使用双指针。arrint *arrmain

但在这种情况下,我只是使用指向结构的指针,它起作用了。我在想我应该使用类似双指针的东西到结构。

为什么在这种情况下,它只适用于单个指针?

C 引用 变量赋 按值传递 指针传递

评论

3赞 Gerhardh 1/23/2023
这是完全相同的机制。必须传递指向要更改的值的指针。无论是单个指针还是结构(恰好包含指针)
0赞 axiac 1/23/2023
尝试。在这种情况下,您不需要。allocateTray(n, &tray); for (int i = 0; i<n; i++) { printf("tray.parr[%d] = %d \n", i, tray.parr[i]); }ptray
0赞 ryyker 1/23/2023
...如果是 ,则初始值设定项将是struct Tray *trayNULL
0赞 William Pursell 1/23/2023
考虑。这里,是一个“双指针”,但不是“双指针”。double *dp; int **ippdpipp

答:

1赞 ryyker 1/23/2023 #1

“即使我在带有参数 int *arr 的函数中分配 arr,它也不会给 main 分配的数组”

通常,函数在通过其参数列表传递时需要修改的任何对象都需要传递对象的地址,而不是对象本身。举例来说,对于任何类型 T:

如果要改变,函数原型的参数应该是;
调用示例为
T ssvoid func(T *s);

T s = 0;
func(&s);

如果要改变,函数原型的参数应该是;
调用示例为
T *s*svoid func(T **s);

T *s = 0;
func(&s);

如果要改变,函数原型的参数应该是;
调用示例为
T **s**svoid func(T ***s);

T **s = 0;
func(&s);  

等等......(请注意,每个调用约定的明显相似性。

示例 - 以下代码将无法更改其参数的值:

int main(void)
{
    int x = 0;//object to be changed
    change_x(x);//passing object directly via argument
                //x is returned unchanged
    return 0;
}

void change_x(int x)
{
    x = 10;//within this function only will x now contain 10
}

但此示例传递 address 并能够更改值:

int main(void)
{
    int x = 0;//object to be changed
    change_x(&x);//passing the address of the object to be changed
    return 0;
}

void change_x(int *x)
{
    *x = 10;//access to the object via its address allows change to occur 
}

“我在想我应该使用类似双指针的东西来指向结构。”

是的,作为函数原型中的参数,当需要更改指针对象指向的内存内容时,该参数将起作用。
对于需要在函数中修改的指针(指向任何对象),也是如此,必须传递其地址,而不是指针本身。然后,这将要求该函数的参数容纳指向指针的指针。一个简单的例子,使用 with members 和 a member:
structintint *

typedef struct {
    int a;
    int b;
    int *parr;
}val_s;    

void change_val(val_s **v, size_t num_parr);

int main(void)
{
    val_s *val = NULL;
    int num = 10;
    change_val(&val, num);//passing address to a pointer
    val->a = 10;
    val->b = 20;
    for(int i = 0;i < num; i++) val->parr[i] = i;
    //once finished using memory, 
    //free it in the reverse order in which it was allocated
    free(val->parr);
    free(val);
    return 0;
}

void change_val(val_s **v, size_t num)//note only top level pointer address needs be send
{                                     //member pointers, whether allocated or not are 
    (*v) = malloc(sizeof(val_s));     //relative to memory of top level object
    if(*v)
    {
        (*v)->parr = malloc(num*sizeof (*v)->parr);//allocate memory to member pointer
    }
}

   
1赞 ikegami 1/23/2023 #2

你不需要“双指针”;您需要一个指向要修改的对象的指针。如果希望函数修改指针对象,则需要将指针传递到该对象。如果希望函数修改结构,则需要传递指向该对象的指针。

而这正是你的代码所做的。您要修改 的 ,因此将指针传递给 的 。所以你的代码是有效的。maintraymaintray

下面演示了这一点。


首先,让我们看看不起作用的代码。

void fac( int *ac ) {
   ac = malloc( sizeof( int ) );
}

int main( void ) {
   int *a = NULL;
   fac( a );
   printf( "%p\n", (void *)a );   // XXX `a` isn't modified.
}

此代码修改 ,但不修改 。它没有实现我们希望它实现的目标。要修改 ,我们需要传递 的地址。acaaa

void fap( int **ap ) {
   *ap = malloc( sizeof( int ) );
}

int main( void ) {
   int *a = NULL;
   fap( &a );
   printf( "%p\n", (void *)a );   // XXX `s` isn't modified.
}

在这里,我们不是修改参数,而是修改 .这段代码确实实现了我们希望它实现的目标。*apa


现在,让我们介绍结构。

typedef struct { int *a } S;

void fsc( S sc ) {
   sc.a = malloc( sizeof( int ) );
}

int main( void ) {
   S s = { .a = NULL };
   fsc( s );
   printf( "%p\n", (void *)(s.a) );   // Ok. `s` is modified.
}

此代码修改 ,但不修改 。它没有实现我们希望它实现的目标。要修改 ,我们需要传递 的地址。scsss

typedef struct { int *a } S;

void fsp( S *sp ) {
   sp->a = malloc( sizeof( int ) );   // `sp->a` means `(*sp).a`
}

int main( void ) {
   S s = { .a = NULL };
   fsp( &s );
   printf( "%p\n", (void *)(s.a) );   // Ok. `s` is modified.
}

在这里,我们不是修改参数,而是修改 .这段代码确实实现了我们希望它实现的目标。*sps


完整演示

#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>

typedef struct { int *a; } S;

void fac( int *ac ) {
   ac = malloc( sizeof( int ) );
}

void fap( int **ap ) {
   *ap = malloc( sizeof( int ) );
}

void fsc( S sc ) {
   sc.a = malloc( sizeof( int ) );
}

void fsp( S *sp ) {
   sp->a = malloc( sizeof( int ) );   // `sp->a` means `(*sp).a`
}

int main( void ) {
   int *a = NULL;
   fac( a );
   printf( "%p\n", (void *)a );       // XXX `a` isn't modified.

   fap( &a );
   printf( "%p\n", (void *)a );       // Ok. `a` is modified.

   S s = { .a = NULL };
   fsc( s );
   printf( "%p\n", (void *)(s.a) );   // XXX `a` isn't modified.

   fsp( &s );
   printf( "%p\n", (void *)(s.a) );   // Ok. `s` is modified.
}

警告:

<source>: In function 'fac':
<source>:10:16: warning: parameter 'ac' set but not used [-Wunused-but-set-parameter]
   10 | void fac( int *ac ) {
      |           ~~~~~^~
<source>: In function 'fsc':
<source>:18:13: warning: parameter 'sc' set but not used [-Wunused-but-set-parameter]
   18 | void fsc( S sc ) {
      |           ~~^~

输出:

(nil)
0x158a2d0
(nil)
0x158a310
1赞 Vlad from Moscow 1/23/2023 #3

要更改函数中的对象,您需要通过引用而不是按值传递它。

在 C 语言中,通过引用传递意味着通过指向对象的指针间接传递对象。因此,取消引用传递的指针,您将直接访问指针指向的对象并可以更改它。

使用数组(不在结构体内),即使我在 带有参数 int *arr 的函数,它不会给 main 分配的 数组,它迫使人们在那里使用双指针。

这意味着指针按值传递给函数。也就是说,该函数处理其局部变量(参数),该变量由传递的指针的值初始化。更改局部变量不会更改用作函数参数的原始指针。

考虑一个简单的程序

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

void f( int *p )
{
    p = malloc( sizeof( int ) );
}

int main( void )
{
    int *a = NULL;

    f( a );

    printf( "a == NULL is %s\n", a == NULL ? "true" : "false" );
}

您可以按如下方式想象函数定义及其调用

f( a );

//...

void f( /* int *p */ )
{
    int *p = a;
    p = malloc( sizeof( int ) );
}

如您所见,该函数更改了其局部变量,该变量由传递给该函数的指针的值初始化。原始指针保持不变。paa

如果要更改函数中 main 中声明的指针,则需要通过引用传递它。也就是说,程序将如下所示a

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

void f( int **p )
{
    *p = malloc( sizeof( int ) );
}

int main( void )
{
    int *a = NULL;

    f( &a );

    printf( "a == NULL is %s\n", a == NULL ? "true" : "false" );

    free( a );
}

现在取消引用函数中的指针p

*p = malloc( sizeof( int ) );

您可以直接访问原始指针,因此可以更改它。a

至于你的第一个程序,那么指针在结构中声明parrTray

struct Tray {
  int *parr;
};

通过指向结构类型的对象的指针通过引用传递给函数。

  struct Tray tray = {NULL}, *ptray;
  //...
  ptray = &tray;

  allocateTray(n, ptray);

也就是说,取消引用函数中的指针,您可以直接访问结构类型的原始对象,并可以更改(任何)其数据成员。ptray

1赞 Ale 1/24/2023 #4

Gerhardh 的评论是正确的:您正在使用双指针。

考虑:

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

struct Tray {
  int *parr;
};

void allocateTray(int n, struct Tray *tray) {
  tray->parr = calloc(n, sizeof(*tray->parr));

  for (int i = 0; i<n; i++) {
    tray->parr[i] = i+1;
  }
}

int main() {
  int *tray = NULL, **ptray;
  int         n;

  n = 5;

  ptray = &tray;

  allocateTray(n, (struct Tray*)ptray);

  for (int i = 0; i<n; i++) {
    printf("ptray[%d] = %d \n", i, (*ptray)[i]);
  }

  return 0;
}