0

I have a structure containing pointers that I would like to populate from an array, both defined below. I'm using strtok to split a string in the elements array and then take the individual values and put them in the relevant structure values.

I can split the string ok but the value that is populated into eleNum is wrong. I appear to be getting a pointer value or something similar. I'm also unsure if my memory allocations for the 3 fields (eleNum, eleSym, eleName) are correct. They work for eleSym and eleName but I don't know if this is just luck or the right way of mallocing space for them.

typedef struct PTdef {
    int     *eleNum;
    char    *eleSym;
    char    *eleName;
} ptDB;

int main(void)
{
    ptDB    pt[118] = {};
    char    elements[][40] = {"1,H,Hydrogen"};
    
    char    *token;
    char    *eleDup = (char *)malloc(40);
    char    sep[] = ",";

    strcpy(eleDup, elements[0]);
    token = strtok(eleDup, sep);        

    pt[0].eleNum = malloc(sizeof(int));
    pt[0].eleSym = (char *)malloc(sizeof(token));
    pt[0].eleName = (char *)malloc(strlen(token));

    pt[0].eleNum = (int *)token;
    token = strtok(NULL, sep);
    strcpy(pt[0].eleSym, token);
    token = strtok(NULL, sep);
    strcpy(pt[0].eleName, token);

Output should be.

pt[0].eleNum = 1.
pt[0].eleSym = H.
pt[0].eleName = Hydrogen.
Griffau
  • 33
  • 5

1 Answers1

2

The posted code has several problems, see the comments inlined below.

typedef struct PTdef {
    int     *eleNum;
    char    *eleSym;
    char    *eleName;
} ptDB;

int main(void)
{
    ptDB    pt[118] = {};
    char    elements[][40] = {"1,H,Hydrogen"};

    char    *token;
    char    *eleDup = (char *)malloc(40);  // no need to cast the return of malloc
                                           // https://stackoverflow.com/questions/605845/
    char    sep[] = ",";

    strcpy(eleDup, elements[0]);
    token = strtok(eleDup, sep);           // now 'token' points to nul-terminated "1"

    pt[0].eleNum = malloc(sizeof(int));    // 'eleNum' points to a newly allocated int

    pt[0].eleSym = (char *)malloc(sizeof(token));  // allocates sizeof(char*) bytes
                                                   // typically 4 bytes in 32-bit
                                                   // or 8 bytes in 64-bit compiles
                                                   // regardless of contents of 'token'

    pt[0].eleName = (char *)malloc(strlen(token)); // allocates strlen("1") = 1 byte

    pt[0].eleNum = (int *)token;            // discards the previous value of the pointer
                                            // so it leaks the malloc(sizeof(int)) memory
                                            // and forces 'eleNum' to point to string "1"
                                            // which is not an 'int'

    token = strtok(NULL, sep);              // now 'token' points to "H"
    strcpy(pt[0].eleSym, token);            // copies nul-terminated "H"
                                            // to oversized 4- or 8-byte buffer

    token = strtok(NULL, sep);              // now 'token' points to "Hydrogen"
    strcpy(pt[0].eleName, token);           // copies nul-terminated "Hydrogen"
                                            // to 1-byte buffer, which overruns it

Keeping it as close to the original as possible, the following code would work correctly.

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

typedef struct PTdef {
    int     eleNum;                   // store value, not pointer
    char    *eleSym;
    char    *eleName;
} ptDB;

int main()
{
    ptDB    pt[118] = {};
    char    elements[][40] = {"1,H,Hydrogen"};
    
    char    *token;
    char    eleDup[40];               // no need for dynamic allocation here
    char    sep[] = ",";

    strcpy(eleDup, elements[0]);
    token = strtok(eleDup, sep);        

    pt[0].eleNum = atoi(token);       // convert string "1" to integer 1

    token = strtok(NULL, sep);
    pt[0].eleSym = strdup(token);     // or:  pt[0].eleSym = malloc(strlen(token) + 1);
                                      //      strcpy(pt[0].eleSym, token);
    token = strtok(NULL, sep);
    pt[0].eleName = strdup(token);

    printf("num %d, sym '%s', name '%s'\n", pt[0].eleNum, pt[0].eleSym, pt[0].eleName);

    free(pt[0].eleName);              // cleanup
    free(pt[0].eleSym);

    return 0;
}

Left for the OP to fill-in:

  • strdup return values should be checked to catch out-of-memory conditions;

  • strtok return values should be checked to catch strings in the wrong format;

  • atoi does not report errors, see Why shouldn't I use atoi()? and use strtol instead.

dxiv
  • 16,984
  • 2
  • 27
  • 49
  • 1
    You may want to mention that `char elements[][40]` makes little sense for holding a single string. – David C. Rankin Oct 12 '20 at 06:02
  • @DavidC.Rankin Right, of course, and the same goes for `pt[118]` holding a single element. But I took OP's snippet to be just one step in some wider context, so I tried to flag and fix the critical issues, rather than attempt a comprehensive code review. – dxiv Oct 12 '20 at 06:16
  • 1
    Yep, I could tell you were sticking close to the question, and there is know way to cover it all -- but I was still left looking at that with much bewilderment `:)` Presumably there will be more than one string. – David C. Rankin Oct 12 '20 at 07:23
  • @DavidC.Rankin noted it would normally have 118 entries though. – Griffau Oct 13 '20 at 02:43
  • 1
    @dxiv many thanks for your response it is greatly appreciated. I still have much to learn re structure and pointers but you have helped a great deal. – Griffau Oct 13 '20 at 02:44