【问题标题】:nested struct in c, problem with addressing pointersc中的嵌套结构,寻址指针问题
【发布时间】:2020-05-09 16:12:50
【问题描述】:

我有一个编程练习,其中我必须制作两个结构:StarClusterStar 必须接受四个参数 - 名称、第一个数字、第二个数字、第三个数字。 Cluster 包含 n - 位于文件 stars.dat 中的星号以及有关星号所在文件名的信息。然后编写一个加载函数,它将文件作为参数并返回一个指向从文件读取的Cluster 结构的指针。我的代码如下所示:

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

#define MAX_NAME 100

struct Star {
    char name[MAX_NAME];
    double recta;
    double decl;
    double mag;
};

struct Cluster {
    struct Star *ptr;
    char *file;
};

struct Cluster *load(char *plik);

int main() {
    load("stars.dat");
    return 0;
}

struct Cluster *load(char *plik) {
    char line[MAX_NAME];
    int i = 0;
    struct Star *star_ptr;

    FILE *file = fopen(plik, "r");
    struct Cluster *cluster_ptr;
    if (!plik) {
        perror(plik);
        exit(-1);
    } else {
        while (fgets(line, MAX_NAME, file) != NULL) {
            star_ptr = malloc(sizeof(struct Star));
            sscanf(line, "%s %lf %lf %lf", star_ptr->name, &star_ptr->recta, &star_ptr->decl, &star_ptr->mag);

            printf("%s \n", star_ptr->name);
            star_ptr++;
            i++;
        }
        cluster_ptr = (struct Cluster *)malloc(sizeof(struct Star) * i);
        cluster_ptr->ptr = star_ptr;
        cluster_ptr->file = plik;
        // printf("%s \n", cluster_ptr->star_ptr[0].name);
        printf("%s \n", star_ptr[0].name);
    }
    fclose(file);
    return cluster_ptr;
}

我的问题是这是否是正确的方法?如何检查返回的指针是否正确(我不知道如何printf 例如列表中第一颗星的名称)。如果有人能给我提示,将不胜感激。

【问题讨论】:

  • 我现在在回答中给了你几个提示。您希望我更改您的代码以使用链表吗?
  • 我将不胜感激,我更改了我的代码,所以现在我可以访问 Struct Cluster 的每个元素,但在我的解决方案中,我预定义了一个给定大小的数组,看起来不正确。
  • 如果你想使用数组而不是链表,你可以做的一件事是首先遍历整个输入文件一次以计算星数。这样,您将知道数组必须有多大。然后您可以使用fseek 返回文件的开头并读取各个星星。但是,如果您改用链表,则不需要这样做。

标签: c struct malloc


【解决方案1】:

您对struct Cluster 成员变量struct Star *ptr 的处理没有意义。

如果您希望 struct Cluster 能够“包含”多个 struct Star,那么您可以例如执行以下操作之一:

  1. 使struct Cluster 包含一个指针,该指针指向数据类型为struct Star 的动态分配的linked list 的第一个元素。
  2. 使struct Cluster 包含一个数组或具有一个指向动态分配的struct Star 类型元素数组的成员指针。为了跟踪该数组中的元素数量,您还需要一个单独的成员变量。
  3. 使struct Cluster 包含一个数组或具有一个指向动态分配的struct Star* 类型元素数组的成员指针,其中每个元素都是指向其自己的动态分配struct Star 的指针。为了跟踪此数组中的元素数量,您可以使用一个单独的变量来指定元素的数量,或者您可以使用特殊值标记数组的末尾,例如 NULL 指针。

您的代码不执行这些选项。相反,您可以执行以下操作:

对于您在文件中找到的每一颗星,您都会为单个struct Star 动态分配足够的内存。但不是记住这个struct Star的内存地址,而是在下一次循环迭代中,用下一个struct Star的内存地址覆盖指向这个内存地址的指针,这样你就不再知道这个内存地址的地址了第一个struct Star。这是memory leak。在循环的最后一次迭代完成后,您将集群的ptr 成员指向最后分配的struct Star,这样它实际上只指向一颗星。它不能指向更多的星星,因为正如我已经指出的那样,你不记得它们的记忆位置。

因此,为了解决您的问题,我建议您决定要使用上面列出的哪些选项并相应地编写代码。如果您事先不知道星团中的恒星数量,我建议您先尝试选项#1(链表)。

此外,以下行包含一个错误:

if (!plik)

您应该将其更改为以下内容:

if (!file)

或者可能是这个,我个人认为这更易读:

if ( file == NULL )

编辑:因为您在 cmets 部分中声明您想要一个链接列表实现的示例,所以我已经相应地修改了您的代码并将其发布在下面:

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

//Do not change the following line without also changing MAX_NAME_MINUS_ONE_STRING
#define MAX_NAME 100
#define MAX_NAME_MINUS_ONE_STRING "99"
//the line above must have the value MAX_NAME - 1 and be enclosed in quotation
//marks, otherwise you risk a buffer overflow in the sscanf function call!

struct StarNode
{
    //this points to the next node of the linked list
    struct StarNode *next;

    //I changed this struct member to a pointer to a dynamically
    //allocated string, because having a char array of 100 bytes
    //was a waste of memory
    char *name;

    double recta;
    double decl;
    double mag;
};

struct Cluster
{
    //points to the head of the linked list of stars
    struct StarNode *p_head;

    //points to its own dynamically allocated copy of the filename
    char *filename;
};

struct Cluster* load_cluster( const char* filename );
void cleanup_cluster( struct Cluster *p_cluster );

int main()
{
    struct Cluster *cluster_ptr;

    cluster_ptr = load_cluster( "stars.dat" );

    cleanup_cluster( cluster_ptr );

    return 0;
}

struct Cluster* load_cluster( const char *filename )
{
    char line[MAX_NAME];

    //pp_next will always point to the address of the struct StarNode * where
    //the address of the next node should be written
    struct StarNode *p_newstar, **pp_next;
    struct Cluster *p_cluster;

    FILE *file = fopen( filename, "r" );
    assert( file != NULL );

    p_cluster = (struct Cluster*)malloc( sizeof( struct Cluster ) );
    assert( p_cluster != NULL );

    pp_next = &p_cluster->p_head;

    while ( fgets( line, MAX_NAME, file ) != NULL )
    {
        char starname[MAX_NAME];
        int i;

        p_newstar = (struct StarNode *)malloc( sizeof( struct StarNode ) );
        assert( p_newstar != NULL );

        //I changed the %s to %99s (assuming MAX_NAME == 100) to prevent buffer overflow.
        //The value must be one less in order to have space for the null terminator.
        //Also, I now check the return value of sscanf.
        i = sscanf( line, "%" MAX_NAME_MINUS_ONE_STRING "s %lf %lf %lf", starname, &p_newstar->recta, &p_newstar->decl, &p_newstar->mag );
        assert( i == 4 );

        printf( "Adding %s\n", starname );

        //allocate memory for star's own copy of starname and copy it
        p_newstar->name = (char*)malloc( strlen( starname ) + 1 /*for null terminator character*/ );
        assert( p_newstar->name != NULL );
        strcpy( p_newstar->name, starname );

        //link the new star node to the linked list
        *pp_next = p_newstar;

        //update pp_next to the address of the pointer where the address of the next node should be written to
        pp_next = &p_newstar->next;
    }

    //the last element of the linked list must have a NULL pointer
    *pp_next = NULL;

    //allocate sufficient memory for filename and copy the string
    p_cluster->filename = (char*)malloc( strlen( filename ) + 1 /*for null terminator character*/ );
    assert( p_cluster->filename != NULL );
    strcpy( p_cluster->filename, filename );

    fclose( file );
    return p_cluster;
}


void cleanup_cluster( struct Cluster *p_cluster )
{
    struct StarNode *p;

    p = p_cluster->p_head;

    //cleanup every star node individually
    while ( p != NULL )
    {
        struct StarNode *temp;

        printf( "Deleting %s\n", p->name );
        free( p->name );

        temp = p;
        p = p->next;
        //free must be called last, because the contents of the current node become invalid
        //once free is called, which means that also the pointer to the next node would
        //become invalid
        free( temp );
    }

    free( p_cluster->filename );

    free( p_cluster );
}

在上面的代码中,我还创建了一个函数cleanup_cluster,以释放集群分配的内存。

请注意我也改了行

cluster_ptr-&gt;file = plik;

以便集群结构存储它自己的字符串副本。这样做的好处是,您还可以将生命周期有限的字符串(例如本地字符数组)传递给函数,而不会将指针变为dangling pointer

【讨论】:

  • 感谢您的解决方案,帮助很大。为什么你不必为其他变量动态分配内存(你只为名称分配它)。是因为 double 类型总是采用相同的大小吗?
  • 是的,动态内存分配只对字符串有意义,因为它的长度是未知的。 double 的大小始终是已知的,因此将其直接包含在结构中是有意义的。
  • double 实际上也是动态分配的,但它是作为其所属结构的一部分分配的(也是动态分配的),而不是像字符串那样单独分配。
  • @Konrad:我希望你不会对我使用双指针(即指向指针的指针)感到困惑。可以改用简单的指针,但这需要额外的if 语句来确定您是否位于列表的开头。有关更多信息,请参阅this question(其中也包含我的答案)。不过,它是 C++,而不是 C。我相信我对这个问题的回答表明使用双指针更有效。
猜你喜欢
  • 1970-01-01
  • 2010-11-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多