Cでファイルの内容を文字列に読み取る方法は?


96

Cでファイルを開き、その内容を文字列(char *、char []など)に読み取る最も簡単な方法(エラーが発生しにくく、コード行数が少ないが、解釈したい)は何ですか?


8
「最も簡単な方法」と「最もエラーが発生しやすい」は、多くの場合互いに逆です。
アンディレスター

14
「最も簡単な方法」と「最もエラーが起こりやすい」は、私の本では実際には同義語です。たとえば、C#での答えはstring s = File.ReadAllText(filename);です。どうすればそれがより簡単になり、エラーが発生しやすくなりますか?
マークラカタ2014

回答:


145

バッファ全体をrawメモリチャンクとしてメモリにロードし、自分で解析を行う傾向があります。そうすれば、標準のlibが複数のプラットフォームで何をするかを最もよく制御できます。

これは私がこれに使用するスタブです。fseek、ftell、freadのエラーコードを確認することもできます。(明確にするために省略)。

char * buffer = 0;
long length;
FILE * f = fopen (filename, "rb");

if (f)
{
  fseek (f, 0, SEEK_END);
  length = ftell (f);
  fseek (f, 0, SEEK_SET);
  buffer = malloc (length);
  if (buffer)
  {
    fread (buffer, 1, length, f);
  }
  fclose (f);
}

if (buffer)
{
  // start to process your data / extract strings here...
}

3
エラーやその他の理由で実際にファイル全体を読み取れない可能性があるため、freadの戻り値も確認します。
空き容量

6
rmeadorが言ったように、fseekは4GBを超えるファイルでは失敗します。
KPexEA 2008年

6
そうだね。大きなファイルの場合、このソリューションは不十分です。
Nils Pipenbrinck 2008年

31
これはランディングページなfreadので、文字列がゼロで終了しないことを指摘しておきます。これはいくつかの問題を引き起こす可能性があります。
ivan-k 2014

18
@Manbroskiが言ったように、バッファは '\ 0'で終了する必要があります。そのためbuffer = malloc (length + 1);、fcloseの後に変更して追加します:buffer[length] = '\0';(Valgrindによって検証済み)
soywod

26

残念ながらOSに大きく依存するもう1つの解決策は、ファイルをメモリマッピングすることです。一般に、アプリケーションのビューとオペレーティングシステムのファイルキャッシュが実際に物理メモリを共有できるため、読み取りのパフォーマンス、メモリ使用量の削減などの利点があります。

POSIXコードは次のようになります。

int fd = open("filename", O_RDONLY);
int len = lseek(fd, 0, SEEK_END);
void *data = mmap(0, len, PROT_READ, MAP_PRIVATE, fd, 0);

一方、Windowsはもう少しトリッキーです、残念ながら私はテストに私の目の前でコンパイラを持っていないが、機能はによって提供されるCreateFileMapping()MapViewOfFile()


3
これらのシステムコールからの戻り値を確認することを忘れないでください!
Toby Speight 2018

3
lseek()を呼び出すときは、intではなくoff_tを使用する必要があります。
ivan.ukr

1
メモリ内の特定の瞬間にファイルの内容を安定してキャプチャすることが目的である場合は、メモリに読み込まれているファイルがインターバル中に他のプロセスによって変更されないことが確実でない限り、この解決策は回避する必要があります。その上でマップが使用されます。詳細については、この投稿を参照してください。
user001

12

「内容を文字列に読み込む」という意味の場合、ファイルにコード0の文字が含まれていない場合は、メモリブロックを受け入れて必要に応じて再割り当てするgetdelim()関数を使用するか、バッファ全体を割り当てるだけでもかまいません。指定された区切り文字またはファイルの終わりに到達するまで、ファイルをその中に読み込みます。ファイル全体を読み取るための区切り文字として '\ 0'を渡すだけです。

この関数は、GNU Cライブラリで利用できます。http://www.gnu.org/software/libc/manual/html_mono/libc.html#index-getdelim-994

サンプルコードは、

char* buffer = NULL;
size_t len;
ssize_t bytes_read = getdelim( &buffer, &len, '\0', fp);
if ( bytes_read != -1) {
  /* Success, now the entire file is in the buffer */

1
私はこれを以前に使用したことがあります。読んでいるファイルがテキスト(\ 0を含まない)であると仮定すると、非常にうまく機能します。
ephemient 2008年

いいね!テキストファイル全体を丸呑みするときの多くの問題を保存します。ここで、区切り文字を必要とせずにEOFまでバイナリファイルストリームを読み取る同様の超単純な方法があったとしましょう。
アンソニー2017年

6

ファイルがテキストで、テキストを1行ずつ取得したい場合、最も簡単な方法はfgets()を使用することです。

char buffer[100];
FILE *fp = fopen("filename", "r");                 // do not use "rb"
while (fgets(buffer, sizeof(buffer), fp)) {
... do something
}
fclose(fp);

6

stdinやパイプなどの特別なファイルを読み込んでいる場合、fstatを使用してファイルサイズを事前に取得することはできません。また、バイナリファイルを読み込んでいる場合、fgetsは埋め込まれた '\ 0'文字のために文字列サイズ情報を失います。次に、ファイルを読み取る最良の方法は、読み取りと再割り当てを使用することです。

#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>

int main () {
    char buf[4096];
    ssize_t n;
    char *str = NULL;
    size_t len = 0;
    while (n = read(STDIN_FILENO, buf, sizeof buf)) {
        if (n < 0) {
            if (errno == EAGAIN)
                continue;
            perror("read");
            break;
        }
        str = realloc(str, len + n + 1);
        memcpy(str + len, buf, n);
        len += n;
        str[len] = '\0';
    }
    printf("%.*s\n", len, str);
    return 0;
}

1
これはO(n ^ 2)です。nはファイルの長さです。これ以上の賛成票を持つすべてのソリューションはO(n)です。実際にはこのソリューションを使用しないでください。または、乗法的に増加する修正バージョンを使用してください。
クラークゲーベル、2016

2
realloc()は、古いメモリを新しい大きなメモリにコピーすることなく、既存のメモリを新しいサイズに拡張できます。malloc()への呼び出しが間にある場合のみ、メモリを移動してこのソリューションをO(n ^ 2)にする必要があります。ここでは、realloc()の呼び出しの間に発生するmalloc()の呼び出しがないため、解決策は適切です。
ジェイク

2
中間の「buf」からコピーする必要なしに、「適切なオフセットで」「str」バッファーに直接読み取ることができます。ただし、この手法では、通常、ファイルの内容に必要なメモリが過剰に割り当てられます。また、バイナリファイルにも注意してください。printfはそれらを正しく処理しません。おそらく、とにかくバイナリを出力したくないでしょう。
アンソニー

3

注:これは、上記の受け入れられた回答の変更です。

これを行う方法は次のとおりです。エラーチェックを完了します。

ファイルが1 GiBを超えたときに終了するサイズチェッカーを追加しました。これは、プログラムがファイル全体を文字列に入れて、RAMを使いすぎてコンピュータをクラッシュさせる可能性があるためです。ただし、それを気にしない場合は、コードから削除できます。

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

#define FILE_OK 0
#define FILE_NOT_EXIST 1
#define FILE_TO_LARGE 2
#define FILE_READ_ERROR 3

char * c_read_file(const char * f_name, int * err, size_t * f_size) {
    char * buffer;
    size_t length;
    FILE * f = fopen(f_name, "rb");
    size_t read_length;

    if (f) {
        fseek(f, 0, SEEK_END);
        length = ftell(f);
        fseek(f, 0, SEEK_SET);

        // 1 GiB; best not to load a whole large file in one string
        if (length > 1073741824) {
            *err = FILE_TO_LARGE;

            return NULL;
        }

        buffer = (char *)malloc(length + 1);

        if (length) {
            read_length = fread(buffer, 1, length, f);

            if (length != read_length) {
                 *err = FILE_READ_ERROR;

                 return NULL;
            }
        }

        fclose(f);

        *err = FILE_OK;
        buffer[length] = '\0';
        *f_size = length;
    }
    else {
        *err = FILE_NOT_EXIST;

        return NULL;
    }

    return buffer;
}

エラーをチェックするには:

int err;
size_t f_size;
char * f_data;

f_data = c_read_file("test.txt", &err, &f_size);

if (err) {
    // process error
}

2

を使用している場合はglibg_file_get_contentsを使用できます。

gchar *contents;
GError *err = NULL;

g_file_get_contents ("foo.txt", &contents, NULL, &err);
g_assert ((contents == NULL && err != NULL) || (contents != NULL && err == NULL));
if (err != NULL)
  {
    // Report error to user, and free error
    g_assert (contents == NULL);
    fprintf (stderr, "Unable to read file: %s\n", err->message);
    g_error_free (err);
  }
else
  {
    // Use file contents
    g_assert (contents != NULL);
  }
}

1
// Assumes the file exists and will seg. fault otherwise.
const GLchar *load_shader_source(char *filename) {
  FILE *file = fopen(filename, "r");             // open 
  fseek(file, 0L, SEEK_END);                     // find the end
  size_t size = ftell(file);                     // get the size in bytes
  GLchar *shaderSource = calloc(1, size);        // allocate enough bytes
  rewind(file);                                  // go back to file beginning
  fread(shaderSource, size, sizeof(char), file); // read each char into ourblock
  fclose(file);                                  // close the stream
  return shaderSource;
}

nullに対して何もチェックされないため、これはかなり大まかな解決策です。


これは、ディスクベースのファイルでのみ発生します。名前付きパイプ、標準入力、またはネットワークストリームでは失敗します。
アンソニー

ハ、私がここに来た理由も!ただし、文字列をnullで終了するか、glShaderSourceオプションで必要な長さを返す必要があると思います。
Ciro Santilli郝海东冠状病六四事件法轮功

1

上記の受け入れられた回答から変更されました。

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

char *readFile(char *filename) {
    FILE *f = fopen(filename, "rt");
    assert(f);
    fseek(f, 0, SEEK_END);
    long length = ftell(f);
    fseek(f, 0, SEEK_SET);
    char *buffer = (char *) malloc(length + 1);
    buffer[length] = '\0';
    fread(buffer, 1, length, f);
    fclose(f);
    return buffer;
}

int main() {
    char *content = readFile("../hello.txt");
    printf("%s", content);
}

これはCコードではありません。質問はC ++としてタグ付けされていません。
Gerhardh 2017年

@Gerhardh編集中の9年前の質問への迅速な対応!関数部分は純粋なCですが、will-not-run-on-cの回答をお詫びします。
BaiJiFeiLong 2017年

この古代の質問は、活発な質問のトップにリストされました。私はそれを捜しませんでした。
Gerhardh 2017年

このコードはメモリをリークします。mallocされたメモリを解放することを忘れないでください:)
ericcurtin

0

参考のために、ここでの回答に基づいて独自のバージョンを追加します。私のコードはsizeof(char)を考慮に入れ、それにいくつかのコメントを追加しています。

// Open the file in read mode.
FILE *file = fopen(file_name, "r");
// Check if there was an error.
if (file == NULL) {
    fprintf(stderr, "Error: Can't open file '%s'.", file_name);
    exit(EXIT_FAILURE);
}
// Get the file length
fseek(file, 0, SEEK_END);
long length = ftell(file);
fseek(file, 0, SEEK_SET);
// Create the string for the file contents.
char *buffer = malloc(sizeof(char) * (length + 1));
buffer[length] = '\0';
// Set the contents of the string.
fread(buffer, sizeof(char), length, file);
// Close the file.
fclose(file);
// Do something with the data.
// ...
// Free the allocated string space.
free(buffer);

0

簡単で端正(ファイルの内容が10000未満であると仮定):

void read_whole_file(char fileName[1000], char buffer[10000])
{
    FILE * file = fopen(fileName, "r");
    if(file == NULL)
    {
        puts("File not found");
        exit(1);
    }
    char  c;
    int idx=0;
    while (fscanf(file , "%c" ,&c) == 1)
    {
        buffer[idx] = c;
        idx++;
    }
    buffer[idx] = 0;
}

事前に必要と思われるすべてのメモリを割り当てないでください。これは悪いデザインの完璧な例です。可能な場合はいつでも、メモリを随時割り当てる必要があります。ファイルの長さが10,000バイトで、プログラムが他のサイズのファイルを処理できず、サイズをチェックしてエラーが発生する場合は、良い設計ですが、ここではそうではありません。あなたは本当にCを正しくコーディングする方法を学ぶべきです。
ジャックギフィン
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.