【问题标题】:Copy with string to linked list doesn't work用字符串复制到链表不起作用
【发布时间】:2019-10-30 07:34:39
【问题描述】:

我的任务是将一些输入复制到链接列表,但是当我尝试使用 strncpy 复制它时,它不起作用并且出现错误

Exception thrown at 0x0F4C0E15 (ucrtbased.dll) in 
ProjectA.exe: 0xC0000005: Access violation writing location 0xCDCDCDCD.

代码:

typedef struct Frame
{
    char*       name;
    unsigned int    duration;
    char*       path;  
} Frame;

typedef struct FrameNode
{
    Frame* frame;
    struct FrameNode* next;
} FrameNode;

FrameNode* createframe(char name[], char path[], int duration)
{
    Frame* list = (Frame*)malloc(sizeof(Frame));
    strncpy((list->name), name, STR_LEN);
    list->duration = duration;
    strncpy(list->path, path, STR_LEN);
    FrameNode* frame = list;
    frame->next = NULL;
    return list;
}

【问题讨论】:

  • malloc() 之后,list->name 是一个未初始化的指针。您需要为字符串分配足够的空间,然后复制到该空间中。与path 类似。不要忘记在字符串末尾允许空字节。
  • 你可以用代码告诉我吗?我不明白你提供给我什么
  • malloc(sizeof(Frame));Frame 分配存储空间,但它不会为要存储在组件namepath 中的字符串分配存储空间。请注意,这些是指针而不是数组。因此,strncpy((list->name), name, STR_LEN); 是 U.B.您既没有初始化组件list->name,也没有为复制到其中的数据分配存储空间。 list->path 也是如此。
  • 一种解决方案可能是将Frame 更改为struct Frame { char name[STR_LEN]; unsigned int duration; char path[STR_LEN]; };,假设STR_LEN 表示足够的常数。另一种解决方案是使用malloc(STR_LEN) 初始化list->namelist->path

标签: c string memory-management linked-list singly-linked-list


【解决方案1】:

对于初学者来说,函数应该声明为

FrameNode * createframe( const char name[], const char path[], int duration );

因为namepath 在函数中都没有改变。

您没有为list->namelist->pathframe 分配内存。

此外,该函数返回的不是FrameNode * 类型的frame,而是Frame * 类型的list

当字符数组被动态分配时,命名常量 STR_LEN 没有多大意义,例如结构 Frame 的数据成员 namepath

您首先应该为FrameNode 类型的对象动态分配内存。

那么您应该为Frame 类型的对象及其数据成员namepath 分配内存。

因此函数定义可以如下所示。

FrameNode * createframe( const char name[], const char path[], int duration )
{
    FrameNode *frame = malloc( sizeof( FrameNode ) );

    if ( frame != NULL )
    {
        char *name_data = NULL;
        char *path_data = NULL;

        size_t n = strlen( name );

        name_data = malloc( n + 1 );

        if ( name_data != NULL ) strcpy( name_data, name );

        if ( name_data != NULL )
        {
            n = strlen( path );

            path_data = malloc( n + 1 );

            if ( path_data != NULL ) strcpy( path_data, path );   
        }

        Frame *list = NULL;

        if ( name_data != NULL && path_data != NULL )
        {
            list = malloc( sizeof( Frame ) );

            if ( list != NULL )
            {
                list->name     = name_data;
                list->duration = duration;
                list->path     = path_data;
            }
        }

        if ( list == NULL )
        {
            free( name_data );
            free( path_data );
            free( frame );
        }
        else
        {
            frame->frame = list;
            frame->next  = NULL; 
        }
    }        

    return frame;
}

如果你确实需要限制动态分配字符串的长度,那么这些语句

size_t n = strlen( name );

name_data = malloc( n + 1 );

if ( name_data != NULL ) strcpy( name_data, name );

n = strlen( path );

path_data = malloc( n + 1 );

if ( path_data != NULL ) strcpy( path_data, path );   

应该替换为

name_data = malloc( STR_LEN );

if ( name_data != NULL ) 
{
    strncpy( name_data, name, STR_LEN );
    name_data[STR_LEN - 1] = '\0';
}           

path_data = malloc( STR_LEN );

if ( path_data != NULL ) 
{
    strncpy( path_data, path, STR_LEN );   
    path_data[STR_LEN - 1] = '\0';
}               

否则将出现与存储数据相关的不一致:一些节点将存储字符串,而其他节点将包含非字符串。

【讨论】:

    【解决方案2】:

    在将字符串复制到目的地之前,您需要使用malloc 分配空间。第一个 malloc 只为Frame 分配空间,但不为其内部的char * 分配空间。

    createframe中的代码应该是:

        Frame* list = malloc(sizeof(Frame));
        list->name = malloc(STR_LEN);
        strncpy((list->name), name, STR_LEN);
        list->duration = duration;
        list->path= malloc(STR_LEN);
        strncpy(list->path, path, STR_LEN);
        //FrameNode* frame = list; // <- nope. FrameNode* should point to a FrameNode not a Frame
        FrameNode* frame = malloc(sizeof(FrameNode)); 
        frame->frame = list;
        frame->next = NULL;
        return frame;
    

    在使用动态分配的变量之前检查malloc 是否已成功也很好,如下所示:

     Frame *list = malloc(sizeof(Frame));
     if(list==NULL){
         perror("problem allocating frame");
         return NULL;
     }
     list->name = malloc(STR_LEN);
     if(list->name==NULL){
         free(list);//free the already allocated memory
         perror("error message");
         return NULL;
      }
     strncpy((list->name), name, STR_LEN);
     ...
     return frame;
     }
    

    createframe返回时,你应该检查它是否已经返回NULL,如果它返回NULL来处理错误,通常通过释放分配的内存并终止程序。


    You should not cast the result of malloc

    【讨论】:

    • 我不知道为什么 OP 不简单地将 char name[STR_LEN]char path[STR_LEN] 存储在 struct Frame 中。恕我直言,这会简化事情......
    • 请注意,FrameNode *frame = list; 是类型不匹配。这可能不是预期的。您可能需要另一个malloc() 和错误检查,然后返回frame 而不是list(否则会出现另一种类型不匹配)。
    • @Scheff 我认为他刚开始使用C
    • @JonathanLeffler 是的
    【解决方案3】:

    基本诊断及处方

    malloc() 之后,list-&gt;name 是一个未初始化的指针。您需要为字符串分配足够的空间,然后复制到该空间中。与path 类似。不要忘记在字符串末尾允许空字节。您没有为FrameNode 分配空间;你也不退货。

    FrameNode *createframe(char name[], char path[], int duration)
    {
        Frame *list = (Frame*)malloc(sizeof(*list));
        size_t name_len = strlen(name) + 1;
        char  *name_cpy = malloc(name_len); 
        size_t path_len = strlen(path) + 1;
        char  *path_cpy = malloc(path_len);
        FrameNode *frame = malloc(sizeof(*frame));
        if (list == NULL || name_cpy == NULL || path_cpy == NULL || frame == NULL)
        {
            free(name_cpy);
            free(path_cpy);
            free(frame);
            free(list);
            return NULL;
        }
        list->duration = duration;
        memmove(path_cpy, path, path_len);
        memmove(name_cpy, name, name_len);
        list->name = name_cpy;
        list->path = path_cpy;
        frame->frame = list;
        frame->next = NULL;
        return frame;
    }
    

    里面有很多修复。

    • 它为FrameFrameNode 分配空间。
    • 代码检查分配失败。
    • 它会在检查失败之前尝试分配所有内存,从而简化错误处理。只有一个错误返回。如果第一次分配失败,其余的可能也会失败,将变量初始化为NULL,可以安全地传递给free()
    • 它计算字符串的长度。
    • 它为字符串及其空终止符分配空间。
    • 由于它知道字符串的长度,它可以使用memmove()(或memcpy())来复制数据。
    • 它使用sizeof(*variable) 表示法而不是sizeof(VariableType)
    • 它返回 FrameNode * 而不是 Frame *
    • 它避免使用strncpy(),因为这不能保证复制的字符串是空终止的,这会导致其他地方出现问题。
    • 它不关注STR_LEN — 不清楚它有什么价值。

    结构的替代设计

    如果您对namepath 的大小确实有一个固定的上限,那么您最好使用如下结构:

    typedef struct Frame
    {
        unsigned int    duration;
        char            name[STR_LEN + 1];
        char            path[STR_LEN + 1];  
    } Frame;
    
    • 使用固定大小的成员来保存分配。
    • 允许长度为STR_LEN 的字符串加上空终止符。
    • 无论STR_LEN 的值如何,将字符数组放在结构的末尾以最小化填充。
    • 然后您需要使用 strncpy() 并确保设置了 list-&gt;name[STR_LEN] = '\0';list-&gt;path[STR_LEN] = '\0'; — 这就是为什么在成员定义中有 + 1

    这种变化将分配数量从 4 减少到 2。您甚至可以在 Frame 结构中包含 next 指针并完全取消 FrameNode 结构 — 再次减少所需的内存管理量和因此简化了代码。也可能有充分的理由将FrameNodeFrame 分开,但从问题中的信息中并不清楚——也不需要这样做;这是你要考虑的事情,仅此而已。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-11-04
      • 2018-07-22
      相关资源
      最近更新 更多