3

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?

10
  • NULL itself is the integer value 0 (though some implementations define it as the pointer value 0). Since you can't mix integers and structures together, what you're trying to do can't work as is. You'd be much better off returning the length of the array by reference. Commented Mar 29, 2014 at 19:54
  • You don't need the initial nist_t allocation, before the fgets loop. Just make sure nist is NULL. And don't assign to the same variable you pass into realloc, think about what would happen in the reallocation would fail, then you loose the original pointer. Commented Mar 29, 2014 at 19:54
  • Your array contains objects, not pointers to objects, so you can't set it to null. Use an array of pointers instead (but you'll need to allocate memory for each object too). Or better yet: just have an open array and store the count. Commented Mar 29, 2014 at 19:54
  • Also, I hope that your config and term string wont be more than 2 and 3 characters (respectively), since otherwise you will write out of bounds. Commented Mar 29, 2014 at 19:56
  • Unterminated arrays and count variables is how most languages do this, including C++'s vector object (which also stores a second count to keep track of how big it can get before needing to realloc) Commented Mar 29, 2014 at 19:57

3 Answers 3

3

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).

Sign up to request clarification or add additional context in comments.

4 Comments

NULL is defined as an implementation-defined null pointer constant. 0 is one common definition; ((void*)0) is another.
There's no evidence that NULL is defined as ((void*)0) and not as 0. But it doesn't matter; all that matters is that NULL expands to an (implementation-defined) null pointer constant which should only be assigned or compared to pointer objects/values.
@KeithThompson It's clear from the last error message that void * is involved, which leads to the educated guess (notice the phrasing seem in my answer) that it's defined ((void *) 0).
Ah, you're right, I didn't notice that. Still, it doesn't matter how NULL is defined -- except perhaps that it makes it easier to understand why that particular error message was produced.
1

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 Comments

I just allocated one addition element for the array, and set config and term to NULL. It seems to work. Thanks.
@Mannix: Huh? config and term are arrays. You can't set them to NULL. What exactly did you do?
Oh, I forgot to mention that I changed the character arrays to character pointers in the typedef declaration.
0

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.