Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

saving and loading a linked list to a binary file (C)

Tags:

c

linked-list

I'm trying to write and read a linked list from a binary file in C. My aim is saving and loading resident data for a Nursing Home (actually, I'm a Nurse), in order to classify each resident via the Resource Utilization Groups. I've already done it for a fixed amount of residents (32, that is the capacity of the facility) using an array of structures, but now I need to do so for a variable set of residents, in order to do a statistic study. Obviously for this first attempt i simplified the structure to the minimum, as the actual structure contains 109 data.

I'm very close to the solution but something doesn't work, that is, each element of the saved list is alternated with a void one.This code should read the list from the binary file, show it on the terminal, add a new element, save the list. Of course each procedure should be put in a function.

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

struct pat
{
    char surn [16];
    char name [16];
    struct pat *next;
};

static FILE *h;
static struct pat *osp;
static struct pat *first;

struct pat *make_structure (void);

int main()
{
    initscr();
    raw();
    keypad (stdscr, TRUE);
    noecho();

    clear();

    osp=make_structure();
    first=osp;

    h=fopen ("archivio","r");

    if (h==NULL)
        printw ("Archivio inesistente\n");
    else
    {
        while (!feof(h))
        {
            printw ("Lungh. nome = %d\n",sizeof osp->name);
            fread (osp->surn,sizeof osp->surn,1,h);
            fread (osp->name,sizeof osp->name,1,h);
            printw ("Cognome: %s\tNome: %s\n",osp->surn,osp->name);
            osp->next=make_structure();
            osp=osp->next;
        }
    }

    getch();

    echo();
    printw ("Surname: ");
    scanw ("%s",osp->surn);
    printw ("\nName: ");
    scanw ("%s",osp->name);

    noecho();

    osp=first;

    h=fopen ("archivio","w");

    while (osp != NULL)
    {
        fwrite (osp->surn,sizeof osp->surn,1,h);
        fwrite (osp->name,sizeof osp->name,1,h);
        osp=osp->next;
    }

    return 0;
}

struct pat *make_structure(void)
{
    struct pat *a;
    a = (struct pat *)malloc(sizeof(struct pat));
    return (a);
}
like image 317
user241968 Avatar asked Jun 14 '16 20:06

user241968


1 Answers

You were so close, I'm not even sure what was the true cause of failure, because, for the first cut, I just applied [most of] the fixes suggested by others and got a working program.

Although it worked, I found that the way you did the "one ahead" make_structure call to be less flexible when you extend the program to do other things.

For example, you'd have a hanging "ghost" record if, instead of adding a new record, you decided to not add a new record, but do some statistics on or manipulation of the existing ones.

So, I created a second version of the program that has more isolation and generality.


Here's the minimally changed version [please pardon the gratuitous style cleanup]:

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

struct pat {
    char surn[16];
    char name[16];
    struct pat *next;
};

static FILE *h;
static struct pat *osp;
static struct pat *first;

struct pat *make_structure(void);

int
main()
{
    int rlen;

    initscr();
    raw();
    keypad(stdscr, TRUE);
    noecho();

    clear();

    osp = make_structure();
    first = osp;

    h = fopen("archivio", "r");

    if (h == NULL)
        printw("Archivio inesistente\n");
    else {
        while (1) {
            printw("Lungh. nome = %d\n", sizeof osp->name);

            // leave early on EOF or badly formed entry
            rlen = fread(osp->surn, sizeof osp->surn, 1, h);
            if (rlen != 1)
                break;

            // leave early on EOF or badly formed entry
            fread(osp->name, sizeof osp->name, 1, h);
            if (rlen != 1)
                break;

            printw("Cognome: %s\tNome: %s\n", osp->surn, osp->name);

            osp->next = make_structure();
            osp = osp->next;
        }
        fclose(h);
    }

    // NOTE: this just chews the first character (debug, I suppose?)
#if 0
    getch();
#endif

    // add new element
    echo();
    printw("Surname: ");
    scanw("%15s", osp->surn);
    printw("Name: ");
    scanw("%15s", osp->name);

    noecho();

    h = fopen("archivio", "w");

    osp = first;
    while (osp != NULL) {
        fwrite(osp->surn, sizeof osp->surn, 1, h);
        fwrite(osp->name, sizeof osp->name, 1, h);
        osp = osp->next;
    }

    fclose(h);

    return 0;
}

struct pat *
make_structure(void)
{
    struct pat *a;

    a = malloc(sizeof(struct pat));

    // NOTE: do this for good measure
    a->next = NULL;

    return (a);
}

Here's the more generalized version that may give you some ideas when you extend the capabilities of the program:

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

struct pat {
    char surn[16];
    char name[16];
    struct pat *next;
};

static FILE *h;
static struct pat *osp;
static struct pat *first;
static struct pat *prev;

void read_archive(const char *file);
void add_new_elements(void);
void write_archive(const char *file);
struct pat *make_structure(void);
void add_to_list(struct pat *pat);

int
main()
{

    initscr();
    raw();
    keypad(stdscr, TRUE);
    noecho();

    clear();

    read_archive("archivio");

    // NOTE: this just chews the first character (debug, I suppose?)
#if 0
    getch();
#endif

    // NOTE: instead of just automatically adding new elements, this might
    // be replaced with a menu, such as:
    //   Enter Operation:
    //     (1) Add new names
    //     (2) Calculate statistics
    //     (3) Backup database
    add_new_elements();

    write_archive("archivio");

    return 0;
}

// read_archive -- read in archive
void
read_archive(const char *file)
{
    int rlen;

    h = fopen(file, "r");

    if (h == NULL)
        printw("Archivio inesistente\n");

    else {
        while (1) {
            osp = make_structure();

            // leave early on EOF or badly formed entry
            rlen = fread(osp->surn, sizeof osp->surn, 1, h);
            if (rlen != 1)
                break;

            // leave early on EOF or badly formed entry
            fread(osp->name, sizeof osp->name, 1, h);
            if (rlen != 1)
                break;

            printw("Cognome: %s\tNome: %s\n", osp->surn, osp->name);

            add_to_list(osp);
        }

        // NOTE: this is _always_ for EOF or bad entry, so free it
        free(osp);

        fclose(h);
    }
}

// add_new_elements -- prompt for new elements
void
add_new_elements(void)
{

    echo();
    while (1) {
        osp = make_structure();

        printw("Surname: ");
        osp->surn[0] = 0;
        scanw("%15s", osp->surn);
        if (osp->surn[0] == 0)
            break;

        printw("Name: ");
        osp->name[0] = 0;
        scanw("%15s", osp->name);
        if (osp->name[0] == 0)
            break;

        add_to_list(osp);
    }
    noecho();

    free(osp);
}

// write_archive -- write out archive
void
write_archive(const char *file)
{

    h = fopen(file, "w");

    for (osp = first;  osp != NULL;  osp = osp->next) {
        fwrite(osp->surn, sizeof osp->surn, 1, h);
        fwrite(osp->name, sizeof osp->name, 1, h);
    }

    fclose(h);
}

struct pat *
make_structure(void)
{
    struct pat *a;

    a = malloc(sizeof(struct pat));

    // NOTE: do this for good measure
    a->next = NULL;

    return (a);
}

void
add_to_list(struct pat *pat)
{

    if (first == NULL)
        first = pat;
    else
        prev->next = pat;

    prev = pat;
}

UPDATE:

I was still trying to figure out the reason of my failure

I didn't debug/single-step your original code because I thought that your linked list logic would need fixing and I wanted to get to that quickly. However, after I reviewed it, the logic was fine. Based on my best guess analysis, the probable failure was the feof which I had already changed to length check on fread.

Of course I was going to organize better the program using functions

I assumed that you would. The splitup in the second program was more of a teaching tool to clarify and illustrate a principle and was not a critique of the modularity.

In your original code, you had to add a new record because osp was empty but already linked into the list. Loosely, a "zombie" record, if you will.

That is, the list had an entry linked in before it was filled in and validated. In other words, after the read loop, but before the user is prompted for the new entry, the list could be considered malformed (i.e. a [small] violation of "contract programming" or "design-by-contract" principles).

The function splitup in the second program was just to emphasize this. In particular, by moving the read loop to a separate function, it illustrated/enforced design-by-contract.

That is, upon entry, the list is whole and well formed [albeit, empty]. Upon return, it either is empty [if the input file does not exist] or has only well formed/complete records in it.

In the second program, a partial/malformed entry is never linked in. The add_to_list is always done last [for a whole/complete record only].

So, for both read_archive and add_new_entries, when they are called, they are both given a whole/complete list with only valid, fully formed records. That is the "contract" to them.

And to fulfill their part of the "contract" these functions must leave things the same way, maintaining the list integrity upon exit. That is the functions' "contract" to the outside world


UPDATE #2:

excuse me for the OT, but could you suggest to me a good IDE for C - C++ that works well with Debian/GNU Linux ?

I may not be the best person to advise you on this as I don't use one. I was writing C long before they existed, so I have developed my own tools suite which is far more powerful than any IDE I've seen. Also, when I've looked at them, I could never find a way to mesh the two.

I'm at home with Code::Blocks, but unfortunately the so called nightly build is buggy and crashes very often

If you're at home with codeblocks, but the nightly build is buggy, perhaps the simple solution is to switch your update to the "stable" tree, if that's possible. That may be the best "short answer".

(the code completion utility is very useful but I must not type str..., otherwise it freezes), and that's very frustrating!

Maybe you could check the bug database and see if the problems you are experiencing are have known bug reports. If not, you could/should file one.


I installed codeblocks. It looks clean and simple enough. I also installed eclipse and took a look at kdevelop. From several web pages, eclipse gets high marks, with netbeans a close second

I tried to use them on source file I had lying around that was built with a Makefile. codeblocks was intuitive enough that I could get it going quickly. I had more trouble with the others. eclipse was originally designed by IBM for internal use and then released as a public service. It is well supported and mature.

I had been running eclipse without CDT, but once I added that, eclipse gets my vote, because it appears to have enough features to control all of what I'm going to gripe about below ;-)

An IDE is a somewhat personal choice [unless your company mandates one], so you should use what you like. In other words, try some and see what features they have and how they work. Here is a page that lists some: https://en.wikipedia.org/wiki/Comparison_of_integrated_development_environments

When selecting an IDE, you have to look at the "most used" features. The most frequent thing you do is scroll around in the source file. So, an editor should support the hjkl aliases to the arrow keys, like vi does. Having to move your right hand over to the arrow keys and back slows things down so much that it's a non-starter.

eclipse uses gvim [graphical vim], so that's a plus.

I'm not a fan of editing with some simple WYSIWYG editor pane that has only crude search/replace features. Likewise, vim allows regex search simply by typing /, so again, the most common operations are "at your fingertips"

I don't use [nor want] autocompletion features. When I've tried them, they frequently get things wrong and it takes longer to backspace over what they've done. I am a very fast typist.

I also turn off syntax highlighting and colorization of source. When entering source code, the colors change just about with every character entered because of what the editor thinks I'm typing (e.g. I'm typing a comment, but it thinks it's code, etc.) I've found that to be distracting.

Also, when looking at the final result, I find that the colorized result to be "too busy" (i.e. more information I have to filter) rather than something that helps me see what I need to see.

I am fairly insistent about indenting, breaking up long code blocks with blank lines to improve readibility. And, of course, good comments. To me, these are far more important than the coloring. I have a custom tool for the indent [As you may recall, when I posted the code above, it was reindented because I ran it through my tool before posting.

Another feature is the graphical debugger. Is it full featured? For example, ddd is a graphical wrapper around [the very powerful] gdb. ddd provides a graphical wrapper and windows for common things, but still allows a direct text window for the gdb prompt, so you can manually type in the more advanced commands (e.g. watch symbol).

Is the IDE extensible? Can you add plugins? Can you easily add/create your own?

What source code control system does the IDE use? I've used many over the years and, now, am completely sold on git. So, if the IDE doesn't support git, it's a non-starter.

It should be noted that git has so many features than they can not be contained in a GUI. So, the really powerful stuff uses a command line tool in a terminal window.

My IDE? Several xterm windows, vi, git, and my tools suite [which is currently 250,000 lines of perl scripts ;-)] Does the IDE force you to do things its way? How easy is it to import/export configuration, etc. to other external tools and IDEs?

I have a very powerful build script of my own design. So, I'd want the IDE to, when I click on the "build" button to not do whatever it would normally do, but turn over control to my build script. Likewise for any other operation the IDE has to offer.

Is the IDE portable and available on all major platforms: linux, OSX, and windows? In the past, that was another reason I shied away from IDEs. They would be available on only one platform. Or, since I was doing consulting work, I'd go into an environment that would not allow the IDE to be installed/used because of [sysadmin] policy.

like image 195
Craig Estey Avatar answered Nov 03 '22 22:11

Craig Estey