C通用链表

时间:2012-06-23 22:28:49

标签: c generics pointers linked-list malloc

我有一个通用链接列表,其中包含void *类型的数据我试图使用struct employee填充我的列表,最终我想破坏对象struct employee。

考虑这个通用的链表头文件(我用char *类型测试过它):

struct accListNode                 //the nodes of a linked-list for any data type
{
  void *data;                     //generic pointer to any data type
  struct accListNode *next;       //the next node in the list
};

struct accList                    //a linked-list consisting of accListNodes
{
  struct accListNode *head;
  struct accListNode *tail;
  int size;
};

void accList_allocate(struct accList *theList);           //allocate the accList and set to NULL
void appendToEnd(void *data, struct accList *theList);    //append data to the end of the accList
void removeData(void *data, struct accList *theList);         //removes data from accList
  --------------------------------------------------------------------------------------

考虑员工结构

struct employee 
{ 
   char name[20]; 
   float wageRate; 
} 

现在考虑将从main()调用的示例测试用例:

    void test2()
    {
      struct accList secondList;
      struct employee *emp = Malloc(sizeof(struct employee));
      emp->name = "Dan";
      emp->wageRate =.5;

      struct employee *emp2 = Malloc(sizeof(struct employee));
      emp2->name = "Stan";
      emp2->wageRate = .3;

      accList_allocate(&secondList);
      appendToEnd(emp, &secondList);
      appendToEnd(emp2, &secondList);

      printf("Employee: %s\n", ((struct employee*)secondList.head->data)->name);   //cast to type struct employee
      printf("Employee2: %s\n", ((struct employee*)secondList.tail->data)->name);  
    }

为什么我在下面发布的答案解决了我的问题?我相信它与指针和内存分配有关。我使用的函数Malloc()是一个自定义malloc,它检查返回的NULL。

以下是我的整个通用链接列表实现的链接:https://codereview.stackexchange.com/questions/13007/c-linked-list-implementation

5 个答案:

答案 0 :(得分:6)

问题在于这个accList_allocate()以及你对它的使用。

struct accList secondList;
accList_allocate(&secondList);

在原始test2()中,secondList是堆栈上的内存。 & secondList是指向该内存的指针。当你调用accList_allocate()时,指针的副本将指向堆栈内存。然后,Malloc()返回一块内存并将其分配给指针的副本,而不是原始的secondList。

退出后,secondList仍指向堆栈上未初始化的内存,因此对appendToEnd()的调用失败。

除了secondList恰好没有垃圾之外,答案也是如此。可能是偶然的,可能是通过编译器的设计。无论哪种方式,它都不是你应该依赖的东西。

或者:

struct accList *secondList = NULL;

accList_allocate(&secondList);

并更改accList_allocate()

accList_allocate(struct accList **theList) {
    *theList = Malloc(sizeof(struct accList));
    (*theList)->head = NULL;
    (*theList)->tail = NULL;
    (*theList)->size = 0;
}

OR

struct accList secondList;

accList_initialise(secondList);

将accList_allocate()更改为accList_initialise(),因为它没有分配

accList_initialise(struct accList *theList) {
    theList->head = NULL;
    theList->tail = NULL;
    theList->size = 0;
}

答案 1 :(得分:1)

我认为你的问题是:

  1. 您已在原始secondList函数中的堆栈上分配了test2
  2. 堆栈内存可能很脏,因此secondList需要初始化
  3. 您的accList_allocate函数会获取指向列表的指针,但会使用Malloc调用覆盖它。这意味着您传入的指针是从不初始化。
  4. test2尝试运行时,它会遇到错误的指针(因为内存未初始化)。
  5. main中分配它时它起作用的原因是你的C编译器可能在程序启动时将堆栈归零。当main在堆栈上分配变量时,该分配是持久的(直到程序结束),因此secondListmain中分配时,实际上并且意外地正确初始化了。{ / p>

    您当前的accList_allocate实际上并未初始化传入的指针,而其余代码将永远不会看到它使用Malloc分配的指针。为了解决您的问题,我将创建一个新函数:accList_initialize,其唯一的工作是初始化列表:

    void accList_initialize(struct accList* theList)
    {
        // NO malloc
       theList->head = NULL;
       theList->tail = NULL;
       theList->size = 0;
    }
    

    在原始accList_allocate函数中使用此代替test2。如果你真的想在堆上分配列表,那么你应该这样做(而不是将它与在堆栈上分配的结构混合)。让accList_allocate 返回指向已分配结构的指针:

    struct accList* accList_allocate(void)
    {
       struct accList* theList = Malloc( sizeof(struct accList) );
       accList_initialize(theList);
       return theList;
    }
    

答案 2 :(得分:0)

当我在main()中放置secondList的声明和分配,并将其余部分放在一个函数中时,它可以工作:

int main(int argc, char *argv[])
{
  struct accList secondList;
  accList_allocate(&secondList);

  test2(&secondList);

  return 0;
}

void test2(struct accList *secondList)
{
  struct employee *emp = Malloc(sizeof(struct employee));
  struct employee *emp2 = Malloc(sizeof(struct employee));

  strcpy(emp->name, "Dan");
  emp->wageRate = .5;
  appendToEnd(emp, secondList);

  strcpy(emp2->name, "Stan");
  emp2->wageRate = .3;
  appendToEnd(emp2, secondList);

  printf("Employee: %s\n", ((struct employee*)secondList->head->data)->name);   //cast to type struct employee
  printf("Employee2: %s\n", ((struct employee*)secondList->tail->data)->name);  
  removeData(emp, secondList);
  printf("Head: %s\n", ((struct employee*)secondList->head->data)->name);
}

struct employee
{
   char name[20];
   float wageRate;
}

以下是整个实施的链接。 https://codereview.stackexchange.com/questions/13007/c-linked-list-implementation

我觉得这与指针和分配有关。有人可以向我解释那部分吗?将问题中的代码与上面的代码进行比较。

谢谢!

答案 3 :(得分:0)

这可能取决于您的员工结构的设计方式,但您应该注意

strcpy(emp->name, "Dan");

emp->name = "Dan";

功能不同。特别是,后者可能是总线错误的来源,因为您通常无法以这种方式写入字符串文字。特别是如果您的代码具有类似

的代码

name =“NONE”

等。

编辑:好的,所以对于员工结构的设计,问题是:

您无法分配数组。 C标准包括可修改的左值列表,而阵列不是其中之一。

char name[20];
name = "JAMES" //illegal

strcpy很好 - 它只是转到名称[0]取消引用的内存地址,并将“JAMES \ 0”复制到那里的内存中,一次一个字节。

答案 4 :(得分:0)

根据原始代码,我在这里看到错误的两件事,在上面的问题中,

您所看到的是未定义的行为,并且由此产生的是总线错误消息,因为您正在为变量分配字符串文字,而实际上您应该使用strcpy函数,您已经原则上编辑了你的原始代码所以......将来要记住的事情:)

单词Malloc的使用会引起混淆,特别是在同行评审中,评论者会有一个大脑屁并说“哇,这是什么,不应该是malloc?”并很可能提高它。 (基本上,不要调用具有与C标准库函数类似的声音名称的自定义函数)

您没有检查NULL,如果Malloc的加密版本失败,那么emp将会NULL怎么办?总是检查一下,无论多么微不足道或你的想法是“啊,这个平台上有大量内存,4GB RAM没问题,不会费心去检查NULL”

查看this question posted elsewhere来解释什么是总线错误。

编辑: 使用链表结构,如何调用函数中的参数对理解它至关重要。请注意& 的使用,意味着获取指向链接列表结构的变量的地址通过引用传递,而不是传递值,这是变量的副本。同样的规则也适用于指针的使用:)

如果您在参数列表中使用了双指针,那么您在问题的第一个代码中的参数略有不同,那么使用&secondList就可以了。