вложенная структура в c, проблема с адресацией указателей - PullRequest
1 голос
/ 09 мая 2020

У меня есть упражнение по программированию, в котором я должен создать две структуры: Star и Cluster. Star должен принимать четыре аргумента - имя, 1-е число, 2-е число, 3-е число. 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, например, имя 1-й звезды в списке). Был бы признателен, если бы кто-нибудь мог мне намекнуть.

1 Ответ

1 голос
/ 09 мая 2020

Ваша обработка переменной-члена struct Cluster struct Star *ptr не имеет смысла.

Если вы хотите, чтобы struct Cluster мог "содержать" несколько struct Star, то вы могли бы, например, сделать одно из следующих:

  1. Сделать struct Cluster содержать указатель, который указывает на первый элемент динамически выделяемого связанного списка типа данных struct Star.
  2. Сделать struct Cluster содержать массив или иметь указатель на член, который указывает на динамически выделяемый массив элементов типа struct Star. Чтобы отслеживать количество элементов в этом массиве, вам дополнительно потребуется отдельная переменная-член.
  3. Сделать struct Cluster содержать массив или иметь указатель элемента, который указывает на динамически выделяемый массив элементов типа struct Star*, где каждый элемент является указателем на свой динамически выделяемый struct Star. Чтобы отслеживать количество элементов в этом массиве, вы можете либо иметь отдельную переменную, которая определяет количество элементов, либо вы можете отметить конец массива специальным значением, например указателем NULL.

Ваш код не поддерживает ни одну из этих опций. Вместо этого вы делаете следующее:

Для каждой звезды, которую вы найдете в файле, вы динамически выделяете достаточно памяти для одной struct Star. Но вместо того, чтобы запоминать адрес памяти этого struct Star, в следующей итерации l oop вы перезаписываете указатель на этот адрес памяти адресом памяти следующего struct Star, так что вы больше не знаете адрес первого struct Star. Это утечка памяти . По окончании последней итерации l oop вы затем заставляете ptr член кластера указывать на последний struct Star, который был выделен, так что он фактически указывает только на одну звезду. Он не может указывать на большее количество звезд, потому что, как я уже указывал, вы не запомнили их расположение в памяти.

Поэтому, чтобы решить вашу проблему, я рекомендую вам решить, какой из перечисленных вариантов выше, вы хотите использовать и написать свой код соответственно. Если вы не знаете заранее количество звезд в скоплении, я рекомендую вам сначала попробовать вариант № 1 (связанный список).

Кроме того, следующая строка содержит ошибку:

if (!plik)

Вы должны изменить его на следующее:

if (!file)

Или, может быть, на это, которое я лично считаю более читаемым:

if ( file == NULL )

РЕДАКТИРОВАТЬ: поскольку в разделе комментариев вы указали, что вам нужен пример реализации связанного списка, я соответствующим образом изменил ваш код и разместил его ниже:

#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->file = plik;

, так что структура кластера хранит свою собственную копию строки. Это имеет то преимущество, что вы также можете передавать в функцию строки с ограниченным временем жизни (например, локальные массивы символов) без превращения указателя в висячий указатель .

...