问题
I am trying to program a tool in C. Part of this program is to use a text file and read it line by line, while storing all lines into an array to have it available for future use.
That's what I have so far:
int main(){
FILE *fp = fopen("file.txt", "ab+");
if (fp == NULL) {
printf("FILE ERROR");
return 1;
}
int lines = 0;
int ch = 0;
while(!feof(fp)){
ch = fgetc(fp);
if(ch == '\n'){
lines++;
}
}
printf("%d\n", lines);
if (lines>0){
int i = 0;
int numProgs = 0;
char* programs[lines];
char line[lines];
FILE *file;
file = fopen("file.txt", "r");
while(fgets(line, sizeof(line), file) != NULL){
programs[i] = strdup(line);
i++;
numProgs++;
}
for (int j= 0; j<sizeof(programs); j++){
printf("%s\n", programs[j]);
}
fclose(file);
fclose(fp);
return 0;
}
My problem is im getting this output:
6
(the number of lines in the file)
Segmentation fault
How can I read a complete line by line , without knowing how long the line is in the beginning. in PHP I can do that very easily, but how can I do that in C?
Thanks for any hint!
回答1:
fix like this:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void){
FILE *fp = fopen("file.txt", "r");//!
if (fp == NULL) {
fprintf(stderr, "FILE ERROR\n");
return 1;
}
int lines = 0;
int ch = 0;
int len = 0;//! length of line
int max_len = 0;//! max length of line
while((ch = fgetc(fp))!=EOF){//!
++len;
if(ch == '\n'){
if(max_len < len)
max_len = len;
++lines;
len = 0;
}
}
if(len)
++lines;
fprintf(stderr, "%d lines.\n", lines);
if (lines > 0){
int numProgs = 0;
char *programs[lines];//use malloc, char **programs = malloc(lines * sizeof(*programs));
char line[max_len+1];//!
rewind(fp);//!
while(fgets(line, sizeof(line), fp))
programs[numProgs++] = strdup(line);//!
for (int j= 0; j < numProgs; j++){//!
printf("%s", programs[j]);//!
free(programs[j]);//!
}
}
fclose(fp);
return 0;
}
回答2:
If you truly want to read an unknown number of characters from an unknown number of lines and store those lines in an array (or, actually, in an object created from a pointer-to-pointer-to-char), then you have a number of options. POSIX getline
is a line oriented input function (like fgets
) which will read a line of text from the give file each time it is called, and will allocate sufficient storage to hold the line regardless of the length. (as a bonus getline
returns the actual number of characters read, eliminating a subsequent call to strlen
if the length is needed)
getline
eliminates the need for repeated checks on whether fgets
actually read the whole line, or just a partial. Further, if your lines are more than a few characters long, the buffered read provided by getline
(and fgets
) is quite a bit faster than character oriented input (e.g. fgetc
). Don't get me wrong, there is nothing wrong with fgetc
, and if your files are small and your lines short, you are not going to notice any difference. However, if you are reading a million lines of 500,000 chars each -- you will notice a significant difference.
As for an array, since you don't know how many lines you will read, you really need a pointer-to-pointer-to-char (e.g a double-ponter, char **array
) so you can allocate some reasonable number of pointers to begin with, allocate and assign the lines to individual pointer until your limit is reached, then realloc
array to increase the number of pointers available, and keep on reading/storing lines.
As with any code that dynamically allocates memory, your must (1) preserve a pointer to each block of memory allocated, so (2) the memory can be freed with no longer in use. You should also validate each allocation (and reallocation) to insure the allocations succeed. When using realloc
, always use a temporary pointer so you can validate that realloc
succeeds before assigning the new block to the original pointer. If you don't, and realloc
fails, you have lost the pointer to your original block of memory that is left untouched, not freed, and you have just created a memory leak.
Lastly, always verify your memory use with a memory error check program such as valgrind
on Linux. There are a number of subtle ways to misuse a block of memory.
Putting all that together, you could do something like the following. The code will read all lines from the filename provided as the first argument (or from stdin
if no filename is given):
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
enum { MAXA = 128 }; /* initial allocation size, MAXA must be >= 1 */
int main (int argc, char **argv) {
char *line = NULL;
char **arr = NULL;
size_t i, maxa = MAXA, n = 0, ndx = 0;
ssize_t nchr = 0;
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;
}
/* allocate MAXA pointers to char -- initially & validate */
if (!(arr = calloc (maxa, sizeof *arr))) {
fprintf (stderr, "error: virtual memory exhausted.\n");
return 1;
}
while ((nchr = getline (&line, &n, fp)) != -1) { /* read each line */
while (line[nchr-1] == '\n') line[--nchr] = 0; /* remove '\n' */
if (!(arr[ndx] = strdup (line))) { /* allocate, copy, add to arr */
fprintf (stderr, "error: virtual memory exhausted.\n");
break; /* leave read loop, preserving existing arr */
}
if (++ndx == maxa) { /* if allocation limit reached, realloc arr */
size_t asz = sizeof *arr;
void *tmp = realloc (arr, (maxa + MAXA) * asz);
if (!tmp) { /* validate realloc succeeded */
fprintf (stderr, "error: realloc, memory exhausted.\n");
break; /* preserving original arr */
}
arr = tmp; /* assign & zero (optional) new memory */
memset (arr + (maxa + MAXA) * asz, 0, MAXA * asz);
maxa += MAXA; /* update current allocation limit */
}
}
if (fp != stdin) fclose (fp); /* close file if not stdin */
if (line) free (line); /* free mem allocated by getline */
for (i = 0; i < ndx; i++) /* output array */
printf (" arr[%4zu] : %s\n", i, arr[i]);
for (i = 0; i < ndx; i++) /* free allocated memory */
free (arr[i]); /* free each line */
free (arr); /* free pointers */
return 0;
}
Example Use/Output
$ ./bin/getline_realloc_arr < dat/words_554.txt
arr[ 0] : Aam
arr[ 1] : Aard-vark
arr[ 2] : Aard-wolf
arr[ 3] : Aaronic
...
arr[ 549] : Accompaniment
arr[ 550] : Accompanist
arr[ 551] : Accompany
arr[ 552] : Accompletive
arr[ 553] : Accomplice
Look things over and let me know if you have any questions.
回答3:
Try Online
#include <stdio.h>
#include <stdlib.h>
char * readLine (FILE * file)
{
size_t len = 0;
int c = 0, i = 0;
long pos = ftell(file);
char * out = 0;
// read the whole line
do { c = fgetc(file); len++; }
while (c!='\0' && c!='\n' && c!=EOF);
// if the cursor didn't move return NULL
if (pos == ftell(file) && c == EOF) return 0;
// allocate required memory
out = (char*)malloc(len+1);
// rewind cursor to beginning of line
fseek (file, pos, SEEK_SET);
// copy the line
do { out[i++] = fgetc(file); }
while (c!='\0' && c!='\n' && c!=EOF);
// make sure there's \0 at the end
out[i] = '\0';
return out;
}
int main (void)
{
// FILE * file = fopen("test.txt", "r");
char * line = readLine(stdin);
while(line)
{
printf(line); // print current line
free(line); // free allocated memory
line = readLine(stdin); // recur
}
return 0;
}
回答4:
Read up on malloc
/ realloc
and friends.
A first approach for reading a single line might be something along the lines of the following (note that this is a toy program, and as such omits error-checking):
size_t line_length = 0;
char *line = NULL;
char ch;
while ((ch = fgetc(fp)) != '\n') {
line = realloc(line, line_length+1);
line[line_length++] = ch;
}
// Add null character at end of line
line = realloc(line, line_length+1);
line[line_length] = 0;
The biggest problem with this is that it's slow, and especially slow for long lines. A better approach would be to keep track of the allocated and written size, and exponentially-grow size of the array as necessary, and then trim to the actual required length at the end.
Also, it'd probably be better (and simpler) to use fgets for that approach.
For reading multiple lines, you can nest this approach.
来源:https://stackoverflow.com/questions/38775052/c-read-entire-line-of-file