David
David

Reputation: 173

Read words from file and return them as char pointers

I'm making a function char** read_from_file(char* fname, int * size) that reads all the words from a file fname and returns them as char**. My file only has 5 words, there is only one word per line. I then have another function print_strings(char** words, int num_words) that prints the strings.

I'm having 3 problems:

  1. When I am comparing the index to < *size I get "comparison between pointer and integer"

  2. I can't store the words in the **words

  3. I'm not sure how to return all the words.

This is my code:

void test_sort(char* fname){
    int i;   
    int num_words;
    char** words = read_from_file(fname, &num_words);

    printf("\n ORIGINAL data:\n");
    print_strings(words, num_words);
}

In Main:

int main(){    

    // test sorting array of string by string length
    test_sort("data.txt");
}

Reading Function

char** read_from_file(char* fname, int * size)  { 

    char** words = (char **)malloc(N_MAX);
    FILE *ifp;
    ifp = fopen(fname, "r");
    if(ifp == NULL){
        fprintf(stderr, "Can't open file\n");
        exit(1);
    }

    int index;

    while (!feof(ifp)){
        for(index = 0; index < size; index++)
        {
            fscanf(ifp,"%s", words[index]);
        }
    }
    fclose(ifp);

    return words;
}

Upvotes: 2

Views: 1642

Answers (1)

David C. Rankin
David C. Rankin

Reputation: 84579

When you allocate an array of pointer-to-pointer-to-char (e.g. char **words;), you must allocate memory for the array of pointers:

char **words = malloc (N_MAX * sizeof *words);

as well as each array of characters (or string) pointed to by each pointer:

words[index] = malloc ((N_MAX + 1) * sizeof **words);

or simply:

words[index] = malloc (N_MAX + 1);

or when allocating memory for a null-terminated string, a shortcut with strdup that both allocates sufficient memory to hold the string and copies the string (including the null-terminating character) to the new block of memory, returning a pointer to the new block:

words[index] = strdup (buf);

A short example of your intended functions could be as follows (note, index is passed as a pointer below, so it must be deferenced to obtain its value *index):

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

#define N_MAX 10
#define C_MAX 64

void test_sort(char* fname);
char** read_from_file(char* fname, size_t *index);

int main (void) {    

    test_sort ("data.txt");
    return 0;
}

void test_sort (char* fname)
{
    size_t i = 0;  
    size_t num_words = 0;

    char **words = read_from_file (fname, &num_words);

    printf("\n ORIGINAL data:\n\n");
    // print_strings(words, num_words);
    for (i = 0; i < num_words; i++)
        printf ("   words[%2zu] : %s\n", i, words[i]);
    putchar ('\n');

    /* free allocated memory */
    for (i = 0; i < num_words; i++)
        free (words[i]);
    free (words);
}

char** read_from_file (char* fname, size_t *index)
{ 
    char **words = malloc (N_MAX * sizeof *words);
    char buf[C_MAX] = {0};
    FILE *ifp = fopen (fname, "r");

    if (ifp == NULL){
        fprintf (stderr, "Can't open file\n");
        exit(1);
    }

    *index = 0;
    while (fgets (buf, C_MAX, ifp))
    {
        char *p = buf;  /* strip trailing newline/carriage return */
        size_t len = strlen (p);
        while (len && (p[len-1] == '\r' || p[len-1] == '\n'))
            p[--len] = 0;

        /* strdup allocates and copies buf */
        words[(*index)++] = strdup (buf);

        if (*index == N_MAX) {
            fprintf (stderr, "warning: N_MAX words read.\n");
            break;
        }
    }
    fclose(ifp);

    return words;
}

Input

$ cat data.txt
A quick
brown fox
jumps over
the lazy
dog.

Output

$ ./bin/read5str

 ORIGINAL data:

   words[ 0] : A quick
   words[ 1] : brown fox
   words[ 2] : jumps over
   words[ 3] : the lazy
   words[ 4] : dog.

Memory Error Check

In any code your write that dynamically allocates memory, you have 2 responsibilites regarding any block of memory allocated: (1) always preserves 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 insure you haven't written beyond/outside your allocated block of memory and to confirm that you have freed all the memory you have allocated. For Linux valgrind is the normal choice. There are so many subtle ways to misuse a block of memory that can cause real problems, there is no excuse not to do it. There are similar memory checkers for every platform. They are all simple to use. Just run your program through it.

$ valgrind ./bin/read5str
==5507== Memcheck, a memory error detector
==5507== Copyright (C) 2002-2012, and GNU GPL'd, by Julian Seward et al.
==5507== Using Valgrind-3.8.1 and LibVEX; rerun with -h for copyright info
==5507== Command: ./bin/read5str
==5507==

 ORIGINAL data:

   words[ 0] : A quick
   words[ 1] : brown fox
   words[ 2] : jumps over
   words[ 3] : the lazy
   words[ 4] : dog.

==5507==
==5507== HEAP SUMMARY:
==5507==     in use at exit: 0 bytes in 0 blocks
==5507==   total heap usage: 7 allocs, 7 frees, 691 bytes allocated
==5507==
==5507== All heap blocks were freed -- no leaks are possible
==5507==
==5507== For counts of detected and suppressed errors, rerun with: -v
==5507== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 2 from 2)

The salient parts above are that there were 7 allocs, 7 frees and All heap blocks were freed. Further ERROR SUMMARY: 0 errors from 0 contexts. You should receive similar output every time. Let me know if you have additional questions.

Upvotes: 3

Related Questions