简体   繁体   中英

Using fgetc() for reading into an array in C

I wanna use fgetc() and put the input character in some array like char *ch . But apparently I can't put character (like 'M') in array and it has to be like "M", I mean with double quotation like a string.
This is my code, after getting user input I got Segmentation fault (core dumped)
So I want to know if there is any way or trick that could do that.
Thanks in advance. :)

void main(){
    char *ch;
    int i = 0;
    while((ch[i] = fgetc(stdin)) != '\n'){
        i++;
    }
    return;
}

In char *ch , ch is a pointer and it's value should be a valid memory location to store data in that memory location. Allocating memory dynamically to ch using malloc() or realloc() (use realloc if ch points to NULL initially) is necessary before using that pointer. Otherwise ch might contain garbage value and accessing that memory location results in Segmentation fault . I made changes to your program and it might help you.

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

int main(){
    char *ch = NULL;
    int i = 0;
    ch = realloc(ch, i+1);
    while((ch[i] = fgetc(stdin)) != '\n'){
        ch = realloc(ch, i+1);
        i++;
    }
    ch[i] = 0;
    puts(ch);
    free(ch);
    return 0;
}

As David C. Rankin said in the below comment:

Calling realloc for every character read is horribly wasteful. Allocate 128, 256, ... 1024 (or some reasonably expected number of characters) read until the limit is reached and then realloc some sane additional number of characters, then repeat...

While you can realloc for every character you read, that is a horribly inefficient way to allocate memory. A call to malloc is relatively expensive compared to an assignment of a character to an array element. It is far better to allocate some reasonably anticipated number of characters, read until your reach the limit, realloc at that point (updating the limit), and repeat until EOF (or whatever termination you choose).

The implementation is straight forward, but there are a couple of subtleties to note. When reallocating, always assign the result of realloc to a temporary pointer so you can validate that realloc succeeded before proceeding. Why? If realloc fails, it returns NULL . If you just blindly assign the result to your original array, and realloc fails, (1) you have just lost the pointer to your original array; and (2) you have just created a memory leak because the original block is left untouched -- it is not freed or moved.

With that in mind, you could read characters into an array, reallocating as needed, until EOF is reaches using something like the following:

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

#define NCHAR 1024  /* must be at least 1 */

int main (int argc, char **argv) {

    int c;
    size_t n = 0, nchar = NCHAR;
    char *arr = malloc (sizeof *arr * nchar);
    FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;

    if (!fp) {  /* validate file open for reading */
        fprintf (stderr, "error: file open failed '%s'.\n", argv[1]);
        return 1;
    }

    if (!arr) { /* validate memory allocation succeeded */
        fprintf (stderr, "error: virtual memory exhausted.\n");
        return 1;
    }

    while ((c = fgetc (fp)) != EOF) {  /* for each char in file */
        arr[n++] = c;       /* assign char to array */

        if (n == nchar) {   /* realloc preserving space for nul */
            void *tmp = realloc (arr, nchar + NCHAR);
            if (!tmp) {           /* validate realloc succeeded */
                fprintf (stderr, "realloc() error: memory exhausted.\n");
                break; /* break read loop, using existing 'arr' */
            }
            arr = tmp;     /* assign reallocated pointer to arr */
            nchar += NCHAR;        /* update the value of nchar */
        }
    }
    arr[n] = 0;                  /* affirmatively nul-terminate */

    if (fp != stdin) fclose (fp);    /* close file if not stdin */

    for (size_t i = 0; i < n; i++)   /* output arr */
    putchar (arr[i]);

    free (arr);   /* free allocated memory */

    return 0;
}

( note: above, on failure, the read loop is exited, preserving all characters in arr read up to that point in time.)

Lastly, in any code your 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 insure you haven't written beyond/outside your allocated block of memory, attempted to read or base a jump on an unintitialized value and finally to confirm that you have freed all the memory you have allocated. (For Linux valgrind is the normal choice.)

A simple example (with NCHAR set to 2 to force multiple reallocations) would be:

$ valgrind ./bin/fgetc_realloc <../dat/captnjack.txt
==19710== Memcheck, a memory error detector
==19710== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al.
==19710== Using Valgrind-3.10.1 and LibVEX; rerun with -h for copyright info
==19710== Command: ./bin/fgetc_realloc
==19710==
This is a tale
Of Captain Jack Sparrow
A Pirate So Brave
On the Seven Seas.
==19710==
==19710== HEAP SUMMARY:
==19710==     in use at exit: 0 bytes in 0 blocks
==19710==   total heap usage: 39 allocs, 39 frees, 1,560 bytes allocated
==19710==
==19710== All heap blocks were freed -- no leaks are possible
==19710==
==19710== For counts of detected and suppressed errors, rerun with: -v
==19710== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 1 from 1)

You want to find All heap blocks were freed -- no leaks are possible and ERROR SUMMARY: 0 errors from 0 contexts (noting: on some OS, valgrind will not report 0 due to incomplete memory exclusion files)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM