在C语言中为结构体指针数组成员分配地址

3

我在指针算术方面遇到了一些麻烦。我认为我理解了概念(指针变量指向内存地址,普通变量指向数据),但我相信我的问题出在语法上(*、&、(*)、*() 等)。

我的目标是构建自定义结构的动态数组(即指向堆结构的指针数组),我的接口提供了两种方法:"ad_to_obj_array"(将要添加的对象和可以为空的数组)和 "obj_array_dustbin"(仅需要处理的数组,同时处理内容,即堆对象)。前者如下所示:

对象的细节并不重要(而且结构已经被重新命名),但是我对一般问题的解决方案如下,如果您能发现错误,我将不胜感激。编译器在我尝试将 RHS 上指针中的地址分配给指向堆结构的指针数组中的指针值时抱怨无效的 lvalue:

#define NUM_ELEM(x) (sizeof (x) / sizeof (*(x)))

obj* add_to_obj_array(obj* new_obj, obj* array)
{
  int number_of_elements = 0;
  if (array != NULL)
  {
    number_of_elements = NUM_ELEM(array);
  }

  obj* new_array = NULL;

  /* note: I am expecting sizeof(new_obj) to return the size of an obj* 
     to go into the array of pointers. */
  if ( NULL ==
       (new_array = (obj*)malloc((number_of_elements + 1)* sizeof(new_obj))) )
  {
    /* memory request refused :( */
    return NULL;
  }

  /* copy the old array pointers into the new array's pointer slots: */
  int i;
  for (i = 0; i < number_of_elements; i++)
  {
    &(new_array[i]) = &(array[i]);
  }

  /* add the new item to the end (assign pointer value directly): */
  new_array[number_of_elements] = new_obj;

  if (number_of_elements > 0)
  {
    free(&array);
  }

  return new_array;
}

现在,我已经尝试了以下有问题行的排列方式:

  &(new_array[i]) = &(array[i]);
  *(new_array[i]) = &(array[i]);
  new_array[i] = &(array[i]);

所有这些代码都会产生编译错误。我相当确定右边是旧数组的第i个元素的地址,但是如果数组的元素是指向结构体的指针,那么如何将其赋值给新数组的第i个元素呢?

编辑 - 请注意,上面的NUM_ELEM宏不起作用;它总是返回1。请参见@Merlyn Morgan-Graham下面的答案,了解原因。

4个回答

9
根据您的描述,您开始的方式是错误的,因此当您开始复制内容时,您所做的任何事情都可能不起作用。
现在,您已将new_array(和presumably、array)定义为指向obj的指针。结果如下所示:
在这种情况下,您有一个指向动态分配对象数组的指针。如果您扩展了分配,您将需要复制所有对象本身。
根据您的描述: "(即堆结构体的指针数组)", 您想要一个指针数组。如果您想自动分配该指针数组,您的定义将如下所示:
obj *array[NUMBER];

我猜您不想这样。也许,您想要动态分配该数组。如下所示:
```c++ obj **new_array; new_array = new obj*[size]; for(int i = 0; i < size; ++i) new_array[i] = new obj; ```
在这种情况下,`new_array`和`array`都需要定义为指向`obj`的指针。然后,您将分配指针数组(即,指向您想要的多个`obj`的指针),并使每个指向一个`obj`:
obj **new_array;

// allocate an array of pointers with space to point at more items:    
new_array = malloc(sizeof(obj *) * new_elements);

// copy the pointers to the current items to the new array:
for (i=0; i<current_elements; i++)
    new_array[i] = array[i];

这样做的优点是,当您进行复制时,只复制指针,而不是对象本身。特别是对于大型对象,这可以节省大量的工作。折衷之处在于,使用元素需要通过两个间接级别,而不是一个,因此引用可能会变慢(尽管很少会慢得多,特别是在相对高性能的处理器上)。
正如@rerun已经指出的那样,在任何情况下,您都可能想要使用realloc。特别是,这可能能够“原地”扩展分配,并避免频繁复制数据。当然,这并不是保证的,但至少您有机会;如果每次使用malloc和copy,您甚至消除了那种优化的可能性。

1
+1,你的回答有图片。我猜我现在停止用(仅仅)文字来回答了。 :D - Jeff Mercado
1
+1 为时髦的图表。 "点点" 是一个奇怪的说法,即使它是准确的 :) - Merlyn Morgan-Graham
如果我有足够的声望来点赞,我会的 :) 谢谢,这一下子就抓住了重点。我曾经认为指针就是指针,你可以在它们里面放置任何旧地址,忘记了目标类型的重要点!所以一个 obj** 就是它了。我还会看一下 realloc():听起来它对于小型指针数组的优化会相当频繁。谢谢! - tehwalrus
@tehwalrus:void*是一种“任意指针”类型。指针的大小都相同,你可以将它们强制转换,但最好通过良好的接口记录你的意图,而不是在实现中进行(令人困惑的)强制转换来简化代码。 - Merlyn Morgan-Graham

2

你有两个数组,new_array[i] = array[i]并不能满足你的需求。

  • 你是否考虑过使用realloc作为可能的解决方案。

如果数组类型相同,那么这应该总是有效的。这假定您想要进行指针的浅复制。如果您想要深度复制(复制底层池),则需要执行更多操作,而不仅仅是跨越分配指针。 - Merlyn Morgan-Graham
这是一个 obj 数组,而不是 obj *。我认为这不是tehwalrus的意图。 - Jeff Mercado
@Jeff M 是的,它是一个结构体数组,但如果他想要一个浅拷贝,那应该可以工作。 - rerun
最终问题在于数组类型与描述不符。你的修改修复了这一行,但并未解决真正的问题。我认为Jerry很好地解决了tehwalrus的实际意图。 - Jeff Mercado
谢谢,看起来realloc会带来一些进一步的优化。向你致敬! :) - tehwalrus

0

只需将值分配过去。new_array[i] = array[i]

你可能遇到的问题是,对于obj*实际上是一个指向指针数组,obj本身必须是指针类型:

typedef struct
{
  int value1;
} obj_pool;

typedef obj_pool* obj;

int main(int argc, char* argv[])
{
  obj_pool pool1;
  pool1.value1 = 5;
  obj array[] = { &pool1 };
  array[0]->value1 = 16;
  return 0;
}

一旦您编译成功,您将遇到另一个问题是sizeof(array) == sizeof(obj*)NUM_ELEM(array)将始终返回相同的值。这意味着您必须向函数传递size_t array_size参数。


顺便说一句,我不建议你使用这个typedef = P,我建议您使用obj **,因为这是常见的C语言用法。可以看一下Jerry的回答,他的图很好理解。 - Merlyn Morgan-Graham
为什么NUM_ELEM会失败?(我只是从网上找到的,而且我还没有测试过..)当您调用sizeof时,如果obj* blah是通过malloc分配为数组指针,则它返回分配的内存量,而不是指针本身的大小,这样肯定没问题吧?如果不是,我将需要使用持久变量(在C中为静态变量?)来跟踪数组大小,这会使代码变得混乱。 - tehwalrus
看起来你是正确的,NUM_ELEM完全无法工作。我必须手动跟踪(咕哝咕哝)。 - tehwalrus
@tehwalrus:这个概念不是我理想的链接,但是 - https://dev59.com/9nI-5IYBdhLWcg3wMFS0。基本上,当数组传递给函数时,它会衰变为指针。处理这种情况的方法有:1.同时传递数组和大小参数。2.在数组中设置一个哨兵值(例如,数组的最后一个元素必须始终设置为“NULL”,类似于将空终止字符数组用作字符串)。3.传递指向数组第一个元素的指针和指向最后一个元素之后的指针,并在循环中使用`current != end`。 - Merlyn Morgan-Graham
谢谢,很有意思的观点。我实际上构建了一个新的结构体 obj_array,包含一个 obj **“数组”和一个 int “计数器”;然后只需要这个方法接收 obj_array * 并在结束时增加计数器成员。如果我能将其模板化/泛型化,我会在某个地方发布它,还可能编写一个 Ruby 脚本,基于头文件生成“数组”结构体和方法。我们会看到我在项目结束时还剩下多少时间! :) - tehwalrus
显示剩余2条评论

0

在您的代码中,数组元素不是指向结构体的指针,而是结构体对象。这个数组 obj** 的元素是指向结构体 obj 的指针。

#define NUM_ELEM(x) (sizeof (x) / sizeof (*(x)))

void add_to_obj_array(obj* new_obj, obj** array)
{
  int number_of_elements = 0;
  if (array != NULL)
  {
    number_of_elements = NUM_ELEM(array);
  }

  // expand array with one more item
  array = (obj**)realloc(array, (number_of_elements + 1) * sizeof(new_obj));

  if (array == NULL )
  {
    /* memory request refused :( */
    return;
  }

  // Put new item at the last place on the array
  array[number_of_elements] = new_obj;
}

所以在这里,我们使用了矩阵(指向 obj 结构的指针)。当我们添加新元素时,我们只需将现有数组扩展一个位置,并在该位置放置新的结构对象。不需要返回值,因为我们操作的是对象的指针,所有更改都是在实际对象上进行的,而不是在它们的副本上。


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