Obamaself
Obamaself

Reputation: 29

How to store numbers from file to dynamically assorted array?

I am having a problem with storing numbers from a file to a 1D array. I want to store the numbers without giving size to the array int numberArray[11]. These are numbers I am trying to read from an input.txt file

200 53 65 98 183 37 122 14 124 65 67

int main()
{

    FILE *myFile;
    myFile = fopen("input.txt", "r");

    //read file into array
    int numberArray[11];
    int i;

    if (myFile == NULL)
    {
        printf("Error Reading File\n");
        exit (0);
    }

    //Store number to array
    for (i = 0; i < 11; i++)
    {
        fscanf(myFile, "%d,", &numberArray[i] );
    }




    /*
    //Print array
    for (i = 0; i < 11; i++){
        printf("%d\n", numberArray[i]);
    }
    */

    //Close the file
    fclose(myFile);

    //FCFS the array
    FCFS(numberArray);

    return 0;
}

As you can see in my code, I have created an array of int size of 11 because there are 11 numbers that I am trying to read into the array. Let's just say I don't know the numbers inside the input file. How do I allocate the array?

P.s my code works but I just want to know whats the efficient approach when it comes to these kind of situations.

Upvotes: 0

Views: 369

Answers (3)

David C. Rankin
David C. Rankin

Reputation: 84551

When wanting to read and store an unknown number of anything, you must provide valid storage for whatever you read. You generally do so by allocating a block of memory using malloc(), calloc() or realloc() and assigning the beginning address for the newly allocated block of memory to a pointer of the appropriate type.

To grow the size of the block of memory as needed, you keep track of the number of objects you have currently allocated storage for and the number used. When used == allocated you must realloc() additional storage using a temporary pointer with realloc().

The reason you use a temporary pointer is when realloc() fails it returns NULL. If you are assigning to your actual pointer instead of a temporary, then you overwrite the address to the current block of memory held by your pointer with NULL. That creates a memory-leak because that block of memory can no longer be freed.

In the event realloc() fails, the temporary pointer is set to NULL, but the address to the current block of memory stored in your original pointer is not freed and it remains valid holding the data collected up to the point of failure. That data is valid and usable, so there is no need to exit on the realloc() failure.

You can grow your memory by whatever amount you like each time you realloc(), though it is best to avoid a reallocation for every newly added object. (that's inefficient even if the memory is mmaped). One scheme that provides reasonable balance between the growth of memory and the number of reallocations required is simply to double the size currently allocated each time a reallocation is needed. When you are done with the memory you have allocated, don't forget to free() it.

A short example that reads integers from the filename provided as the first argument on the command line (or reads from stdin by default if no argument is provided) can be written like:

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

#define NINTS 1   /* if you need a constant, #define one (or more) */

int main (int argc, char **argv) {
  
  int val, *arr = NULL;             /* val to read and ptr to memory */
  size_t allocated = 0, used = 0;   /* number ints allocated & used */
  /* use filename provided as 1st argument (stdin by default) */
  FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;
  
  if (!fp) {  /* validate file open for reading */
    perror ("fopen-fp");
    return 1;
  }
  
  while (fscanf (fp, "%d", &val) == 1) {  /* for each int read from file */
    if (used == allocated) {              /* check if realloc needed */
      if (!allocated) {                   /* not allocated set to NINTS */
        allocated = NINTS;
      }
      /* always realloc using a temproary pointer */
      void *tmp = realloc (arr, 2 * allocated * sizeof *arr);
      if (!tmp) {                         /* validate EVERY allocation */
        perror ("realloc-tmp");
        break;                            /* break on error, arr still OK */
      }
      arr = tmp;                /* set arr to newly allocated block */
      allocated *= 2;           /* update the number of ints allocated */
    }
    arr[used++] = val;          /* add value to block of memory */
  }
  
  for (size_t i = 0; i < used; i++)       /* output results */
    printf ("%3zu - %d\n", i, arr[i]);
  
  free (arr);     /* don't forget to free what you allocate */
}

(note: realloc() can be used for the first allocation so long as the pointer is set NULL)

Example Use/Output

Using your example you could do:

$ echo "200 53 65 98 183 37 122 14 124 65 67" | ./bin/dynarrint
  0 - 200
  1 - 53
  2 - 65
  3 - 98
  4 - 183
  5 - 37
  6 - 122
  7 - 14
  8 - 124
  9 - 65
 10 - 67

Memory Use/Error Check

In any code you write that dynamically allocates memory, you have 2 responsibilities regarding any block of memory allocated: (1) always preserve a pointer to the starting address for the block of memory so, (2) it can be freed when it is no longer needed.

It is imperative that you use a memory error checking program to ensure you do not attempt to access memory or write beyond/outside the bounds of your allocated block, attempt to read or base a conditional jump on an uninitialized value, and finally, to confirm that you free all the memory you have allocated.

For Linux valgrind is the normal choice. There are similar memory checkers for every platform. They are all simple to use, just run your program through it.

$ echo "200 53 65 98 183 37 122 14 124 65 67" | valgrind ./bin/dynarrint
==12405== Memcheck, a memory error detector
==12405== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==12405== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==12405== Command: ./bin/dynarrint
==12405==
  0 - 200
  1 - 53
  2 - 65
  3 - 98
  4 - 183
  5 - 37
  6 - 122
  7 - 14
  8 - 124
  9 - 65
 10 - 67
==12405==
==12405== HEAP SUMMARY:
==12405==     in use at exit: 0 bytes in 0 blocks
==12405==   total heap usage: 6 allocs, 6 frees, 5,240 bytes allocated
==12405==
==12405== All heap blocks were freed -- no leaks are possible
==12405==
==12405== For counts of detected and suppressed errors, rerun with: -v
==12405== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

Always confirm that you have freed all memory you have allocated and that there are no memory errors.

Upvotes: 0

pm100
pm100

Reputation: 50120

There are 2 classic solutions 2 this

First is to have a number in the file that indicates how many numbers there are and to size the array using VLA. ie

  int num;
  fscanf(myFile, "%d", &num);
  int numbers[num];

(Should check that return from fscanf is 1)

second is to dynamically allocate space using malloc - say 5 to start - and keep doubling the size of the array using realloc until you reach end of file

  int size = 5;
  int count = 0;
  int *numbers = malloc(size * sizeof(int));
  int numin;
  while (fscanf(myFile, "%d", &numin) != EOF){
     if(count== size){
        size *= 2; 
        numbers = realloc(numbers, size * sizeof(int));
     }
     count++;
     numbers[count] = numin;
  }
   

Upvotes: 1

Francis
Francis

Reputation: 54

The question may be a bit cloudy but I think you want to know how do you manage to read variable numbers of data from a file. There are a number of ways to manage that actually...some of them are:

  1. Read the file and only count the numbers before allocating (without reading data). then allocate the array and read the data.
  2. Dynamically allocate and reallocate twice the size of array every time the array comes to be short.
  3. Use a List :)

Approach 1:

   int count = 0;
   int var; 
   while(fscanf(myFile, "%d", &var) == 1){   //included only a snippet instead of full code   
      count++;  
   }  
   int numberArray[count];
   //iterate again and read the data

Approach 2:

int curr_size = 2; //you can take any positive integer, but be wary of heap overflow(large allocation)
int i = 0;
int* varr = malloc (sizeof(int) * curr_size);
while(fscanf(myFile, "%d", &varr[i++]) == 1){   
    if(i == curr_size){  
        curr_size *= 2; //you can do + instead of *, depends on what you're dealing with... '*' can sometimes lead to unnecessary allocation of big chunks of memory 
        int* temp = realloc(varr, sizeof(int)*curr_size); 
        if(temp == NULL) { printf("Error allocating"); return -1;} 
        free(varr);
        varr = temp;
    }   
}
// 'i' will give you total numbers read you can realloc if you like  
//if you use it for reading string be sure to account for '\0'    //if you use it for reading string be sure to account for '\0'

Approach 3: Use List Data structure to store variable number of data. that will be pretty easier

Approach 2 is one of most used ways between programmers to read variable sized strings(char sequences) from a source in c.
Approach 1 will be easier for beginners.
Approach 3 will be easier for reading integers.
Trying all will help you grow.

Upvotes: 2

Related Questions