Search code examples
cpointersgetlinescanfstrtok

Incompatible pointers not allowing for csv placement into 2D array


I'm trying to read in a CSV file line by line and then split the lines into their values as read from the CSV file by separating the lines with the comma delimiter. Once successful, the goal is to read this 2D array into a sophisticated model in C as the input. For this I'm using getline() and strtok().

I'm new to C, and I've spent weeks getting to this point so please don't suggest different functions for this unless absolutely necessary. I'll post what I have so far and insert what warnings I'm getting and where if anyone could please help me figure out why this code won't produce the array. I think it may just be a pointer issue but I've been trying everything I can and it's not working.

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define ARRAYLENGTH 9
#define ARRAYWIDTH 7

float data[ARRAYLENGTH][ARRAYWIDTH];

int main(void) {

  char *line = NULL;
  size_t len = 0;
  ssize_t read;

  FILE *fp;
  fp=fopen("airvariablesSend.csv", "r");

  if(fp == NULL){
    printf("cannot open file\n\n");
    return -1;
  }

  int k , l;
  char **token; //for parsing through line using strtok()

  char comma = ',';
  const char *SEARCH = &comma; //delimiter for csv 
  char *todata; 

  for (l=0; l< ARRAYLENGTH +1; l++){ 
    while ((read = getline(&line, &len, fp)) != -1) {

      //Getline() automatically resizes when you set pointer to NULL and 
      //len to 0 before you loop
      //Here, the delimiting character is the newline character in this 
      //form. Newline character included in csv file

      //printf("Retrieved line of length %zu :\n", read);
      printf("%s", line);

      //The first line prints fine here. Now once I try to parse through 
      //I start getting warnings:

      for(k = 0; k < ARRAYWIDTH; k++) { //repeats for max number of columns

        token = &line;
        while (strtok(token, SEARCH)){

          //I'm getting this warning for the while loop line:
          //warning: passing argument 1 of `strtok' from incompatible pointer type

          fscanf(&todata, "%f", token);

          //I'm getting these warnings for fscanf. I'm doing this because
          //my final values in the array to be floats to put in the  
          //model      

          //warning: passing argument 1 of `fscanf' from incompatible pointer type
          //warning: format `%f' expects type `float *', but argument 3 has type 
          // 'char **'  

          todata = &data[l][k];

          //And finally, I'm getting this warning telling me everything is 
          //incompatible.
          //warning: assignment from incompatible pointer type. 

          printf("%f", data[l][k]);
        }

      }

    }
  }       

  free(line);
  //Free memory allocated by getline()
  fclose(fp);
  //Close the file.
  exit(EXIT_SUCCESS);
  return 0;
}

Solution

  • Using getline:

    While strtok is fine, it is unnecessary when converting the values directly to numbers with strtof, strtol, .. or the like. Unless you are using the values as string values, you will have to call a conversion routine (and do appropriate error checking) anyway. The conversion routines already set an end pointer for you that can be used to parse the input. The point being, why use two functions to accomplish what one was intended to do to begin with? The following makes use of getline and strtof:

    #define _GNU_SOURCE
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <errno.h>
    
    #define ARRAYLENGTH 9
    #define ARRAYWIDTH 7
    
    int main (void) {
    
        char *line = NULL;      /* initialize ALL variables */
        size_t len = 0;
        ssize_t read = 0;
        float data[ARRAYLENGTH][ARRAYWIDTH] = {{0},{0}};
        size_t al = 0;          /* array length counter     */
        size_t aw = 0;          /* array width counter      */
        FILE *fp = NULL;
    
        if (!(fp = fopen ("airvariablesSend.csv", "r"))) {
            fprintf (stderr, "error: file open failed.\n");
            return 1;  /* do not return -1 to the shell */
        }
    
        while ((read = getline (&line, &len, fp)) != -1)
        {
            char *p = line;     /* pointer to line      */
            char *ep = NULL;    /* end pointer (strtof) */
    
            /* strip trailing '\n' or '\r' 
             * not req'd here, but good habit 
             */
            while (read > 0 && (line[read-1] == '\n' || line[read-1] == '\r'))
                line[--read] = 0;
    
            errno = 0;
            aw = 0;
            while (errno == 0)
            {
                /* parse/convert each number in line    */
                data[al][aw] = strtof (p, &ep);
    
                /* note: overflow/underflow checks omitted */
                /* if valid conversion to number */
                if (errno == 0 && p != ep)
                {
                    aw++;                   /* increment index      */
                    if (aw == ARRAYWIDTH)   /* check for full row   */
                        break;
                    if (!ep) break;         /* check for end of str */
                }
    
                /* skip delimiters/move pointer to next (-) or digit   */
                while (*ep && *ep != '-' && (*ep <= '0' || *ep >= '9')) ep++;
                if (*ep)
                    p = ep;
                else
                    break;
            }
    
            al++;
            if (al == ARRAYLENGTH)          /* check length full    */
                break;
        }   
    
        if (line) free(line);
        if (fp) fclose(fp);
    
        printf ("\nArray Contents:\n\n");
        for (al = 0; al < ARRAYLENGTH; al++) {
            for (aw = 0; aw < ARRAYWIDTH; aw++)
                printf (" %8.2f", data[al][aw]);
            printf ("\n");
        }
    
        printf ("\n");
    
        exit(EXIT_SUCCESS);
    }
    

    Note: _GNU_SOURCE and string.h are unnecessary for this code, but have been left in case they are need in the remainder of your code.

    Input

    $ cat airvariablesSend.csv
    
    -1.21,2.30,3.41,4.52,5.63,6.74,7.85
    1.21,-2.30,3.41,4.52,5.63,6.74,7.85
    1.21,2.30,-3.41,4.52,5.63,6.74,7.85
    1.21,2.30,3.41,-4.52,5.63,6.74,7.85
    1.21,2.30,3.41,4.52,-5.63,6.74,7.85
    1.21,2.30,3.41,4.52,5.63,-6.74,7.85
    1.21,2.30,3.41,4.52,5.63,6.74,-7.85
    1.21,2.30,3.41,4.52,5.63,-6.74,7.85
    1.21,2.30,3.41,4.52,-5.63,6.74,7.85
    

    Output

    $ ./bin/getlinefloatcsv
    
    Array Contents:
    
        -1.21     2.30     3.41     4.52     5.63     6.74     7.85
         1.21    -2.30     3.41     4.52     5.63     6.74     7.85
         1.21     2.30    -3.41     4.52     5.63     6.74     7.85
         1.21     2.30     3.41    -4.52     5.63     6.74     7.85
         1.21     2.30     3.41     4.52    -5.63     6.74     7.85
         1.21     2.30     3.41     4.52     5.63    -6.74     7.85
         1.21     2.30     3.41     4.52     5.63     6.74    -7.85
         1.21     2.30     3.41     4.52     5.63    -6.74     7.85
         1.21     2.30     3.41     4.52    -5.63     6.74     7.85
    

    Using fscanf Only:

    Of course, if your intention was to use fscanf and do away with getline, then your input routine reduces to:

    #include <stdio.h>
    #include <stdlib.h>
    
    #define ARRAYLENGTH 9
    #define ARRAYWIDTH 7
    
    int main (void) {
    
        float data[ARRAYLENGTH][ARRAYWIDTH] = {{0},{0}};
        size_t al = 0;          /* array length counter     */
        size_t aw = 0;          /* array width counter      */
        FILE *fp = NULL;
    
        if (!(fp = fopen ("airvariablesSend.csv", "r"))) {
            fprintf (stderr, "error: file open failed.\n");
            return 1;  /* do not return -1 to the shell */
        }
    
        for (al =0; al < ARRAYLENGTH; al++)
            fscanf (fp, "%f,%f,%f,%f,%f,%f,%f", &data[al][0], &data[al][1], 
                    &data[al][2], &data[al][3], &data[al][4], &data[al][5], &data[al][6]);
    
        if (fp) fclose(fp);
    
        printf ("\nArray Contents:\n\n");
        for (al = 0; al < ARRAYLENGTH; al++) {
            for (aw = 0; aw < ARRAYWIDTH; aw++)
                printf (" %8.2f", data[al][aw]);
            printf ("\n");
        }
    
        printf ("\n");
    
        exit(EXIT_SUCCESS);
    }
    

    However, note: using fscanf is far less flexible than using either getline or fgets. It relies on the input format string matching the data exactly to prevent a matching failure. While this is fine in some cases, where flexibility is needed, reading a line at a time with getline of fgets is the better choice. (all it takes is a stray character to torpedo the fscanf conversion)