1

I have a file that looks like this:

Kathryn 1561 2589.98

Hollie 2147 2496.36

Sherlock 3574 2514.65

and my fscanf - related code is as follows:

int load_data(char* fileName, char** accountNames, int* accountNumbers, float* amounts, int numOfAccounts)
{
    FILE *fPtr = fopen(fileName, "r");
    int counter = 1, test;

    if(fPtr  == NULL)
    {
            fclose(fPtr);
            return 0;
    }
    else
    {
            test = fscanf(fPtr,"%20s%d%f",  *accountNames, accountNumbers, amounts);
            printf("%d\n", test);
            while(counter < numOfAccounts)
            {
                test = fscanf(fPtr,"%20s%d%f",  *accountNames, accountNumbers, amounts);
                    printf("%d\n", test);
                    counter++;
            }

            fclose(fPtr);
            return 1;
    }

}

Here is the calling code:

int main(int argc, char** argv)
{
        int numOfAccounts = atoi(*(argv + 2));
        char* fileName = *(argv + 1);
        char** accountNames = malloc(sizeof(char) * 20 * numOfAccounts);
        int* accountNums = malloc(sizeof(int) * numOfAccounts);
        float* amounts = malloc(sizeof(float) * numOfAccounts);
        int dataload = load_data(fileName, accountNames, accountNums, amounts, numOfAccounts);
        dataload++;

return 0;
}

The if statement works, and the file is recognized. However, fscanf only returns 0. I'm really really new at C and fscanf so I ask that you be patient as I am learning, but if anyone could help me that would be great. Please let me know if I need to include more information. Thank you.

Not Steve
  • 127
  • 10

3 Answers3

1

Your char **accountNames should be a 1D array, of length numOfAccounts, of type char *; and the memory allocation for accountNames is done in two steps, not one:

int anlen, ai;

accountNames = (char **) malloc (numOfAccounts * sizeof (char *));

anlen = 20;

for (ia=0; ia<numOfAccounts; ia++)
{
    numOfAccounts[ia] = (char *) malloc (anlen * sizeof (char));

}

Your counter can run from 0 to numOfAccounts rather than from 1 to numOfAccounts, so that you have only one fscanf statement, contained in your while (count<numOfAccounts) loop.

The string in your fscanf statement should be " %19s %d %f". Note the spaces here, meaning 'ignore any leading whitespace'. This is one of the features that makes fscanf such a powerful and useful function, when used properly. Also, your *accountNames in your fscanf statement should now be *(accountNames + count) or accountNames[count].

You may want to add a check that each name has been read, for example by inserting into your fscanf a %c to read the character immediately following the name on each line of the data file and checking this character using isspace(). If this shows an error, you might want to use, for example, the discussion here to enable fscanf to read names of more than 19 characters.

rsnave
  • 11
  • 1
0

I'm not advocating this approach but it seems to be what you're aiming for with your current code:

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

#define NAME_SIZE 20

int load_data(char *fileName, char *accountNames, int *accountNumbers, float *amounts, int numOfAccounts)
{
    FILE *fPtr = fopen(fileName, "r");

    if (fPtr == NULL)
    {
        return 0;
    }

    int count = 0;

    for (int counter = 0; counter < numOfAccounts; counter++)
    {
        if (fscanf(fPtr, "%19s %d %f",  accountNames + (counter * NAME_SIZE), accountNumbers + counter, amounts + counter) == 3)
        {
            count++;
        }
    }

    fclose(fPtr);

    return count;
}

void unload_data(char *accountNames, int *accountNumbers, float *amounts, int numOfAccounts)
{
    for (int counter = 0; counter < numOfAccounts; counter++)
    {
        printf("%s %d %8.2f\n",  &accountNames[counter * NAME_SIZE], accountNumbers[counter], amounts[counter]);
    }
}

int main(int argc, char *argv[])
{
    int numOfAccounts = atoi(argv[2]);
    char *fileName = argv[1];

    char *accountNames = calloc(numOfAccounts, NAME_SIZE);
    int *accountNums = calloc(numOfAccounts, sizeof(int));
    float *amounts = calloc(numOfAccounts, sizeof(float));

    int datacount = load_data(fileName, accountNames, accountNums, amounts, numOfAccounts);
    printf("%d\n", datacount);
    unload_data(accountNames, accountNums, amounts, datacount);

    free(accountNames);
    free(accountNums);
    free(amounts);

    return 0;
}

OUTPUT

% ./a.out test.dat 3
3
Kathryn 1561  2589.98
Hollie 2147  2496.36
Sherlock 3574  2514.65
% 

Any time your code relies on parallel arrays, you're missing a proper data structure.

cdlane
  • 40,441
  • 5
  • 32
  • 81
0

OP's code has trouble with memory allocation and it shows up in file input.

The best is to isolate these 2 functions of code.

Given each line has 3 items: a name, number and amount, let us do that first.

There are so many weaknesses to using fscanf() @William Pursell , going directly to fgets() to read a line of text. Then parse it.

Rather than OP's "%20s%d%f", (which should be "%19s%d%f", code below uses a more complex parse, yet flexible should the name size limit change. Rather than checking the return value of sscanf(), this code checks the last parsed field. Code could check both.

int Read_Record(FILE *istream, char *name, size_t name_size, int *number, double *amount) {
  // No need to be stingy
  #define LINE_SIZE 256
  char line[LINE_SIZE];

  if (fgets(line, sizeof line, istream) == NULL) {
    return EOF;
  }
  // %n saves the offset of scanning at that point.
  int n1;
  int n2;
  int n3 = -1;
  sscanf(line, " %n%*s%n%d%lf %n", &n1, &n2, number, amount, &n3);

  // If scanning did not complete or had extra junk on the line, fail
  if (n3 < 0 || line[n3] != '\0') {
    return 1; // parse failure
  }
  if (n2 - n1 >= name_size) {
    return 2; // name too long
  }
  memcpy(name, line + n1, n2-n1);
  name[n2-n1] = '\0';
  return 0; //success
}

To allocate memory for numOfAccounts records, best to use a structure. As OP is a learner, let us do this without struct.

unsigned numOfAccounts = ...;
// TBD: Add test to insure numOfAccounts > 0

// Notice this style of defining the size needed - simple and clean
char **accountNames = malloc(sizeof *accountNames * numOfAccounts);
int *accountNums = malloc(sizeof *accountNums * numOfAccounts);
double *amounts = malloc(sizeof *amounts * numOfAccounts);
// TBD: Add allocation checks

for (unsigned i = 0; i < numOfAccounts; i++) {
  #define NAME_SIZE 20
  char name[NAME_SIZE];
  int rv = Read_Record(istream, name, sizeof name, &accountNums[i],
      &amounts[i]);
  if (rv != 0) Handle_Error();
  accountNums[i] = strdup(name);
}

// Use data

// free when done.

float is a weak choice for money. double is better. Many issues with any choice.

Community
  • 1
  • 1
chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256