C++におけるfwrite関数の使い方を5つのサンプルコードで徹底解説

C++のfwrite関数を徹底解説するイメージ C++
この記事は約17分で読めます。

【サイト内のコードはご自由に個人利用・商用利用いただけます】

この記事では、プログラムの基礎知識を前提に話を進めています。

説明のためのコードや、サンプルコードもありますので、もちろん初心者でも理解できるように表現してあります。

基本的な知識があればサンプルコードを活用して機能追加、目的を達成できるように作ってあります。

※この記事は、一般的にプロフェッショナルの指標とされる『実務経験10,000時間以上』を凌駕する現役のプログラマチームによって監修されています。

サイト内のコードを共有する場合は、参照元として引用して下さいますと幸いです

※Japanシーモアは、常に解説内容のわかりやすさや記事の品質に注力しております。不具合、分かりにくい説明や不適切な表現、動かないコードなど気になることがございましたら、記事の品質向上の為にお問い合わせフォームにてご共有いただけますと幸いです。
(送信された情報は、プライバシーポリシーのもと、厳正に取扱い、処分させていただきます。)

はじめに

この記事では、C++でよく使用されるfwrite関数について詳しく解説します。

プログラミングの初心者から中級者まで、ファイル操作の基本を学びたい方に向けて、fwrite関数の使い方から応用技術までを段階的に紹介していきます。

具体的なサンプルコードも交えながら、実際にどのようにデータをファイルに書き込むか、その方法を明確に説明します。

エラーハンドリングやパフォーマンスの最適化についても触れ、実務で直面するかもしれない問題への対処法を身につけることができます。

●fwrite関数の基本

fwrite関数は、C++においてバイナリファイルを含むあらゆるファイルへのデータ書き込みを行うために用いられます。

この関数を使用する主な理由は、データのブロック単位での高速な書き込みが可能であることです。

特に大量のデータを扱う場合、その効率性が非常に重要になります。

○fwrite関数とは何か?

fwrite関数は、標準入出力ライブラリに含まれる関数で、FILEポインタが指すファイルにデータを書き込むために使用されます。

主に、バイナリモードで開かれたファイルに対して利用されることが多いですが、テキストファイルに対しても利用可能です。

fwriteは、書き込むデータのアドレス、データ一つあたりのサイズ、書き込むデータの数、ファイルポインタを引数に取ります。

○fwrite関数のプロトタイプと引数の解説

C++でのfwrite関数のプロトタイプは下記の通りです。

size_t fwrite(const void *ptr, size_t size, size_t count, FILE *stream);

ここで、ptrはデータが格納されているメモリのアドレスを指します。

sizeは書き込むオブジェクト一つあたりのサイズ(バイト単位)、countは書き込むオブジェクトの数を表し、streamはデータの書き込み先のファイルポインタです。

この関数は成功すると書き込まれたオブジェクトの総数を返し、エラーが発生した場合は、書き込まれたオブジェクト数が予定の数より少なくなることでエラーを表します。

ファイル操作でのエラーチェックは、fwriteの戻り値を検証することで行うことが推奨されます。

●fwrite関数の使い方

先ほどの説明で、fwrite関数の基本的な概要とそのプロトタイプについて触れましたが、今度はこの関数をどのように使うのか、具体的な使い方を詳しく見ていきましょう。

fwrite関数を用いるときは、まずファイルを適切に開く必要があります。

ファイルポインタを得た後、fwrite関数を呼び出してデータを書き込みます。

ここで重要なのは、ファイルがバイナリモードで開かれているか、テキストモードで開かれているかを理解しておくことです。

○サンプルコード1:テキストファイルへの書き込み

最初の例として、テキストファイルへの簡単な文字列の書き込みを見てみましょう。

ここでは、C++でファイルを開き、”Hello, world!”という文字列をファイルに書き込むプロセスを紹介します。

#include <cstdio>

int main() {
    FILE *fp = fopen("example.txt", "w");
    if (fp == nullptr) {
        perror("File opening failed");
        return EXIT_FAILURE;
    }

    const char *text = "Hello, world!";
    fwrite(text, sizeof(char), strlen(text), fp);
    fclose(fp);

    return 0;
}

このコードでは、fopen関数を使用してファイルを書き込みモードで開いています。

fwrite関数は、指定されたテキストをファイルに書き込みます。

エラーが発生した場合は、適切なエラーメッセージが表示されます。

○サンプルコード2:バイナリファイルへの書き込み

次に、バイナリファイルへのデータの書き込みを考えてみます。

この例では、整数の配列をバイナリファイルに書き込みます。

#include <cstdio>
#include <vector>

int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5};
    FILE *fp = fopen("data.bin", "wb");
    if (fp == nullptr) {
        perror("File opening failed");
        return EXIT_FAILURE;
    }

    fwrite(numbers.data(), sizeof(int), numbers.size(), fp);
    fclose(fp);

    return 0;
}

この例では、std::vectorを使用して整数の配列を管理しており、fwriteはそのデータをdata.binファイルにバイナリ形式で書き込みます。

○サンプルコード3:ファイルへの構造体の書き込み

C++では、構造体をファイルに書き込むことも一般的です。

下記の例は、ユーザー定義の構造体をファイルに保存する方法を表しています。

#include <cstdio>
#include <cstring>

struct Person {
    char name[50];
    int age;
};

int main() {
    Person p = {"John Doe", 30};
    FILE *fp = fopen("person.dat", "wb");
    if (fp == nullptr) {
        perror("File opening failed");
        return EXIT_FAILURE;
    }

    fwrite(&p, sizeof(Person), 1, fp);
    fclose(fp);

    return 0;
}

このコードでは、Person構造体のインスタンスがファイルに直接書き込まれます。

この方法はデータの永続化に非常に効率的ですが、プラットフォーム間での互換性には注意が必要です。

○サンプルコード4:エラーハンドリングの実装

ファイル操作においては、エラーハンドリングも非常に重要です。

下記のコードは、fwrite関数を使用中にエラーが発生した場合にどのように対応するかを表しています。

#include <cstdio>

int main() {
    FILE *fp = fopen("example.txt", "w");
    if (fp == nullptr) {
        perror("File opening failed");
        return EXIT_FAILURE;
    }

    const char *text = "Hello, world!";
    size_t result = fwrite(text, sizeof(char), strlen(text), fp);
    if (result < strlen(text)) {
        perror("File write failed");
        fclose(fp);
        return EXIT_FAILURE;
    }

    fclose(fp);
    return 0;
}

この例では、fwriteの戻り値をチェックして、期待された数のアイテムが書き込まれているかを確認しています。

完全に書き込みができなかった場合にはエラーを報告します。

○サンプルコード5:大量データの効率的な書き込み

大量のデータを扱う場合、効率的な書き込みが求められます。

下記のサンプルでは、大量のデータを一度にファイルに書き込む方法を表しています。

#include <cstdio>
#include <vector>

int main() {
    std::vector<int> large_data(1000, 123);  // 1000個の123で初期化されたベクター
    FILE *fp = fopen("large_data.bin", "wb");
    if (fp == nullptr) {
        perror("File opening failed");
        return EXIT_FAILURE;
    }

    fwrite(large_data.data(), sizeof(int), large_data.size(), fp);
    fclose(fp);

    return 0;
}

この方法では、std::vectorを使って大量の整数データを管理し、一度のfwrite呼び出しで全データをファイルに書き込んでいます。

これにより、ファイルアクセスの回数を減らし、全体的なパフォーマンスを向上させることができます。

●よくあるエラーと対処法

プログラミングにおいてファイル操作は非常に一般的ですが、ファイル書き込みに関連するエラーもまた一般的です。

ここでは、C++でのfwrite関数を使用している際に頻繁に遭遇する可能性があるいくつかの典型的なエラーと、それらの対処法について解説します。

エラーの理解と適切な対応策を知ることは、より堅牢なアプリケーションを開発する上で重要です。

○エラー例1:ファイルが開けない場合の対処法

ファイルを開く際に最も一般的なエラーは、指定したファイルが存在しないか、アクセス権限がない場合です。

この問題は、fopen関数がNULLを返すことで明らかになります。

対処法は、ファイルの存在を確認し、アクセス権を適切に設定することです。

#include <cstdio>
#include <cerrno>
#include <cstring>

int main() {
    FILE *fp = fopen("example.txt", "r");
    if (!fp) {
        printf("Error opening file: %s\n", strerror(errno));
        return EXIT_FAILURE;
    }

    // ファイル操作
    fclose(fp);
    return 0;
}

このコードでは、ファイルが開けなかった場合にエラーメッセージを表示し、errno変数を用いて具体的なエラー内容をユーザーに通知します。

○エラー例2:書き込みデータが正しく保存されない場合

ファイルへの書き込み後、データが期待通りにファイルに保存されていない場合があります。

これは、データの書き込みが完了していない、または書き込み中にエラーが発生したことが原因です。

fwrite関数は書き込みが成功したアイテムの数を返しますので、この戻り値をチェックすることでエラーを検出できます。

下記のコードは、書き込みプロセスを確認し、問題がある場合にはエラー処理を行う方法を表しています。

#include <cstdio>
#include <vector>

int main() {
    std::vector<int> data = {1, 2, 3, 4, 5};
    FILE *fp = fopen("data.bin", "wb");
    if (!fp) {
        perror("Failed to open file");
        return EXIT_FAILURE;
    }

    size_t itemsWritten = fwrite(data.data(), sizeof(int), data.size(), fp);
    if (itemsWritten < data.size()) {
        perror("Failed to write all data");
        fclose(fp);
        return EXIT_FAILURE;
    }

    fclose(fp);
    return 0;
}

このコード例では、書き込むべきデータの量と実際に書き込まれた量を比較し、一致しない場合にはエラー処理を行っています。

●fwrite関数の応用例

fwrite関数は、その基本的な機能に加えて、さまざまな応用が可能です。

先ほどは基本的なファイル書き込みやエラーハンドリングについて見てきましたが、今度はもう少し複雑なシナリオに焦点を当てます。

具体的には、ファイルの分割書き込みやネットワークを介したデータの送信など、実務で直面する可能性のある問題への対処を探ります。

○サンプルコード6:ファイルの分割書き込み

大きなデータファイルを扱う場合、ファイルを分割して書き込むことが効率的です。

下記の例では、特定のサイズごとにファイルを分割し、複数のファイルにデータを書き込む方法を表しています。

#include <cstdio>
#include <vector>

int main() {
    std::vector<int> large_data(10000, 42); // 大量データの例
    const size_t chunk_size = 1000; // 1つのファイルあたり1000個のデータ
    size_t data_processed = 0;

    for (int i = 0; large_data.size() > data_processed; i++) {
        char filename[32];
        sprintf(filename, "output_part%d.bin", i);
        FILE *fp = fopen(filename, "wb");
        if (!fp) {
            perror("Failed to open file");
            break;
        }

        size_t items_to_write = std::min(chunk_size, large_data.size() - data_processed);
        fwrite(&large_data[data_processed], sizeof(int), items_to_write, fp);
        fclose(fp);
        data_processed += items_to_write;
    }

    return 0;
}

このコードでは、データが大量にあり、それを1000個ずつ分割して複数のファイルに書き込んでいます。

ファイル名は連番で自動生成されます。

○サンプルコード7:ネットワーク越しのデータ送信にfwriteを使用

ネットワークを通じてデータを送信する場合、C++のfwrite関数を使用して、ソケットへの書き込みを行うことができます。

#include <cstdio>
#include <cstring>
#include <sys/socket.h>
#include <netinet/in.h>

int main() {
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) {
        perror("Cannot open socket");
        return EXIT_FAILURE;
    }

    sockaddr_in server_addr;
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(12345);
    server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");

    if (connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
        perror("Cannot connect");
        return EXIT_FAILURE;
    }

    const char *message = "Hello, server!";
    fwrite(message, sizeof(char), strlen(message), fdopen(sockfd, "w"));
    close(sockfd);

    return 0;
}

このコードは、クライアントがサーバーに接続し、「Hello, server!」というメッセージを送信するためのものです。

ここでは、fwriteを使用してソケットディスクリプタに直接書き込んでいます。

●エンジニアが知っておくべきTips

プログラミングでは、パフォーマンスとセキュリティは常に重要なテーマです。

特に、ファイル操作を行う際には、これらの側面に注意を払うことが重要です。

先ほどのfwrite関数の応用例に続いて、ここではエンジニアが知っておくべきいくつかの重要なTipsを紹介します。

これらのTipsは、日々の開発業務において、より良いコードを書くために役立ちます。

○Tips1:パフォーマンスの向上技術

ファイルへの書き込み処理では、パフォーマンスを最適化することが重要です。

特に大量のデータを扱う場合、効率的な書き込みが求められます。

バッファリングを適切に管理することで、ディスクへのアクセス回数を減らし、パフォーマンスを向上させることができます。

#include <cstdio>
#include <vector>

int main() {
    std::vector<char> large_data(1000000, 'x'); // 大量のデータ
    FILE *fp = fopen("output.bin", "wb");
    if (!fp) {
        perror("Failed to open file");
        return EXIT_FAILURE;
    }

    // バッファサイズを設定
    const size_t buffer_size = 1024 * 1024; // 1MBのバッファ
    setvbuf(fp, nullptr, _IOFBF, buffer_size);

    fwrite(large_data.data(), sizeof(char), large_data.size(), fp);
    fclose(fp);

    return 0;
}

この例では、setvbuf関数を使用してファイルストリームのバッファサイズを大きく設定しています。

これにより、システムがディスクに書き込む回数が減少し、全体のパフォーマンスが向上します。

○Tips2:セキュリティを考慮したファイル書き込み

ファイルへの書き込みを行う際には、セキュリティも考慮する必要があります。

特に、外部からの入力をそのままファイルに書き込む場合、不正な入力による攻撃のリスクがあります。

適切なデータの検証とサニタイズを行うことで、このようなリスクを軽減できます。

#include <cstdio>
#include <cstring>

int main() {
    char input[256];
    printf("Enter some text: ");
    fgets(input, sizeof(input), stdin);

    // 文字列のサニタイズを行う
    for (int i = 0; i < strlen(input); i++) {
        if (input[i] == '<' || input[i] == '>' || input[i] == '&') {
            input[i] = '?'; // 危険な文字を置換
        }
    }

    FILE *fp = fopen("safe_output.txt", "w");
    if (!fp) {
        perror("Failed to open file");
        return EXIT_FAILURE;
    }

    fwrite(input, sizeof(char), strlen(input), fp);
    fclose(fp);

    return 0;
}

このコードでは、ユーザーからの入力を受け取り、特定の危険な文字を置換することで、ファイルへの書き込みをより安全に行います。

これにより、クロスサイトスクリプティング(XSS)などの脆弱性を防ぐことができます。

まとめ

この記事では、C++のfwrite関数の使い方から応用例までを詳細に解説しました。

基本的な使用方法からエラー対応、パフォーマンスの向上やセキュリティを考慮した安全なファイル書き込みまで、多岐にわたるテクニックを紹介してきました。

それぞれのサンプルコードは、実際のプログラミングシナリオで役立つように設計されており、C++を使用したファイル操作の理解を一層深めることができます。

読者の皆さんがこれらの情報を活用して、より効果的かつ安全にプログラムを作成できるようになることを願っています。