在C语言中如何在一个结构体内使用可变长度的结构体数组

4

你好,我正在尝试使用C语言实现一个简单的结构体:
有两个盒子,每个盒子里包含不同数量的粒子;粒子的确切数量在主函数中传递。

我编写了以下代码:

typedef struct Particle{
    float x;
    float y;
    float vx;
    float vy;
}Particle;

typedef struct Box{
    Particle p[];
}Box;

void make_box(Box *box, int number_of_particles);

int main(){
    Box b1, b2;
    make_box(&b1, 5);  //create a box containing 5 particles
    make_box(&b2, 10); //create a box containing 10 particles
}

我尝试使用以下代码实现make_box功能。
void make_box(struct Box *box, int no_of_particles){
    Particle po[no_of_particles];
    po[0].x = 1;
    po[1].x = 2;
    //so on and so forth...
    box->p = po;
}

总是提示“无效使用可变数组成员”。如果有人能给予一些指导,将不胜感激。


如果我没记错的话,C语言不允许您使用变量作为数组大小。 - Scott Smith
1
C99引入了一项称为可变长度数组或VLAs的功能。此外,C99还通过引入“灵活数组成员”来赋予了“结构体hack”的祝福。 - Alok Singhal
6个回答

2
void make_box(struct Box *box, int no_of_particles){
    Particle po[no_of_particles];
    //...
    box->p = po;
}

po是一个本地变量,其存储空间会自动分配在堆栈上;返回其地址是个坏主意。你应该从堆中分配内存,并记得在使用完后释放该内存:

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

typedef struct Particle_ {
    float x;
    float y;
    float vx;
    float vy;
} Particle;

typedef struct Box_ {
    Particle *p;
} Box;

void make_box(Box *box, int no_of_particles);

void make_box(Box *box, int no_of_particles){
    Particle *po = (Particle *) malloc ( no_of_particles*sizeof(Particle) );
    po[0].x = 1;
    po[1].y = 2;
    //so on and so forth...
    box->p = po;
}

void destroy_box(Box *box){
    free(box->p);
}


int main(){
    Box b1, b2;
    make_box(&b1, 5);  //create a box containing 5 particles
    make_box(&b2, 10); //create a box containing 10 particles

    // do the job...
    printf("box b1, point 0, x: %5.2f\n", b1.p[0].x);
    printf("box b2, point 1, y: %5.2f\n", b2.p[1].y);

    destroy_box(&b1);
    destroy_box(&b2);

    return 0;
}

谢谢,我一直是Java程序员,对C语言还不熟悉。内存分配的概念让我感到很困惑。请问在main()函数中,如何访问particle数组的每个元素?例如printf("%f", b1.p[1].x)。 - renz
@ Federico,你可能想尝试一下Boehem GC用于C或C++ http://www.hpl.hp.com/personal/Hans_Boehm/gc/ - philcolbourn

1

你不能将值赋给 Particle p[],请使用 Particle* p

当然,你需要在堆上分配你的 Particle 数组,而不是在栈上!否则,一旦你离开函数 make_box,它就会被销毁。

void make_box(struct Box *box, int no_of_particles){
    Particle* po = (Particle*)malloc(sizeof(Particle)*no_of_particles);
    for (int i = 0; i < no_of_particles; i++)
        po[0].x = i;
    //so on and so forth...
    box->p = po;
}

并且

struct Box
{
    Particle* p;
};

不要忘记在不再需要时释放内存。


1
你需要动态分配 struct Box
struct Box *box1 = malloc(sizeof *box1 +
                          sizeof (Particle[number_of_particles]));

for (size_t i=0; i < number_of_particles; ++i)
    box1->p[i] = po[i];
/* or memcpy(box1->p, po, sizeof po); */

但是,如果您正在执行上述操作,最好声明struct Box在其中具有指针。 如果您想要struct中的所有数据都是连续的,则灵活的数组成员“技巧”非常有用。 如果在struct中有一个Point *指针并动态分配了它,则struct和点的内存布局将不连续。

您收到错误的原因是在C中无法对数组进行赋值,而box->p是一个数组(来自n1124 6.7.2.1p16):

然而,当一个 . (或->)运算符具有左操作数(指向)带有柔性数组成员的结构体,并且右操作数命名该成员时,它的行为就像该成员被替换为最长的数组(具有相同的元素类型),不会使结构体比所访问的对象更大;即使这将与替换数组的偏移量不同,该数组的偏移量仍应保持为柔性数组成员的偏移量。如果此数组没有元素,则其行为就像它有一个元素,但如果尝试访问该元素或生成一个超过它的指针,则行为是未定义的。
您还需要在您的struct中至少有另一个成员,除了柔性数组成员。

1
如果您有一个带有可变数组成员的结构体,则无法直接创建该类型的对象,因为编译器不知道您想要它有多长。这就是您尝试使用此行执行的操作:
Box b1, b2;

这样的struct总是需要动态分配,这意味着您只能声明指向它们的指针。因此,您需要将该声明更改为:

Box *b1, *b2;

...并将您的 make_box() 函数更改为返回这样的指针:

Box *make_box(int no_of_particles)
{
    Box *box = malloc(sizeof *box + no_of_particles * sizeof box->p[0]);

    if (box)
    {
        box->p[0].x = 1;
        box->p[1].x = 2;
        //so on and so forth...
    }

    return box;
}

void destroy_box(Box *box){
    free(box);
}

int main()
{
    Box *b1 = make_box(5);  //create a box containing 5 particles
    Box *b2 = make_box(10); //create a box containing 10 particles

    // Test that b1 and b2 are not NULL, then do the job...

    destroy_box(b1);
    destroy_box(b2);

    return 0;
}

注意: 您还需要向Box结构体添加至少一个其他成员(no_of_particles似乎是一个不错的选择),因为灵活数组成员不能是结构体的唯一成员。


你的最后一条评论很有道理,但是你的make_box()函数的第一条指令对我来说没有任何意义。更好的做法是:Box box = malloc(sizeof Box); if(box) { box->nparticles = no_of_particles; box->p = malloc(no_of_particlessizeof(Particle)); if(box->p) { 等等...然后void destroy_box(Box *box){ if(box){ if(box->p) free(box->p); free(box);}} - Federico A. Ramponi
Federico:我正在使用的方法(也是OP所问的)利用了灵活数组成员,这允许整个东西在单个分配中分配。请注意,在struct Box中成员p的类型是不完整的数组类型,而不是指针。有关此内容的GCC文档在此处:http://www.delorie.com/gnu/docs/gcc/gcc_42.html,尽管请注意它*确实*是标准C。 - caf
哇!抱歉我之前的评论有点无聊,但是它确实有效。我的问题实际上是关于对“*box”和“box->p”的引用,以及在box本身的声明中使用它们。就此而言,即使像“int a = sizeof(a);”这样的声明也会非常可疑(但现在我看到它可以正确编译并执行预期的操作)。显然,我的C语言知识比我想象中更生疏和陈旧了。+1 - Federico A. Ramponi

0

如果你要有一个“灵活的数组”,它必须是结构体中的最后一个元素。这是因为编译器无法预测下一个元素的偏移量。此外,当分配结构体时,你需要为数组元素分配额外的内存。

历史注释: 过去我经常看到这样的做法...

struct a {
  int x, y, z;
  char name[1];
};

struct a *  ptr;

ptr = (struct a*) malloc (sizeof (a) + EXTRA SPACE FOR name);

or

ptr = (struct a*) buffer;   /* <buffer> would have been a passed character array */

上述代码将允许我们访问 [name] 的边界之外的内容。这是合法且有效的 C 代码,但潜在的危险在于您会有意超出 [name] 最初声明的边界。

在使用时请小心。


这是有效的C代码吗?char a[1] 可以转换为 char *a,但它们是不同的类型。如果结构体 *foo 包含一个字段 char a[1],那么标准是否会禁止编译器将 foo->a[i] 优化为 foo->a[0],因为数组的最高合法下标是声明大小和分配空间中较小的那个。如果 foo->a[i] 对于任何非零值的 i 都会导致未定义行为,那么这表明编译器有权假定 i 是零(否则,代码可以执行任何想要的操作,包括表现为 i 等于零)。 - supercat

0
在Box结构体中,为什么不使用指向Particle的指针,以便创建动态数组?

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接