NULL-terminated array of struct types in C

2019-07-16 06:57发布

问题:

I'm trying to built a NULL-terminate array of stucts

Here is the code: lzdata.c

#include <stdlib.h>
#include <stdio.h>
#include "nist.h"

int main(int argc,char *argv[])
{

    nist_t *nist; /* NIST data */

    nist=readnist();
}

The file nist.c

#include <stdlib.h>
#include <stdio.h>
#include "nist.h"

nist_t *readnist()
{
    nist_t *nist; /* NIST data */
    char line[50];
    int len=50;
    int i=0;

    nist=(nist_t*)malloc(sizeof(nist_t));
    while(fgets(line,len,stdin))
    {
        nist=(nist_t*)realloc(nist,sizeof(nist_t)*(i+1));
        sscanf(line,"%s %s %f %lf",nist[i].config,nist[i].term,&(nist[i].j),&(nist[i].level));
        ++i;
    }
    nist=(nist_t*)realloc(nist,sizeof(nist_t)*(i+1));
    nist[i]=(nist_t)NULL;

    return nist;
}

The header file nist.h:

#ifndef NIST_H
#define NIST_H

typedef struct
{
    char config[3];
    char term[4];
    float j;
    double level;
} nist_t;

nist_t *readnist();


#endif

The data file, which will be fed to the application via STDIN:

2s  ¹S   0.0    0.000000
2p  ³P°  1.0    142075.333333
2p  ¹P°  0.0    271687.000000
2p  ³P   1.0    367448.333333
2p  ¹D   0.0    405100.000000
2p  ¹S   0.0    499633.000000
3s  ³S   0.0    1532450.000000
3s  ¹S   0.0    1558080.000000
3p  ¹P°  0.0    1593600.000000
3p  ³P°  1.0    1597500.000000
3d  ³D   1.0    1631176.666667
3d  ¹D   0.0    1654580.000000
3s  ³P°  1.0    1711763.333333
3s  ¹P°  0.0    1743040.000000
3p  ³D   1.0    1756970.000000
3p  ³S   0.0    1770380.000000
3p  ³P   0.5    1779340.000000
3p  ¹D   0.0    1795870.000000
3d  ³P°  1.0    1816053.333333
3d  ¹F°  0.0    1834690.000000
3d  ¹P°  0.0    1841560.000000
...
...

When I compile:

$ cc -O2 -o lzdata lzdata.c nist.c nist.c: In function ‘readnist’: nist.c:24:2: error: conversion to non-scalar type requested

I've tried changing the line nist[i]=(nist_t)NULL; to nist[i]=(nist_t*)NULL; and I got:

$ cc -O2 -o lzdata lzdata.c nist.c nist.c: In function ‘readnist’: nist.c:24:9: error: incompatible types when assigning to type ‘nist_t’ from type ‘struct nist_t *’

I've tried changing the line nist[i]=(nist_t)NULL; to nist[i]=NULL; and I got:

$ cc -O2 -o lzdata lzdata.c nist.c nist.c: In function ‘readnist’: nist.c:24:9: error: incompatible types when assigning to type ‘nist_t’ from type ‘void *’

There could be a different number of lines in different data files. I'm seeking to build a NULL-terminated array of nist_t data, so I can process it until I get to the NULL element. Is this possible?

回答1:

For your compiler the macro NULL seems to be defined as ((void *) 0), that is a generic pointer to zero. Since nist[i] (for any valid value of i) is not a pointer you get the errors.

The best way to solve this might be to either pass the pointer from main by reference into the function and use that, and return the size. Or by passing an integer by reference, and set it to the size.

There is another solution too, and that is to have an array of pointers. Then you need to allocate each nist_t structure separately, and free them all too. Then you can use NULL to indicate the end of the array. This is actually how argv works, it's terminated by a NULL pointer (so argv[argc] is always equal to NULL).



回答2:

You have an array of structures. NULL is a null pointer constant. There is no null value for a structure type -- unless you define some distinguished value yourself.

For example, you could modify your nist_t type so it has a member that indicates whether the entire value is valid or not:

#include <stdbool.h>
typedef struct
{
    bool is_valid;
    char config[3];
    char term[4];
    float j;
    double level;
} nist_t;

and then mark the end of the array with a nist_t element with is_valid set to false. Or perhaps you can use some invalid value for one of the other members.

This means you'll need to be careful with any code you write that deals with these structures to ensure that is_valid is initialized correctly, and that you don't try to use the other member values with is_valid is false.

Or you can build an array if nist_t* pointers, setting the terminating element to a null pointer type assigning NULL to it.

But it might be simpler just to keep track of the number of valid elements in your array of nist_t objects.

The particular way that NULL is defined is not very important here. All that really matters is that it's a null pointer constant. Assigning NULL to a pointer object makes it a null pointer; assigning it to any non-pointer is invalid (though there are cases where the compiler, unfortunately, might let you get away with it).



回答3:

This works:

nist_t *readnist()
{
    nist_t *nist=NULL;  /* NIST data */
    char line[50];      /* A single line of NIST data */
    char config[10];    /* nl quantum numbers */
    char term[10];      /* Rotational term */
    float j;
    double level;       /* energy level */
    int len=50;         /* default length of string */
    int i=0;            /* counter/index variable */
    int n;              /* principal quantum number */
    char l;             /* azimuthal quantum number */
    int en=0;           /* error number */

    while(fgets(line,len,stdin))
    {
        (void)sscanf(line,"%s\t%s\t%f\t%lf",config,term,&j,&level);
        if(NULL==(nist=(nist_t*)realloc(nist,sizeof(nist_t)*(i+1))))
        {
            en=errno;
            fprintf(stderr,"Error %d: memory allocation failure\n",en);
            fprintf(stderr,"File:\t\t%s\n",__FILE__);
            fprintf(stderr,"Function:\t%s\n",__FUNCTION__);
            fprintf(stderr,"Line:\t\t%d\n",__LINE__-6);
            exit(en);
        };
        nist[i].config=(char*)malloc(sizeof(char)*(strlen(config)+1));
        nist[i].term=(char*)malloc(sizeof(char)*(strlen(term)+1));
        strcpy(nist[i].config,config);
        sscanf(config,"%d%c",&n,&l);
        nist[i].n=n;
        strcpy(nist[i].term,term);
        nist[i].j=j;
        nist[i].level=level;
        ++i;
    }
    nist=(nist_t*)realloc(nist,sizeof(nist_t)*(i+1));
    nist[i].config=NULL;
    nist[i].term=NULL;
    nist[i].j=0.0;
    nist[i].level=0.0;

    return nist;
}

And here is a routine I used to test it:

nist_t *prtnist(nist_t *nist)
{
    int i=0;

    while((NULL!=nist[i].config) && (NULL!=nist[i].term))
    {
       fprintf(stderr,"%s\t%d\t%s\t%4.1f\t%14.8e\n",nist[i].config,nist[i].n,nist[i].term,nist[i].j,nist[i].level);
        ++i;
    }
    return nist;
}

Now I've got to figure a way to get the azimuthal quantum number out of the config string.