テキストモードでファイルオープン – プログラミング – Home

テキストモードでファイルオープン
 
通知
すべてクリア

[解決済] テキストモードでファイルオープン


ARA
 ARA
(@ARA)
ゲスト
結合: 5年前
投稿: 7
Topic starter  

お世話になっています。

テキストモードでのファイル読み込みの挙動について、質問がございます。

テキストモードでオープンしたファイルをReadすると、なぜかファイルサイズよりも余分
にバッファが確保されてしまいます。
ある程度のサイズ(私の環境では約1MB)を超えるファイルだと現象が出ます。
また、改行を含まないファイルでは現象が出ないことを確認しています。

なぜ、このような現象が出るのか、教えていただけないでしょうか?

OS:Windows10 64bit
開発環境:VisualStudio2010(コンソールアプリケーション。また、プロジェクトの文字セ
ットを「マルチバイト文字セットを使用する」としています)


引用未解決
トピックタグ
ARA
 ARA
(@ARA)
ゲスト
結合: 5年前
投稿: 7
Topic starter  

// Test.cpp : コンソール アプリケーションのエントリ ポイントを定義します。
//

#include stdafx.h
#include <tchar.h>
#include <afx.h>
#include <afxwin.h>

int _tmain(int argc, _TCHAR* argv[])
{
CStdioFile fileWrite;
CStdioFile fileRead;
CString strTmpData, strData;
TCHAR* szBuf(NULL);
TCHAR* szTmpBuf(NULL);
int n(0), m(0), nIndex(0);
ULONGLONG nSize(0), nReadSize(0), nBufSize(0);

const long ROWS(1050);// サイズがこれ以上だと現象が出ます。
const long LENGTH(1000);

//const long ROWS(1049);// サイズがこれ以下では現象は出ません。
//const long LENGTH(1000);

//CString strToken(_T(\r\n));// 区切りが改行の場合、現象が出ます。
CString strToken(_T(\t\t));// 区切りが改行以外の場合、現象が出ません。

TCHAR* szFile = _T(Test.txt);

// 書き込み
if(fileWrite.Open(szFile, CFile::typeBinary | CFile::modeCreate |
CFile::modeWrite))
{
for(m = 0; m < ROWS; m++)
{
strData.Empty();
for(n = 0; n < LENGTH; n++)
{
strTmpData.Format(_T(%d), n % 10);
strData += strTmpData;
}
strData += strToken;
fileWrite.WriteString(strData.GetBuffer(0));
}
fileWrite.Close();
}

// 読み込み
if(fileRead.Open(szFile, CFile::modeRead))// テキストモード
{
nSize = fileRead.GetLength();
szBuf = (TCHAR*)malloc((size_t)(nSize + 1));
ZeroMemory(szBuf, (size_t)(nSize + 1));

nReadSize = fileRead.Read(szBuf, (UINT)nSize);//
nReadSize:1051050

nBufSize = strnlen(szBuf, ULONG_MAX);// nBufSize:1051054(なぜ
か、nReadSizeと相違)

szTmpBuf = strtok(szBuf, strToken.GetBuffer(0));

while(szTmpBuf != NULL)
{
if(nIndex == ROWS)
AfxMessageBox(余分なデータ);// なぜかここに入
ります。

szTmpBuf = strtok(NULL, strToken.GetBuffer(0));
nIndex++;
}

free(szBuf);
fileRead.Close();
}

return 0;
}


返信引用
ARA
 ARA
(@ARA)
ゲスト
結合: 5年前
投稿: 7
Topic starter  

上記のプログラムは、改行を含むファイルをテキストモードでオープンし、Readした場
合、データの末尾に余計なデータが入り込んでしまっていることを示すものです。

また、下記については確認しています。

・C言語(read関数, fread関数)を用いても同様の現象が出る
・バイナリモードでオープンすると、正しいサイズを取得できる

よろしくお願いいたします。


返信引用
ARA
 ARA
(@ARA)
ゲスト
結合: 5年前
投稿: 7
Topic starter  

提示したプログラムを一点、訂正いたします。

//CString strToken(_T(\r\n));// 区切りが改行の場合、現象が出ます。
CString strToken(_T(\t\t));// 区切りが改行以外の場合、現象が出ません。

 ↓↓↓

CString strToken(_T(\r\n));// 区切りが改行の場合、現象が出ます。
//CString strToken(_T(\t\t));// 区切りが改行以外の場合、現象が出ません。

提示したそのままのソースでは現象が出ず、改行のコメントアウトを復活させると現象が出ま
す。(いろいろテストした結果、変更箇所がそのままになっていました。失礼いたしました。)

また、補足ですが、プロジェクトの設定は「共有 DLL で MFC を使う」としています。


返信引用
瀬戸っぷ
 瀬戸っぷ
(@瀬戸っぷ)
ゲスト
結合: 18年前
投稿: 178
 

>ある程度のサイズ(私の環境では約1MB)を超えるファイルだと現象が出ます。

サイズは関係ないはずなんですけどねぇ…。
バッファに収まるかどうかで挙動変わるんでしょうか?

バイナリモードで書き込んで、テキストモードで読み出す。
という処理をしているので、改行コードの変換部分でサイズの変化があるのでしょう。
# テキストモードで書き込んで、バイナリモードで読み出す。でも似たような問題が発生
するでしょうけど。

CStdioFile::Read()ではなく、
CStdioFile::ReadString()で読み込むべきかと思います。
# CStdioFile::Read()は継承元のCFile::Read()になるでしょうけど。


返信引用
ARA
 ARA
(@ARA)
ゲスト
結合: 5年前
投稿: 7
Topic starter  

瀬戸っぷさん

返信いただきありがとうございます。

試しに、テキストモードで書き込みテキストモードで読み出すよう変更しましたが、同様の現象
が出ました。

(下記の部位について、変更を行いました。)

if(fileWrite.Open(szFile, CFile::typeBinary | CFile::modeCreate |
CFile::modeWrite))

 ↓↓↓

if(fileWrite.Open(szFile, CFile::typeText | CFile::modeCreate |
CFile::modeWrite))

CStdioFile::ReadString() は速度が遅いため使用を躊躇しています。
バイナリモードでWrite、Readともに行うことで、一応の現象の回避はできていますが、根本的
な原因の解明にはなっていないので、引き続きよろしくお願いいたします。


返信引用
瀬戸っぷ
 瀬戸っぷ
(@瀬戸っぷ)
ゲスト
結合: 18年前
投稿: 178
 

Windows7 Pro 64Bit + VS2017で確認。

>nSize = fileRead.GetLength();
>nReadSize = fileRead.Read(szBuf, (UINT)nSize);
で nSize > nReadSizeとなっていました。

テキストモードで開いていた為、0Dh 0Ahを0Ahに変換しているようです。
# 1050行で、nReadSizeが1050バイト分ほど少ない。

で、書き込み時のバイナリモードをテキストモードにして書き出しを行い、出力された
ファイルをバイナリエディタで覗くと…
\r\n(0Dh 0Ah)が\r\r\n(0Dh 0Dh 0Ah)に変換されます。
内部的には改行は\nで扱うべき…となります。
# CStdioFile::Read()でも変換は生きるんですねぇ……。

サイズで動作に差が出る原因は不明…なままです。


返信引用
瀬戸っぷ
 瀬戸っぷ
(@瀬戸っぷ)
ゲスト
結合: 18年前
投稿: 178
 

バイナリモードで書き込んで、テキストモードで読み込んだ状態で…
メモリウィンドウで終端部分をダンプしてみました。

30 31 32 33 34 35 36 37 38 39 0a 38 39 0d 0a 00 00……

先に出てくる0Ahまでが、本来読み込み完了した位置(szBuf[nReadSize])になります。
CStdioFile::Read()としては、「読み込んだと報告した位置以降」なので、知ったこっ
ちゃないよ。
ということでしょう。
読み込みできるバッファサイズを越えていたりもしませんし。

で、「文字列として読み込んだ」わけではないので、「読み込んだと報告した位置」の後
に終端マーク('\0')の付与はしていない。
# プログラマが責任もって付けろ。
ということでしょう。

で、テキストモードの為、0Dh 0Ahを0Ahに切り詰めるという処理を指定されたバッファ内
で行っていて、その時のデータが「読み込んだと報告した位置以降」に残っている。
ということかも知れません。
# なので、ダンプすると0Dh 0Ahで終わっている。

CStdioFile::Read()の後でszBuf[nReadSize] = _T('\0');として、明示的に「終端マー
ク」を設定することで正しく動作するかと。


返信引用
瀬戸っぷ
 瀬戸っぷ
(@瀬戸っぷ)
ゲスト
結合: 18年前
投稿: 178
 

>先に出てくる0Ahまでが、本来読み込み完了した位置(szBuf[nReadSize])

ちょっと間違えたか。
szBuf[(nReadSize - 1)]かな。

szBuf[nReadSize] = _T('\0');
を行った後のメモリダンプは、
30 31 32 33 34 35 36 37 38 39 0a 00 39 0d 0a 00 00……
となりました。


返信引用
瀬戸っぷ
 瀬戸っぷ
(@瀬戸っぷ)
ゲスト
結合: 18年前
投稿: 178
 

もうひとつ…

http://www.c-tipsref.com/reference/string/strtok.html
strtok()では、第2引数の文字列内の「いずれかの文字」で区切るので、
今回の場合'\r'か'\n'を発見すると区切ります。
「\r\nと連続した場合」ではありませんのでご注意を。


返信引用
仲澤@失業者
(@uncle_kei)
Prominent Member
結合: 4年前
投稿: 828
 

回答については、瀬戸っぷさんのご指摘の通りでしょう。

ところで、CStdioFileクラスなのですが、コンパイルオプションで文字コードを

(1)MBCSにしている場合は入出力がMBCS(ASCII/Shift-JIS)
(2)Unicodeにしている場合は入出力がUnicode

の専用になってしまい。
UnicodeアプリとしてコンパイルするとMBCSの入出力ができなくなってしまうようなクラス
です。
何か裏道があるのかもしれませんけどみあたりませんでした。
現状、

(A)MSの公式見解として「MBCS用のMFCは非推奨」。UnicodeのMFCはOK。
(B)なので、UnicodeのMFCからは、CStdioFileを使ってASCII文字の入出力ができない。

という事態になっているようです(やれやれ)。


返信引用
ARA
 ARA
(@ARA)
ゲスト
結合: 5年前
投稿: 7
Topic starter  

瀬戸っぷさん

丁寧に調査していただきありがとうございます。
大変参考になりました。

>テキストモードで開いていた為、0Dh 0Ahを0Ahに変換しているようです。
># 1050行で、nReadSizeが1050バイト分ほど少ない。

GetLength()で得た値とReadの戻り値の相違は、ちょうど改行文字が1バイトに変換された分
ですね。こちらは想定どおりの結果です。

>で、書き込み時のバイナリモードをテキストモードにして書き出しを行い、出力された
>ファイルをバイナリエディタで覗くと…
>\r\n(0Dh 0Ah)が\r\r\n(0Dh 0Dh 0Ah)に変換されます。
>内部的には改行は\nで扱うべき…となります。
># CStdioFile::Read()でも変換は生きるんですねぇ……。

プログラムで改行を\r\nで扱うと、テキストモードで書き込むと、改行変換の結果、余計な\r
が付与されてしまう、ということですね。
気を付けなければならないですね。

>で、テキストモードの為、0Dh 0Ahを0Ahに切り詰めるという処理を指定されたバッファ内
>で行っていて、その時のデータが「読み込んだと報告した位置以降」に残っている。
>ということかも知れません。
># なので、ダンプすると0Dh 0Ahで終わっている。

なるほど。いったん、メモリにバイナリデータを読み込んだのち、クリアせずに切り詰め処理
を行っているということでしょうかね。
切り詰められた差分が完全に残っていないのは、適当なブロック単位に読み込んでいるため中
途半端に残っているのかもしれませんね。(あくまでも推測ですが)

>明示的に「終端マーク」を設定することで正しく動作するかと。

その手がありましたね。
私の環境でもこの方法で正しく動作することを確認しました。
わざわざバイナリモードに変更するより、こちらの方法が簡潔でよさそうですね。

>strtok()では、第2引数の文字列内の「いずれかの文字」で区切るので、
>今回の場合'\r'か'\n'を発見すると区切ります。
>「\r\nと連続した場合」ではありませんのでご注意を。

こちらは誤解しておりました。指摘していただきありがとうございます。


返信引用
ARA
 ARA
(@ARA)
ゲスト
結合: 5年前
投稿: 7
Topic starter  

仲澤@失業者さん

貴重な情報をいただきありがとうございます。

>(A)MSの公式見解として「MBCS用のMFCは非推奨」。UnicodeのMFCはOK。
>(B)なので、UnicodeのMFCからは、CStdioFileを使ってASCII文字の入出力ができない。

ネットで情報を調べて、公式見解はまだ確認していないのですが、ひとまず、Visual Studio
C++ 2013 では、MFCのMBCS版が非推奨ということを確認しました。

既存のアプリでUnicode対応を迫られることになり、なかなか面倒な事態ですね。


返信引用
ITO
 ITO
(@ITO)
ゲスト
結合: 22年前
投稿: 1235
 

>MFCのMBCS版が非推奨ということを確認しました。
MBCS版の追加パッケージがダウンロ-ドできました。
今もできるかな?
MBCSでプロジェクト設定すると可能になります。
Unicodeは使えなくなるのかな?
_T()は使えたはずですね。


返信引用

返信する

投稿者名

投稿者メールアドレス

タイトル *

プレビュー 0リビジョン 保存しました
共有:
タイトルとURLをコピーしました