Cファイルを1行ずつ読み取る


184

ファイルから行を読み取るために、この関数を書きました。

const char *readLine(FILE *file) {

    if (file == NULL) {
        printf("Error: file pointer is null.");
        exit(1);
    }

    int maximumLineLength = 128;
    char *lineBuffer = (char *)malloc(sizeof(char) * maximumLineLength);

    if (lineBuffer == NULL) {
        printf("Error allocating memory for line buffer.");
        exit(1);
    }

    char ch = getc(file);
    int count = 0;

    while ((ch != '\n') && (ch != EOF)) {
        if (count == maximumLineLength) {
            maximumLineLength += 128;
            lineBuffer = realloc(lineBuffer, maximumLineLength);
            if (lineBuffer == NULL) {
                printf("Error reallocating space for line buffer.");
                exit(1);
            }
        }
        lineBuffer[count] = ch;
        count++;

        ch = getc(file);
    }

    lineBuffer[count] = '\0';
    char line[count + 1];
    strncpy(line, lineBuffer, (count + 1));
    free(lineBuffer);
    const char *constLine = line;
    return constLine;
}

関数はファイルを正しく読み取り、printfを使用すると、constLine文字列も正しく読み取られたことがわかります。

ただし、たとえば次のように関数を使用すると、

while (!feof(myFile)) {
    const char *line = readLine(myFile);
    printf("%s\n", line);
}

printfは意味不明な出力をします。どうして?


fgets代わりに使用しますfgetc。行ごとではなく、文字ごとに読んでいます。
Shiv

3
これgetline()はPOSIX 2008の一部であることに注意してください。特にPOSIX 2008の残りの部分をサポートしていないが、POSIXシステムの世界でgetline()は最近移植性が高い場合、POSIXに類似したプラットフォームが存在する可能性があります。
ジョナサンレフラー

回答:


305

あなたの仕事が行ごとの読み込み関数を発明することではなく、ファイルを行getline()ごとに読み込むことである場合、関数を含む典型的なコードスニペットを使用することができます(ここのマニュアルページを参照してください):

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

int main(void)
{
    FILE * fp;
    char * line = NULL;
    size_t len = 0;
    ssize_t read;

    fp = fopen("/etc/motd", "r");
    if (fp == NULL)
        exit(EXIT_FAILURE);

    while ((read = getline(&line, &len, fp)) != -1) {
        printf("Retrieved line of length %zu:\n", read);
        printf("%s", line);
    }

    fclose(fp);
    if (line)
        free(line);
    exit(EXIT_SUCCESS);
}

83
それはポータブルではありません。
JeremyP 2010

16
より正確には、これgetlineはGNU libc、つまりLinuxに固有です。ただし、意図が(Cを学習するのではなく)行読み機能を持つことである場合は、Web上で利用可能ないくつかのパブリックドメイン行読み機能があります。
Gilles「SO-邪悪なことをやめる」

11
なぜそれをすべきなのか?マニュアルを読んでください、バッファは各呼び出しで再割り当てされ、それは最後に解放されるべきです。
mbaitoff 2012年

29
if(line)チェックは余計です。呼び出しfree(NULL)は基本的に何もしません。
aroth

50
このgetlineはGNU libcに固有であると言った人のために、「getline()とgetdelim()はどちらももともとGNU拡張機能でした。それらはPOSIX.1-2008で標準化されました。」
willkill07 2015

37
FILE* filePointer;
int bufferLength = 255;
char buffer[bufferLength];

filePointer = fopen("file.txt", "r");

while(fgets(buffer, bufferLength, filePointer)) {
    printf("%s\n", buffer);
}

fclose(filePointer);

私にとって、これは各行を次の行で上書きすることになります。上記の回答に基づいてこの質問を参照してください。
Cezar Cobuz

5
なぜキャスト(FILE*) fp?はfpすでにa ではなくFILE *、またa をfopen()返しますFILE *か?
会計士م

1
行を特定の長さに制限しても問題ない場合は、これが最良の答えです。それ以外の場合getlineは、使用することをお勧めします。FILE *キャストは不要だと思う。
theicfire

不要なキャストを削除し、バッファー長の変数を追加して、わかりやすくfpするfilePointerために変更しました。
ロブ

21

あなたではreadLine機能、あなたはへのポインタを返すline(厳密に、その最初の文字へのポインタを言えば、その差は、ここでは関係ありません)配列。これは自動変数(つまり、「スタック上」)なので、関数が戻るとメモリが解放されます。printf自分のものをスタックに入れているので、意味不明なことがわかります。

関数から動的に割り当てられたバッファを返す必要があります。あなたはすでにそれを持っています、それはlineBufferです。あなたがしなければならないすべては、それを望ましい長さに切り詰めることです。

    lineBuffer[count] = '\0';
    realloc(lineBuffer, count + 1);
    return lineBuffer;
}

ADDED(コメントのフォローアップ質問への応答):readLine行を構成する文字へのポインターを返します。このポインターは、行の内容を処理するために必要なものです。またfree、これらのキャラクターが使用するメモリを使い終わったときに渡す必要があります。このreadLine関数の使用方法は次のとおりです。

char *line = readLine(file);
printf("LOG: read a line: %s\n", line);
if (strchr(line, 'a')) { puts("The line contains an a"); }
/* etc. */
free(line);
/* After this point, the memory allocated for the line has been reclaimed.
   You can't use the value of `line` again (though you can assign a new value
   to the `line` variable if you want). */

@鉄:私は私の答えに何かを追加しましたが、あなたの難しさが何であるかはわからないので、それはマークから外れているかもしれません。
Gilles「SO-邪悪なことをやめなさい」

@鉄:答えはあなたがそれを解放しないことです。返されたバッファがmallocされ、ansdが呼び出し元によって解放される必要があるという事実を(APIドキュメントで)文書化します。次に、readLine関数を使用する人は(うまくいけば!)Gillesが彼の回答に追加したスニペットに似たコードを記述します。
JeremyP 2010

15
//open and get the file handle
FILE* fh;
fopen_s(&fh, filename, "r");

//check if file exists
if (fh == NULL){
    printf("file does not exists %s", filename);
    return 0;
}


//read line by line
const size_t line_size = 300;
char* line = malloc(line_size);
while (fgets(line, line_size, fh) != NULL)  {
    printf(line);
}
free(line);    // dont forget to free heap memory

1
このコードにはいくつかの問題があります:fopen_sコードを移植不可能にします。printf書式指定子を探し、なりませんパーセント記号と、次の文字を印刷し、彼らがそうであるように。nullバイトは、行の残りのすべての文字を消し去ります。(nullバイトが発生する可能性がないことを教えてください!)
hagello

ちなみに、あなたは問題を解決していません。OPは、彼の関数の戻り値が消えると説明しています。私はあなたがこの問題に取り組んでいるとは思わない。
hagello 2016年

@Hartley私はこれが古いコメントであることを知っていますが、誰かが彼のコメントを読まないように追加して、ループ内で(行)を解放しようとしています。lineのメモリは、ループが始まる前に1回だけ割り当てられるため、ループが終了した後に1回だけ解放する必要があります。ループ内でラインを解放しようとすると、予期しない結果が生じます。free()がポインターを処理する方法に応じて。メモリの割り当てを解除し、ポインタを古い場所に向けたままにすると、コードが機能する場合があります。ポインタに別の値を割り当てると、メモリの別のセクションが上書きされます。
アラニアン

2
printf(line)は間違っています!こんなことしないで。これにより、コードが文字列形式の脆弱性にさらされ、印刷されたものを介してメモリに自由に直接読み書きできるようになります。%n /%pをファイルに入れ、ポインタをメモリ内の(ファイルからの文字列内の)メモリ内のアドレスに戻すと、そのコードを実行できます。
オキサガスト2018

10

readLine() 未定義の動作を引き起こすローカル変数へのポインタを返します。

あなたを回避するために:

  1. 呼び出し元関数で変数を作成し、そのアドレスを渡す readLine()
  2. line使用するメモリを割り当てるmalloc()-この場合lineは永続的です
  3. グローバル変数を使用しますが、一般的には悪い習慣です


4

この例のいくつかの問題:

  • printfに\ nを追加するのを忘れました。また、エラーメッセージはstderr ieに送信されます。fprintf(stderr, ....
  • (大物ではありませんが)fgetc()ではなくを使用することを検討してくださいgetc()getc()マクロでfgetc()あり、適切な関数です
  • getc()intso chを返すので、として宣言する必要がありintます。との比較EOFが正しく処理されるため、これは重要です。一部の8ビット文字セット0xFFは有効な文字として使用され(ISO-LATIN-1が例になります)、-1はに割り当てられた場合に有効EOFになります。0xFFchar
  • ラインで潜在的なバッファオーバーフローがあります

    lineBuffer[count] = '\0';

    行がちょうど128文字の場合、count実行される時点で128です。

  • 他の人が指摘したようにline、ローカルに宣言された配列です。ポインタを返すことはできません。

  • strncpy(count + 1)最大でコピーされますcount + 1文字が、それがヒットした場合に終了します'\0' 、あなたが設定されているためlineBuffer[count]'\0'あなたはそれが得ることはありません知っていますcount + 1。ただし、そうした場合でも、終了'\0'行われため、実行する必要があります。あなたはしばしば次のようなものを見ます:

    char buffer [BUFFER_SIZE];
    strncpy(buffer, sourceString, BUFFER_SIZE - 1);
    buffer[BUFFER_SIZE - 1] = '\0';
  • malloc()(ローカルchar配列の代わりに)返す行の場合、戻り値の型はchar*-をドロップする必要がありconstます。


2
void readLine(FILE* file, char* line, int limit)
{
    int i;
    int read;

    read = fread(line, sizeof(char), limit, file);
    line[read] = '\0';

    for(i = 0; i <= read;i++)
    {
        if('\0' == line[i] || '\n' == line[i] || '\r' == line[i])
        {
            line[i] = '\0';
            break;
        }
    }

    if(i != read)
    {
        fseek(file, i - read + 1, SEEK_CUR);
    }
}

これはどうですか?


2

これが私の数時間です...ファイル全体を1行ずつ読み取ります。

char * readline(FILE *fp, char *buffer)
{
    int ch;
    int i = 0;
    size_t buff_len = 0;

    buffer = malloc(buff_len + 1);
    if (!buffer) return NULL;  // Out of memory

    while ((ch = fgetc(fp)) != '\n' && ch != EOF)
    {
        buff_len++;
        void *tmp = realloc(buffer, buff_len + 1);
        if (tmp == NULL)
        {
            free(buffer);
            return NULL; // Out of memory
        }
        buffer = tmp;

        buffer[i] = (char) ch;
        i++;
    }
    buffer[i] = '\0';

    // Detect end
    if (ch == EOF && (i == 0 || ferror(fp)))
    {
        free(buffer);
        return NULL;
    }
    return buffer;
}

void lineByline(FILE * file){
char *s;
while ((s = readline(file, 0)) != NULL)
{
    puts(s);
    free(s);
    printf("\n");
}
}

int main()
{
    char *fileName = "input-1.txt";
    FILE* file = fopen(fileName, "r");
    lineByline(file);
    return 0;
}

1
fgetc代わりになぜ使用していますfgetsか?
theicfire、

1
const char *readLine(FILE *file, char* line) {

    if (file == NULL) {
        printf("Error: file pointer is null.");
        exit(1);
    }

    int maximumLineLength = 128;
    char *lineBuffer = (char *)malloc(sizeof(char) * maximumLineLength);

    if (lineBuffer == NULL) {
        printf("Error allocating memory for line buffer.");
        exit(1);
    }

    char ch = getc(file);
    int count = 0;

    while ((ch != '\n') && (ch != EOF)) {
        if (count == maximumLineLength) {
            maximumLineLength += 128;
            lineBuffer = realloc(lineBuffer, maximumLineLength);
            if (lineBuffer == NULL) {
                printf("Error reallocating space for line buffer.");
                exit(1);
            }
        }
        lineBuffer[count] = ch;
        count++;

        ch = getc(file);
    }

    lineBuffer[count] = '\0';
    char line[count + 1];
    strncpy(line, lineBuffer, (count + 1));
    free(lineBuffer);
    return line;

}


char linebuffer[256];
while (!feof(myFile)) {
    const char *line = readLine(myFile, linebuffer);
    printf("%s\n", line);
}

「line」変数は呼び出し関数で宣言されてから渡されるため、readLine関数は事前定義されたバッファを満たし、それを返すだけです。これは、ほとんどのCライブラリが機能する方法です。

私が知っている他の方法があります:

  • char line[]を静的として定義する(static char line[MAX_LINE_LENGTH] ->関数から戻った後の値を保持します)。->悪い、関数は再入可能ではなく、競合状態が発生する可能性がある-> 2つのスレッドから2回呼び出すと、結果が上書きされる
  • malloc()char型のラインをINGの[]、および関数を呼び出すには、それを解放- >あまりにも多くの高価なmallocのは、そして、別の関数にバッファを解放するために責任を委譲(最もエレガントな解決策を呼び出すことですmallocし、free同じ関数内の任意のバッファ上)

ところで、からの「明示的な」キャストchar*にはconst char*冗長です。

ところで、lineBuffer は必要ありません。malloc()定義するだけchar lineBuffer[128]なので、解放する必要はありません。

btw3は '動的サイズのスタック配列'(配列をとして定義char arrayName[some_nonconstant_variable])を使用しません。何をしているのか正確にわからない場合は、C99でのみ機能します。


1
'line'変数は呼び出し元の関数で宣言されてから渡されることに注意してください。おそらく、関数内の行のローカル宣言を削除しておく必要があります。また、あなたはバッファはあなたが渡しているということですどのくらいの機能を伝え、あなたが渡すバッファに対して長すぎる行を処理するための戦略を考える必要があります。
JeremyP

1

たとえば、行を読み取るにはANSI関数を使用する必要があります。fgets。呼び出し後、呼び出しコンテキストでfree()が必要です。例:

...
const char *entirecontent=readLine(myFile);
puts(entirecontent);
free(entirecontent);
...

const char *readLine(FILE *file)
{
  char *lineBuffer=calloc(1,1), line[128];

  if ( !file || !lineBuffer )
  {
    fprintf(stderr,"an ErrorNo 1: ...");
    exit(1);
  }

  for(; fgets(line,sizeof line,file) ; strcat(lineBuffer,line) )
  {
    if( strchr(line,'\n') ) *strchr(line,'\n')=0;
    lineBuffer=realloc(lineBuffer,strlen(lineBuffer)+strlen(line)+1);
    if( !lineBuffer )
    {
      fprintf(stderr,"an ErrorNo 2: ...");
      exit(2);
    }
  }
  return lineBuffer;
}

1

ファイルからコンテンツを読み取って取得するメソッドを実装する(input1.txt)

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

void testGetFile() {
    // open file
    FILE *fp = fopen("input1.txt", "r");
    size_t len = 255;
    // need malloc memory for line, if not, segmentation fault error will occurred.
    char *line = malloc(sizeof(char) * len);
    // check if file exist (and you can open it) or not
    if (fp == NULL) {
        printf("can open file input1.txt!");
        return;
    }
    while(fgets(line, len, fp) != NULL) {
        printf("%s\n", line);
    }
    free(line);
}

この助けを願っています。幸せなコーディング!


0

自動変数へのポインターを戻すのは間違いです。変数行はスタックに割り当てられ、関数が有効である限りのみ有効です。ポインターが返されるとすぐに別の場所にメモリが割り当てられるため、ポインターを返すことはできません。

const char* func x(){
    char line[100];
    return (const char*) line; //illegal
}

これを回避するには、ヒープに常駐するメモリへのポインタを返します。lineBufferを使用する場合、free()を呼び出すのはユーザーの責任です。または、行の内容を書き込むメモリアドレスを引数として渡すようにユーザーに要求することもできます。


不正な動作と未定義の動作には違いがあります^^。
Phong

0

私はグラウンド0からのコードが欲しいので、辞書の単語の内容を1行ずつ読み取るためにこれを行いました。

char temp_str [20]; //要件に応じてバッファサイズを変更でき、ファイル内の1行の長さを変更できます。

注:行を読み取るたびに、バッファーをNull文字で初期化しました。この機能は自動化できますが、概念実証が必要であり、プログラムをバイト単位で設計する必要があるためです。

#include<stdio.h>

int main()
{
int i;
char temp_ch;
FILE *fp=fopen("data.txt","r");
while(temp_ch!=EOF)
{
 i=0;
  char temp_str[20]={'\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0'};
while(temp_ch!='\n')
{
  temp_ch=fgetc(fp);
  temp_str[i]=temp_ch;
  i++;
}
if(temp_ch=='\n')
{
temp_ch=fgetc(fp);
temp_str[i]=temp_ch;
}
printf("%s",temp_str);
}
return 0;
}

ブラケットが適切な場所にある場合、プログラムは機能します;)例)int main() {
dylnmc

ちなみに、20個すべての '\ 0'を指定する必要はありません。あなたはただ書くことができます: codechar temp_str [20] = {'\ 0'}; code 配列宣言が機能する方法は、配列に含まれるより少ない要素で配列が初期化される場合、最後の要素が残りの要素を埋めることです。
アラニアン

char temp_str[20] = {0}また、文字配列全体をnullターミネータで埋めると思います。
Thu Yein Tun

0

私のゼロからの道具:

FILE *pFile = fopen(your_file_path, "r");
int nbytes = 1024;
char *line = (char *) malloc(nbytes);
char *buf = (char *) malloc(nbytes);

size_t bytes_read;
int linesize = 0;
while (fgets(buf, nbytes, pFile) != NULL) {
    bytes_read = strlen(buf);
    // if line length larger than size of line buffer
    if (linesize + bytes_read > nbytes) {
        char *tmp = line;
        nbytes += nbytes / 2;
        line = (char *) malloc(nbytes);
        memcpy(line, tmp, linesize);
        free(tmp);
    }
    memcpy(line + linesize, buf, bytes_read);
    linesize += bytes_read;

    if (feof(pFile) || buf[bytes_read-1] == '\n') {
        handle_line(line);
        linesize = 0;
        memset(line, '\0', nbytes);
    }
}

free(buf);
free(line);

スタックの代わりにヒープ(malloc)を使用するのはなぜですか?それをfgets使用できるよりシンプルなスタックベースのソリューションがあるようです。
theicfire

0

ポータブルで汎用的なgetdelim関数を提供し、テストはmsvc、clang、gccを介して渡されます。

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

ssize_t
portabl_getdelim(char ** restrict linep,
                 size_t * restrict linecapp,
                 int delimiter,
                 FILE * restrict stream) {
    if (0 == *linep) {
        *linecapp = 8;
        *linep = malloc(*linecapp);
        if (0 == *linep) {
            return EOF;
        }
    }

    ssize_t linelen = 0;
    int c = 0;
    char *p = *linep;

    while (EOF != (c = fgetc(stream))) {
        if (linelen == (ssize_t) *linecapp - 1) {
            *linecapp <<= 1;
            char *p1 = realloc(*linep, *linecapp);
            if (0 == *p1) {
                return EOF;
            }
            p = p1 + linelen;
        }
        *p++ = c;
        linelen++;

        if (delimiter == c) {
            *p = 0;
            return linelen;
        }
    }
    return EOF == c ? EOF : linelen;
}


int
main(int argc, char **argv) {
    const char *filename = "/a/b/c.c";
    FILE *file = fopen(filename, "r");
    if (!file) {
        perror(filename);
        return 1;
    }

    char *line = 0;
    size_t linecap = 0;
    ssize_t linelen;

    while (0 < (linelen = portabl_getdelim(&line, &linecap, '\n', file))) {
        fwrite(line, linelen, 1, stdout);
    }
    if (line) {
        free(line);
    }
    fclose(file);   

    return 0;
}

なぜこれがfgets存在するのですか?
theicfire、

fgetsは行区切り文字をカスタマイズしたり、現在の行に対して何をするかをカスタマイズしたりできますか?
南山竹

getdelimカスタマイズされた区切り文字を使用できます。また、行の長さに制限がないことに気づきました。この場合、でスタックを使用できますgetline。(どちらもここで説明されています:man7.org/linux/man-pages/man3/getline.3.html
theicfire

Linuxだけについて話していますか?問題は、Cで行を読み取る方法ですよね?
南山竹

この任意の標準Cの実装のための作品(getdelimgetlinePOSIX.1-2008で標準化された、他の誰かがこのページで言及しています)。fgetsまた、標準のCであり、Linux固有ではありません
theicfire
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.