未知大小的结构体数组中包含结构体。

22

我一整天都在努力理解这个问题...

基本上,我有一个名为“State”的结构体,它包含一个名称,还有另一个名为“StateMachine”的结构体,它包含一个名称、一组状态和添加的总状态数:

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

typedef struct State {
  const char * name;

} State;

typedef struct StateMachine {
  const char * name;

  int total_states;
  State ** states;

} StateMachine;

StateMachine * create_state_machine(const char* name) {
  StateMachine * temp;

  temp = malloc(sizeof(struct StateMachine));

  if (temp == NULL) {
    exit(127);
  }

  temp->name = name;
  temp->total_states = 0;

  temp->states = malloc(sizeof(struct State));
  return temp;
}

void destroy_state_machine(StateMachine* state_machine) {
  free(state_machine);
}

State * add_state(StateMachine* state_machine, const char* name) {
  State * temp;

  temp = malloc(sizeof(struct State));

  if (temp == NULL) {
    exit(127);
  }

  temp->name = name;

  state_machine->states[state_machine->total_states]= temp;
  state_machine->total_states++;

  return temp;
}

int main(int argc, char **argv) {

  StateMachine * state_machine;

  State * init;
  State * foo;
  State * bar;

  state_machine = create_state_machine("My State Machine");

  init = add_state(state_machine, "Init");
  foo  = add_state(state_machine, "Foo");
  bar  = add_state(state_machine, "Bar");

  int i = 0;

  for(i; i< state_machine->total_states; i++) {
    printf("--> [%d] state: %s\n", i, state_machine->states[i]->name);
  }

}
由于某些原因(阅读的 C-fu / 多年使用 Ruby/Python/PHP),我无法表达 states 是 State(s)数组的事实。以上代码输出:

出现问题了,我不太懂如何将 states 表示为 State(s)数组。上述代码输出:

--> [0] state: ~
--> [1] state: Foo
--> [2] state: Bar

第一个添加的状态发生了什么?

如果我在第一个状态添加时使用malloc分配状态数组(例如state_machine = malloc(sizeof(temp));),那么我可以得到第一个值但无法得到第二个值。

有什么建议吗?

这是一个关于C语言的问题。我正在使用gcc 4.2.1编译示例代码。

5个回答

13

看起来你在状态机中除了第一个状态之外没有为状态分配空间。

StateMachine * create_state_machine(const char* name) {
  StateMachine * temp;

  temp = malloc(sizeof(struct StateMachine));

  if (temp == NULL) {
    exit(127);
  }

  temp->name = name;
  temp->total_states = 0;

  temp->states = malloc(sizeof(struct State)); // This bit here only allocates space for 1.
  return temp;
}
你最好在状态机结构体中放一个固定大小的状态数组。如果不行,你就得重新分配内存并移动整个集合,或者分配块并跟踪当前长度,或者创建一个链接列表。
顺便说一句,init、foo和bar从未被使用。
编辑:我的建议看起来像这样:
#define MAX_STATES 128 // Pick something sensible.
typedef struct StateMachine {
  const char * name;
  int total_states;
  State *states[MAX_STATES];
} StateMachine;

1
如果我将其设置为NULL(因为在创建状态机时我不知道状态的大小),那么我该如何添加状态? - apann
1
@apann:你不需要知道状态的大小,只需要知道指向状态的指针的大小。在StateMachine中放置一个State *数组应该可以工作,只要确保不超出它的范围。 - nmichaels
1
谢谢,我本来想点赞的,但我没够足够的声望值 :) - apann
3
保持你提问的质量,你将不会在声望上遇到麻烦。这是我见过的最好的初次提问之一。 - nmichaels

12

看起来您想在每个状态机中拥有可变数量的状态,但是您分配内存的方式不正确。在create_state_machine函数中,这一行:

temp->states = malloc(sizeof(struct State));

分配一个单独的State对象,而不是指针数组(你正在使用它的方式)。

有两种方法可以更改此内容。

  1. states声明为State states[<some-fixed-size>];,但这样您永远不能拥有超过固定数量的状态。
  2. 添加另一个成员来指示为states分配了多少存储空间,以便您可以跟踪已使用的存储量(这就是total_states被使用的方式)。

后者将看起来像这样:

#include <stdlib.h>
#include <string.h>

typedef struct 
{
    const char *name;
} State;

typedef struct 
{
    const char *name;
    int total_states;
    int states_capacity;
    State *states;
} StateMachine;

StateMachine *create_state_machine(const char *name)
{
    StateMachine *temp = malloc(sizeof(StateMachine));
    memset(temp, 0, sizeof(*temp));

    temp->name = name;
    temp->states_capacity = 10;
    temp->states = malloc(sizeof(State) * temp->states_capacity);

    return temp;
}

State *add_state(StateMachine *machine, const char *name)
{
    if (machine->total_states == machine->states_capacity)
    {
        // could grow in any fashion.  here i double the size, could leave
        // half the memory wasted though.
        machine->states_capacity *= 2;

        machine->states = realloc(
            machine->states, 
            sizeof(State) * machine->states_capacity);
    }

    State *state = (machine->states + machine->total_states);
    state->name = name;

    machine->total_states++;

    return state;
}

3
谢谢!我看到这个有用,但是我不能选择超过一个答案 :) - apann

4
在你的add_state函数内部:
temp = malloc(sizeof(struct StateMachine)); 

应该是

temp = malloc(sizeof(struct State));

但是,即使更改了这个,我仍然能够得到正确的输出:
--> [0] state: Init
--> [1] state: Foo
--> [2] state: Bar

也许你的代码没有问题。我正在使用gcc版本4.4.3。

谢谢你发现了这个问题,我会编辑我的帖子。然而,它并没有解决问题(我得到了相同的结果)。 - apann
1
我想知道是否有编译器标志或其他东西,可以使其在4.2.1(Snow Leopard的默认编译器)下工作。 - apann

1
State ** states;

将创建一个状态数组的数组。

实话实说,我还没有完全阅读整个解决方案(得赶紧走),但你提到想要一个状态数组 - 你可能想做:

State* states

或者

State states[size];

这是我的建议,你可以考虑一下。但很可能不是你的问题,因为我没有完全阅读它 :p


1
我也尝试过,但是当我执行state_machine->states[state_machine->total_states]= temp;时,出现了错误:赋值时类型不兼容。 - apann

1

你犯了一个概念性错误:

State ** states;

确实,您可以将状态视为指向State对象的指针数组,但您只分配了一个状态的空间。 当您执行以下操作时:

state_machine->states[state_machine->total_states]= temp;

如果total_states大于零,那么你做错了什么,因为你指向的是未分配的内存段(我想知道为什么你没有得到SEGFAULT)。要以这种方式存储动态数量的State,你需要一个链表,或者每次添加状态时调用realloc(但这不是一个好主意)。你使用不同的malloc调用分配的内存不是连续的。

1
可能是由于状态非常小(每个只有1个单词),导致了段错误。如果C语言更加安全,它会检查这种情况。 - nmichaels
1
谢谢提供详细信息,现在我明白我做错了什么。 - apann

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