[英]Creating a function in C that reads a file but it infinitely loops
我基本上想打印出我制作的文件中的所有行,但它只是一遍又一遍地循环回到开头,基本上因为在 function 我设置了fseek(fp, 0, SEEK_SET);
这部分,但我不知道我将如何放置它以通过所有其他线路,我基本上每次都回到开始。
#include<stdio.h>
#include <stdlib.h>
char *freadline(FILE *fp);
int main(){
FILE *fp = fopen("story.txt", "r");
if(fp == NULL){
printf("Error!");
}else{
char *pInput;
while(!feof(fp)){
pInput = freadline(fp);
printf("%s\n", pInput); // outpu
}
}
return 0;
}
char *freadline(FILE *fp){
int i;
for(i = 0; !feof(fp); i++){
getc(fp);
}
fseek(fp, 0, SEEK_SET); // resets to origin (?)
char *pBuffer = (char*)malloc(sizeof(char)*i);
pBuffer = fgets(pBuffer, i, fp);
return pBuffer;
}
这是我到目前为止的工作
继续我的评论,你的想法是正确的,你只是没有按照正确的顺序把各个部分放在一起。 在main()
中,您正在循环调用 function,分配一行,然后输出一行文本并一遍又一遍地执行此操作。 (并且没有释放您分配的任何 memory - 创建每行读取的 memory 泄漏)
如果您正在分配存储来保存该行,您通常希望通过文件分配和存储所有行,并返回指向您的行集合的指针,以便在main()
中打印main()
(或任何调用 function 是)。
您可以通过添加一个额外的间接级别并让您的 function 返回char **
来做到这一点。 这是一个简单的两步过程,您可以:
分配一个包含指针的 memory 块(每行一个指针)。 由于您事先不知道有多少行,因此您只需分配一些指针,然后在realloc()
时重新分配更多指针;
对于您读取的每一行,您分配length + 1
字符的存储空间并将当前行复制到 memory 的该块中,将该行的地址分配给您的下一个可用指针。
(您要么跟踪分配的指针和行数,要么在最后一个指针分配一行后提供一个设置为NULL
的附加指针作为哨兵——由您决定,只需使用计数器跟踪可能在概念上更容易)
阅读最后一行后,您只需将指针返回到分配给调用者使用的指针集合。 (您也可以将char **
的地址作为参数传递给您的 function,导致类型为char ***
,但成为三星级程序员并不总是一种恭维)。 但是,这样做并没有错,在某些情况下,它是必需的,但如果你有替代方案,那通常是首选路线。
那么这将如何在实践中发挥作用呢?
只需将 function 返回类型更改为char **
并将附加指针传递给计数变量,以便在从 function 返回之前使用读取的行数更新该地址处的值。 例如,你可以这样做:
char **readfile (FILE *fp, size_t *n);
这会将您的文件指针指向打开的文件 stream ,然后从中读取每一行,为该行分配存储空间并将该分配的地址分配给您的一个指针。 在 function 中,您将使用足够大的字符数组来保存您使用fgets()
读取的每一行。 从末尾修剪'\n'
并获取长度,然后分配length + 1
个字节来保存该行。 将新分配的块的地址分配给一个指针,然后从您的数组复制到新分配的块。
( strdup()
既可以分配也可以复制,但它不是标准库的一部分,它是 POSIX ——尽管如果您提供正确的选项,大多数编译器都支持它作为扩展)
在readfile()
下面 function 把它放在一起,从一个指针开始,当你用完时重新分配两倍的当前数字(这提供了所需的分配数量和指针数量之间的合理权衡。(仅 20 次调用后)到realloc()
,你将有 1M 指针)。你可以选择任何你喜欢的重新分配和增长方案,但你要避免为每一行调用realloc()
—— realloc()
仍然是一个相对昂贵的调用。
#define MAXC 1024 /* if you need a constant, #define one (or more) */
#define NPTR 1 /* initial no. of pointers to allocate */
/* readfile reads all lines from fp, updating the value at the address
* provided by 'n'. On success returns pointer to allocated block of pointers
* with each of *n pointers holding the address of an allocated block of
* memory containing a line from the file. On allocation failure, the number
* of lines successfully read prior to failure is returned. Caller is
* responsible for freeing all memory when done with it.
*/
char **readfile (FILE *fp, size_t *n)
{
char buffer[MAXC], **lines; /* buffer to hold each line, pointer */
size_t allocated = NPTR, used = 0; /* allocated and used pointers */
lines = malloc (allocated * sizeof *lines); /* allocate initial pointer(s) */
if (lines == NULL) { /* validate EVERY allocation */
perror ("malloc-lines");
return NULL;
}
while (fgets (buffer, MAXC, fp)) { /* read each line from file */
size_t len; /* variable to hold line-length */
if (used == allocated) { /* is pointer reallocation needed */
/* always realloc to a temporary pointer to avoid memory leak if
* realloc fails returning NULL.
*/
void *tmp = realloc (lines, 2 * allocated * sizeof *lines);
if (!tmp) { /* validate EVERY reallocation */
perror ("realloc-lines");
break; /* lines before failure still good */
}
lines = tmp; /* assign reallocted block to lines */
allocated *= 2; /* update no. of allocated pointers */
}
buffer[(len = strcspn (buffer, "\n"))] = 0; /* trim \n, save length */
lines[used] = malloc (len + 1); /* allocate storage for line */
if (!lines[used]) { /* validate EVERY allocation */
perror ("malloc-lines[used]");
break;
}
memcpy (lines[used], buffer, len + 1); /* copy buffer to lines[used] */
used++; /* increment used no. of pointers */
}
*n = used; /* update value at address provided by n */
/* can do final realloc() here to resize exactly to used no. of pointers */
return lines; /* return pointer to allocated block of pointers */
}
在main()
中,您只需传递文件指针和size_t
变量的地址,并在迭代指针之前检查返回值,以使用您需要的任何行(它们简单地打印在下面),例如
int main (int argc, char **argv) {
char **lines; /* pointer to allocated block of pointers and lines */
size_t n; /* number of lines read */
/* 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 ("file open failed");
return 1;
}
lines = readfile (fp, &n);
if (fp != stdin) /* close file if not stdin */
fclose (fp);
if (!lines) { /* validate readfile() return */
fputs ("error: no lines read from file.\n", stderr);
return 1;
}
for (size_t i = 0; i < n; i++) { /* loop outputting all lines read */
puts (lines[i]);
free (lines[i]); /* don't forget to free lines */
}
free (lines); /* and free pointers */
return 0;
}
(注意:完成后不要忘记释放您分配的 memory。当您调用在其他函数中分配的函数时,这变得至关重要。在main()
中,memory 将在退出时自动释放,但要养成良好的习惯.)
示例使用/输出
$ ./bin/readfile_allocate dat/captnjack.txt
This is a tale
Of Captain Jack Sparrow
A Pirate So Brave
On the Seven Seas.
该程序将读取任何文件,无论它有 4 行还是 400,000 行,直到系统的物理限制 memory(如果您的行长于 1023 个字符,请调整MAXC
)。
Memory 使用/错误检查
在您编写的任何动态分配 memory 的代码中,对于分配的 memory 的任何块,您有两个责任:(1)始终保留指向 ZCD69B4957F06CD818D7BF3D21980 块的起始地址的指针,所以它可以被释放,更需要。
您必须使用 memory 错误检查程序,以确保您不会尝试访问 memory 或写入超出/超出分配块的边界,尝试读取或基于未初始化值的条件跳转,最后确认释放所有已分配的 memory。
对于 Linux valgrind
是正常的选择。 每个平台都有类似的 memory 检查器。 它们都易于使用,只需通过它运行您的程序即可。
$ valgrind ./bin/readfile_allocate dat/captnjack.txt
==4801== Memcheck, a memory error detector
==4801== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==4801== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==4801== Command: ./bin/readfile_allocate dat/captnjack.txt
==4801==
This is a tale
Of Captain Jack Sparrow
A Pirate So Brave
On the Seven Seas.
==4801==
==4801== HEAP SUMMARY:
==4801== in use at exit: 0 bytes in 0 blocks
==4801== total heap usage: 10 allocs, 10 frees, 5,804 bytes allocated
==4801==
==4801== All heap blocks were freed -- no leaks are possible
==4801==
==4801== For counts of detected and suppressed errors, rerun with: -v
==4801== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
始终确认您已释放所有已分配的 memory 并且没有 memory 错误。
该示例使用的完整代码仅包含标头,但为了完整起见,将其包含在下面:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAXC 1024 /* if you need a constant, #define one (or more) */
#define NPTR 1 /* initial no. of pointers to allocate */
/* readfile reads all lines from fp, updating the value at the address
* provided by 'n'. On success returns pointer to allocated block of pointers
* with each of *n pointers holding the address of an allocated block of
* memory containing a line from the file. On allocation failure, the number
* of lines successfully read prior to failure is returned. Caller is
* responsible for freeing all memory when done with it.
*/
char **readfile (FILE *fp, size_t *n)
{
char buffer[MAXC], **lines; /* buffer to hold each line, pointer */
size_t allocated = NPTR, used = 0; /* allocated and used pointers */
lines = malloc (allocated * sizeof *lines); /* allocate initial pointer(s) */
if (lines == NULL) { /* validate EVERY allocation */
perror ("malloc-lines");
return NULL;
}
while (fgets (buffer, MAXC, fp)) { /* read each line from file */
size_t len; /* variable to hold line-length */
if (used == allocated) { /* is pointer reallocation needed */
/* always realloc to a temporary pointer to avoid memory leak if
* realloc fails returning NULL.
*/
void *tmp = realloc (lines, 2 * allocated * sizeof *lines);
if (!tmp) { /* validate EVERY reallocation */
perror ("realloc-lines");
break; /* lines before failure still good */
}
lines = tmp; /* assign reallocted block to lines */
allocated *= 2; /* update no. of allocated pointers */
}
buffer[(len = strcspn (buffer, "\n"))] = 0; /* trim \n, save length */
lines[used] = malloc (len + 1); /* allocate storage for line */
if (!lines[used]) { /* validate EVERY allocation */
perror ("malloc-lines[used]");
break;
}
memcpy (lines[used], buffer, len + 1); /* copy buffer to lines[used] */
used++; /* increment used no. of pointers */
}
*n = used; /* update value at address provided by n */
/* can do final realloc() here to resize exactly to used no. of pointers */
return lines; /* return pointer to allocated block of pointers */
}
int main (int argc, char **argv) {
char **lines; /* pointer to allocated block of pointers and lines */
size_t n; /* number of lines read */
/* 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 ("file open failed");
return 1;
}
lines = readfile (fp, &n);
if (fp != stdin) /* close file if not stdin */
fclose (fp);
if (!lines) { /* validate readfile() return */
fputs ("error: no lines read from file.\n", stderr);
return 1;
}
for (size_t i = 0; i < n; i++) { /* loop outputting all lines read */
puts (lines[i]);
free (lines[i]); /* don't forget to free lines */
}
free (lines); /* and free pointers */
return 0;
}
如果您还有其他问题,请告诉我。
如果你想读到最后一行,你可以简单地使用getc()
。 当到达文件末尾或读取失败时,它返回 EOF。 因此,如果使用getc()
来确保到达文件末尾,最好使用feof()
,如果到达文件末尾则返回非零值,否则返回 0。
例子:-
int main()
{
FILE *fp = fopen("story.txt", "r");
int ch = getc(fp);
while (ch != EOF)
{
/* display contents of file on screen */
putchar(ch);
ch = getc(fp);
}
if (feof(fp))
printf("\n End of file reached.");
else
printf("\nError while reading!");
fclose(fp);
getchar();
return 0;
}
你也可以fgets()
,下面是例子: -
#define MAX_LEN 256
int main(void)
{
FILE *fp = fopen("story.txt", "r");
if (fp == NULL) {
perror("Failed: "); //prints a descriptive error message to stderr.
return 1;
}
char buffer[MAX_LEN];
// -1 to allow room for NULL terminator for really long string
while (fgets(buffer, MAX_LEN - 1, fp))
{
// Remove trailing newline
buffer[strcspn(buffer, "\n")] = 0;
printf("%s\n", buffer);
}
fclose(fp);
return 0;
}
或者,
int main() {
int MAX_LEN = 255;
char buffer[MAX_LEN];
FILE *fp = fopen("story.txt", "r");
while(fgets(buffer, MAX_LEN, fp)) {
printf("%s\n", buffer);
}
fclose(fp);
return 0;
}
更多详情请参考C 逐行读取文件
您的方法是错误的,很可能会产生无限循环。 我将解释为什么使用原始代码和内联注释:
char *freadline(FILE *fp){
int i;
// This part attempts to count the number of characters
// in the whole file by reading char-by-char until EOF is set
for(i = 0; !feof(fp); i++){
getc(fp);
}
// Here EOF is set
// This returns to the start and clears EOF
fseek(fp, 0, SEEK_SET);
// Here EOF is cleared
char *pBuffer = (char*)malloc(sizeof(char)*i);
// Here you read a line, i.e. you read characters until (and including) the
// first newline in the file.
pBuffer = fgets(pBuffer, i, fp);
// Here EOF is still cleared as you only read the first line of the file
return pBuffer;
}
所以在你做的时候main
是
while(!feof(fp)){
...
}
你有一个无限循环,因为feof
是错误的。 您的程序将一次又一次地打印同一行,并且您有 memory 泄漏,因为您从不调用free(pInput)
。
所以你需要重新设计你的代码。 阅读fgets
做了什么,例如这里https://man7.org/linux/man-pages/man3/fgets.3p.html
需要解决的一些问题:
使用fgets
并不能保证您在 function 返回后读取一行。 因此,如果您真的想检查您是否阅读了完整的行,请检查返回字符串中的字符数,并检查字符串末尾是否存在换行符。
您对fseek
的使用在这里很有趣,因为它的作用是告诉 stream 指向 go 的指针回到文件的开头,并从那里开始读取。 这意味着在第一次调用freadline
function 后,您将继续每次从文件中读取第一个字节。
最后,你的程序像贪婪的婴儿一样囤积memory! 您永远不会释放您所做的任何分配!
话虽如此,这是一个改进的freadline
实现:
char *freadline(FILE *fp) {
/* initializations */
char buf[BUFSIZ + 1];
char *pBuffer;
size_t size = 0, tmp_size;
/* fgets returns NULL when it reaches EOF, so our loop is conditional
* on that
*/
while (fgets (buf, BUFSIZ + 1, fp) != NULL) {
tmp_size = strlen (buf);
size += tmp_size;
if (tmp_size != BUFSIZ || buf[BUFSIZ] == '\n')
break;
}
/* after breaking from loop, check that size is not zero.
* this should only happen if we reach EOF, so return NULL
*/
if (size == 0)
return NULL;
/* Allocate memory for the line plus one extra for the null byte */
pBuffer = malloc (size + 1);
/* reads the contents of the file into pBuffer */
if (size <= BUFSIZ) {
/* Optimization: use memcpy rather than reading
* from disk if the line is small enough
*/
memcpy (pBuffer, buf, size);
} else {
fseek (fp, ftell(fp) - size, SEEK_SET);
fread (pBuffer, 1, size, fp);
}
pBuffer[size] = '\0'; /* set the null terminator byte */
return pBuffer; /* remember to free () this when you are done! */
}
这种方式不需要对feof
的额外调用(这通常是命中注定的),而是依赖于fgets
返回的内容来确定我们是否已经到达文件末尾。
通过这些更改,将main
更改为:
int main() {
FILE *fp = fopen("story.txt", "r");
if(fp == NULL){
printf("Error!");
}else{
char *pInput;
/* here we just keep reading until a NULL string is returned */
for (pInput = freadline(fp); pInput != NULL; pInput = freadline(fp))) {
printf("%s", pInput); // output
free (pInput);
}
fclose(fp);
}
return 0;
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.