質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.53%
C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

C++Builder

C++Builderは、C/C++を用いてアプリ開発できる統合開発環境 (IDE) 。DelphiのC++版です。コンポーネントによるビジュアル開発、高機能なコードエディターなどで生産性の高い開発ができます。

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

Q&A

解決済

2回答

4051閲覧

fclose(fp)ではファイルポインタはNULLにならないのでしょうか?

dem0nmichik0

総合スコア37

C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

C++Builder

C++Builderは、C/C++を用いてアプリ開発できる統合開発環境 (IDE) 。DelphiのC++版です。コンポーネントによるビジュアル開発、高機能なコードエディターなどで生産性の高い開発ができます。

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

0グッド

0クリップ

投稿2021/11/11 14:59

編集2021/11/11 17:44

###困っていること
業務アプリの中で,下記機能があります。現状,ファイルサイズと受信データの合計サイズが1MB以上の場合,3.4.のときに,「fp == NULL」と処理を作っているせいか,3.4.で更新したファイル名が新規作成されず,3.5.で書き込みエラーになります。3.4.のように設計したのは,ファイルオープン判定時に「fp != NULL」とチェックするので,fclose()すれば,ポインタ(アドレス)もなくなると思っていました。どのように修正するのが適切でしょうか?どなたかご教授お願いします。

「現状」
ファイルサイズと受信データの合計サイズが1MB未満:1→2→3→3.1.→1に戻っての繰り返し
ファイルサイズと受信データの合計サイズが1MB以上:1→2→3→3.2→3.3.→3.4.→3.5→合計サイズが1MB未満に戻っての繰り返し。
「大まかなフロー」
0. データ受信
0. 受信データサイズの取得
0. ファイルサイズの最大値チェック
3.1.最大値未満の場合,受信データ書込み
3.2.最大値以上の場合,ファイルクローズ
3.3ファイル名の更新
3.4.ファイルポインタがない場合,更新したファイル名のオープン
3.5.受信データの書き込み

###調査して分かったこと

  • リスト3.2.でファイルクローズ(fclose())しても,ファイルポインタ(アドレス)はNULLにならない(デバッガ機能のインスペクタで確認済み)。

→3.4の「ファイルポインタがない場合」という条件判断を取り除くのが適切なのでしょうか?

###機能
本アプリは,該当装置からデータを受信したら,ファイルへ書き込みます。ファイルの最大サイズは1MBです。ファイルサイズと受信(書込む)データの合計サイズが1MB超えている場合,ファイル番号を更新したファイルに受信(書込む)データを書込みます。

###メモリイメージ
fopen()後とfclose()後のメモリイメージは下記のようなイメージでよいのでしょうか?
メモリイメージ

###テストアプリの画面
「データ書込み(200ms)」チェックボックスにチェックをつけると,200msでファイルへデータを書込む。メモにエラーログを出力する。
テストアプリの画面

###ソース
「main.cpp」

C++

1//--------------------------------------------------------------------------- 2 3#include <vcl.h> 4#pragma hdrstop 5 6#include "main.h" 7//--------------------------------------------------------------------------- 8#pragma package(smart_init) 9#pragma resource "*.dfm" 10TForm1 *Form1; 11const int ADDSIZE = 1400; // 追加データサイズ 12const int FILESIZE_MAX = 1048576; // 最大ファイルサイズ(1MB=1,048,576Bytes) 13const int RETRYCNT = 20; // ファイルオープンリトライ回数 14const int SLEEPTIME = 100; // スリープ時間(ms) 15//--------------------------------------------------------------------------- 16__fastcall TForm1::TForm1(TComponent* Owner) 17 : TForm(Owner) 18{ 19 // 書込みファイルインデックスの初期化 20 writeIndex = 1; 21 // 書込みパスの初期化 22 writePath = "C:\C++Builder\FILE\Win32\Debug\"; 23 24 Timer1->Enabled = false; 25} 26//--------------------------------------------------------------------------- 27// データ保存 28void TForm1::SaveData() 29{ 30 // 変数宣言 31 int file_size = 0; // ファイルサイズ 32 int add_size = 0; // 追加データサイズ 33 int write_size = 0; // 書込みデータサイズ 34 AnsiString writeFilePath; // 書込みパス(ファイル名含む) 35 int ans_i = 0; // 関数の戻り値 36 char ch[50] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; 37 38 // 書込みパス(ファイル名含む)の設定 39 writeFilePath = writePath + "test_" + writeIndex + ".log"; 40 41 // ファイルオープンリトライ対策(20回) 42 for(int i=0; i<RETRYCNT; i++) 43 { 44 // ジャーナルファイルをオープン(a:追加書込み,b:バイナリモード) 45 FILE *fp = fopen( writeFilePath.c_str(), "a+b" ); 46 // オープン成功の場合 47 if( fp != NULL ) 48 { 49 fseek( fp, 0, SEEK_END ); // ファイル最後尾に移動 50 file_size = ftell( fp ); // ファイルサイズを取得 51 fseek( fp, 0, SEEK_SET ); // ファイル先頭に戻る 52 53 // 追加データサイズ(実際は可変だが,試験のために固定値) 54 add_size = ADDSIZE; 55 56 // ファイルサイズ゙が1MBを超えている場合(次のファイルにデータを保存) 57 if( FILESIZE_MAX < (file_size + add_size) ) 58 { 59 // ファイルを閉じる 60 fclose( fp ); 61 // ファイルポインタの解放 62 // freeだと下記例外メッセージが出る 63/* プロジェクト Project1.exeは例外クラス $ 64 C0000005(メッセージ'access violation at 0x5003982b: read of address 0x04020000')を送出しました。 65*/ 66// free( fp ); 67 68 // 書込みインデックスの更新 69 writeIndex++; 70 71 // 書込みパス(ファイル名含む)の設定 72 writeFilePath = writePath + "test_" + writeIndex + ".log"; 73 } 74 try 75 { 76 // ファイルがオープンしていない場合 77 if( fp == NULL ) 78 { 79 // ファイルを新規オープン 80 fp = fopen( writeFilePath.c_str(), "ab" ); 81 } 82 } 83 catch(...) 84 { 85 86 } 87 // 書込みデータサイズ 88 write_size = add_size; 89 try 90 { 91 // ファイルへデータを書き込み 92 ans_i = (int)fwrite( ch, (size_t)write_size, 1, fp ); 93 // 書込み失敗の場合 94 if( ans_i != 1 ) 95 { 96 // エラーログ出力 97 Memo1->Lines->Add("データ書き込みエラー データサイズ:" + IntToStr(write_size)); 98 } 99 } 100 catch(...) 101 { 102 // エラーログ出力 103 Memo1->Lines->Add("ファイルオープンエラー リトライ回数:" + IntToStr(i+1)); 104 } 105 // ファイルを閉じる 106 fclose( fp ); 107 108 break; 109 } 110 // オープン失敗の場合 111 else 112 { 113 // エラーログ出力 114 Memo1->Lines->Add("ファイルオープンエラー リトライ回数:" + IntToStr(i+1)); 115 // スリープ時間の設定 116 std::chrono::milliseconds duration( SLEEPTIME ); 117 // スリープ 118 std::this_thread::sleep_for( duration ); 119 120 continue; 121 } 122 } 123} 124//--------------------------------------------------------------------------- 125void __fastcall TForm1::Timer1Timer(TObject *Sender) 126{ 127 Timer1->Enabled = false; 128 129 // データ保存 130 SaveData(); 131 132 Timer1->Enabled = true; 133} 134//--------------------------------------------------------------------------- 135 136void __fastcall TForm1::CheckBox1Click(TObject *Sender) 137{ 138 Timer1->Enabled = true; 139} 140//--------------------------------------------------------------------------- 141

「main.h」

C++

1//--------------------------------------------------------------------------- 2 3#ifndef mainH 4#define mainH 5//--------------------------------------------------------------------------- 6#include <System.Classes.hpp> 7#include <Vcl.Controls.hpp> 8#include <Vcl.StdCtrls.hpp> 9#include <Vcl.Forms.hpp> 10#include <Vcl.ExtCtrls.hpp> 11#include <chrono> 12#include <thread> 13//--------------------------------------------------------------------------- 14class TForm1 : public TForm 15{ 16__published: // IDE で管理されるコンポーネント 17 TMemo *Memo1; 18 TTimer *Timer1; 19 TCheckBox *CheckBox1; 20 void __fastcall Timer1Timer(TObject *Sender); 21 void __fastcall CheckBox1Click(TObject *Sender); 22private: // ユーザー宣言 23public: // ユーザー宣言 24 __fastcall TForm1(TComponent* Owner); 25 void SaveData(); 26 AnsiString writePath; // 書込みファイルパス 27 int writeIndex; // 書込みファイルインデックス 28}; 29//--------------------------------------------------------------------------- 30extern PACKAGE TForm1 *Form1; 31//--------------------------------------------------------------------------- 32#endif

###開発環境
OS:Windows64bit
IDE:Embarcadero C++Builder 10.3.3

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

C言語の引数についてに書かれているように、

plain

1呼ばれた関数側で呼び元の関数の変数を変更することはできない

のです。

-「fp = NULL」はファイルポインタ(アドレス)が解放されたというわけではないですよね?FILE構造体の各メンバの値が不定になっただけと思っていいのでしょうか?

ファイル構造体には、OSが管理するファイルディスクリプタ(あるいはファイルハンドル)が含まれているか、参照されています。fcloseはそのファイルディスクリプタ(あるいはファイルハンドル)をcloseすることで無効にしています。

投稿2021/11/11 17:32

ppaul

総合スコア24666

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

dem0nmichik0

2021/11/11 22:16

ppaul様,回答ありがとうございます。 >fcloseはそのファイルディスクリプタ(あるいはファイルハンドル)をcloseすることで無効にしています。 それは知りませんでした。一般的には開けたファイルFILE構造体のアドレスがある/ないで,ファイルオープンの成功/失敗を判断すると思いますが,ファイルディスクリプタで,ファイルオープンの成功/失敗を判断できるのでしょうか?
ppaul

2021/11/12 09:02

一般的には開けたファイルFILE構造体のアドレスがある/ないで,ファイルオープンの成功/失敗を判断すると思いますが,ファイルディスクリプタで,ファイルオープンの成功/失敗を判断できるのでしょうか? fopenして、NULLが返ってくれば、ファイルディスクリプタを調べようがありません。 fopenして、NULL以外が返ってくれば、ファイルディスクリプタを調べる必要がありません。 fcloseしたあと、ファイルディスクリプタが無効になっているかどうかはコードで把握していなければわかりません。
guest

0

ベストアンサー

fclose(fp)は関数fcloseにポインタfpの値(つまりFILE構造体のアドレス)を渡します。
fcloseから帰ってきた後もポインタfpの値は当然変わりません。

修正は簡単な話かと。

C++

1fclose(fp); 2fp = NULL;

投稿2021/11/11 15:22

itagagaki

総合スコア8402

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

dem0nmichik0

2021/11/11 15:41

itagagaki様,回答ありがとうございます。 >fclose(fp); >fp = NULL; 「fp = NULL;」を追加したら,問題なく動きました。2点追加質問してもよいでしょうか? 1.今回のような場合,ファイルクローズ(fclose)しても,ポインタ(アドレス)が解放されないなら,「if( fp == NULL)」の条件判断処理は入れるべきでないのでしょうか?それとも条件判断がないほうがいいのでしょうか? 2.「fp = NULL」はファイルポインタ(アドレス)が解放されたというわけではないですよね?FILE構造体の各メンバの値が不定になっただけと思っていいのでしょうか?というのも「free()」とかでエリアを解放する必要があるのではと思って,やってみたら,例外クラスが出たのでなぜなのか疑問に思った次第です。
itagagaki

2021/11/11 16:15

「ポインタを解放する」という表現は少しおかしいです。 ポインタは、データのアドレスを保持するただの変数です。 メモリ領域を確保したり解放したりする仕組みではありません。 fopenは、ファイルを開き、FILE構造体のためのメモリ領域を1つ確保し、そこに情報を格納して、そのFILE構造体のアドレスを返します。 FILE *fp = fopen(file, mode); では、fopenが確保したFILE構造体のアドレスをfpに代入していることになります。 fclose(fp); は、fpが指すFILE構造体(回りくどく言えば、変数fpに格納されているアドレスからの領域にあるFILE構造体)に格納されている情報を読み取って、該当ファイルを閉じる処理をし、そのFILE構造体(つまり、fopenが確保した、FILE構造体のためのメモリ領域)を解放します。 fpは、ポインタと言えど、ただの変数なので、 if (flag) { // flag が真の場合だけファイルを開く fp = fopen(file, mode); } else { // flag が偽ならファイルが開けなかったことにする fp = NULL; } if (fp == NULL) { // ファイルが開けなかった場合(開けなかったことにした場合も含む)の処理 } みたいなことは結構普通にやります。
dem0nmichik0

2021/11/11 17:47

itagagaki様,回答ありがとうございます。 >fopenは、ファイルを開き、FILE構造体のためのメモリ領域を1つ確保し、そこに情報を格納して、そのFILE構造体のアドレスを返します。 >FILE *fp = fopen(file, mode); では、fopenが確保したFILE構造体のアドレスをfpに代入していることになります。 >fclose(fp); は、fpが指すFILE構造体(回りくどく言えば、変数fpに格納されているアドレスからの領域にあるFILE構造体)に格納されている情報を読み取って、該当ファイルを閉じる処理をし、そのFILE構造体(つまり、fopenが確保した、FILE構造体のためのメモリ領域)を解放します。 メモリイメージは,追記した図のイメージのような感じでいいのでしょうか? 変数fpにFILE構造体のアドレスが代入されていることは分かりました。 fclose()時にFILE構造体(のメモリ領域)を解放するということは,イメージのFILE構造体の領域(アドレス)はなくなると思っていいのでしょうか?
itagagaki

2021/11/11 18:15

イメージは大体合っています。 0x1000~がFILE構造体のイメージですね? 通常の実装ならお書きの通りヒープ領域から確保されます。 メモリ確保とは、わかりやすく言えば、「ヒープ領域」という管理されたメモリ領域から、欲しい分だけのメモリの区画を借りてくることです。貸し出されるアドレスはそのとき決まります。他に貸し出されていない区画から貸し出されるので。表で言うと0x1000~0x1008(としておきましょう)が貸し出されてFILE構造体を格納するのに使っていることになりますね。 そしてメモリ解放とは、ヒープ領域を管理する仕組みに対して、0x1000~0x1008の区画を返却することです。返却と言っても、そのアドレスのメモリは厳然とそこに存在しているわけで、返却した瞬間のデータもだいたい残存していますが、そこはもう、ルールとして誰も読み書きしてはいけない領域になります。 要求があればメモリの区画としてまた貸し出されて、使われるわけです。
dem0nmichik0

2021/11/11 22:10

itagagaki様,回答ありがとうございます。 >0x1000~がFILE構造体のイメージですね? はい,その通りで0x1000~0x1008がFILE構造体です。 >そしてメモリ解放とは、ヒープ領域を管理する仕組みに対して、0x1000~0x1008の区画を返却すること >です。返却と言っても、そのアドレスのメモリは厳然とそこに存在しているわけで、返却した瞬間のデータ >もだいたい残存していますが、そこはもう、ルールとして誰も読み書きしてはいけない領域になります。 上記ですが,「返却と言っても、そのアドレスのメモリは厳然とそこに存在しているわけで、返却した瞬間のデータ>もだいたい残存していますが、そこはもう、ルールとして誰も読み書きしてはいけない領域になります。」の説明で,今回の事象が納得しました。 今回の説明の例でいうと0x1000~0x1008のFILE構造体は,アプリ起動時にアプリが確保したヒープ領域(メモリ領域)に,fopen()のとき,割り当てられるということなんですね。そして,fclose()するとヒープ領域を返す(ただし,fclose()直後ではアドレスやデータも存在している)。だから,借りた0x1000~0x1008のヒープ領域にNULLを代入して「空」にするわけですね。 丁寧に説明していただき,ありがとうございました。 ポインタやメモリ領域を再度,追加で勉強しなおします。
itagagaki

2021/11/12 00:16

えーと、ちょっと違っているところがあります。 > 借りた0x1000~0x1008のヒープ領域にNULLを代入して「空」にするわけですね。 ptr = NULL は、変数ptr(0x500のメモリの内容)に0を代入するだけです。 元の値(例だと0x1000)のメモリの内容には触りません。
dem0nmichik0

2021/11/12 00:40

itagagaki様,回答ありがとうございます。 >ptr = NULL は、変数ptr(0x500のメモリの内容)に0を代入するだけです。 >元の値(例だと0x1000)のメモリの内容には触りません。 すいません,勘違いしてました。次のような流れですね。 fopen時:fpのアドレス:FILE構造体のアドレス(0x1000)を取得。 FILE構造体:アドレスやデータに適切な値が入る ↓ fclose時:fpのアドレス:FILE構造体のアドレス(0x1000)のまま。 FILE構造体:アドレスやデータはそのまま。 ↓ fp = NULL時:fpのアドレス:NULL(0)を代入。 FILE構造体:アドレスやデータはそのまま。 たくさん説明していただき,ありがとうございます。 お世話になりました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.53%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

同じタグがついた質問を見る

C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

C++Builder

C++Builderは、C/C++を用いてアプリ開発できる統合開発環境 (IDE) 。DelphiのC++版です。コンポーネントによるビジュアル開発、高機能なコードエディターなどで生産性の高い開発ができます。

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。