【環境】WinXP/VC++6.0/Win32コンソールアプリ
初めて質問させていただきます。
テキストファイル入出力クラスを作成中で、書式指定付きの読込み関数を次のように定義しています。
CTextFileIO::Read(LPCSTR lpFormat,...)
{
va_list arg;
va_start(arg,lpFormat);
fscanf(this->hFile,lpFormat,arg);
va_end(arg);
}
hoge.txtの内容
5
使用する場合
void main()
{
int i;
CTextFileIO file("hoge.txt");
// ☆読込み
file.Read("%d",&i);
// i に 5 が読込まれることを期待している。
printf("%d",i);
}
実行結果
-858993460
ご質問は、☆の箇所でどうして i に 5 が取得できないのかということです。同様の作り方で出力関数 vfprintf() のラッパー関数を試した場合は正しく動作しました。
以上よろしくお願いいたします。
No.3ベストアンサー
- 回答日時:
# 誤解させました(というか問題を読み違えて誤解してた…orz。
すみません。vfscanfがないなら(あってうまくいってないと思ってた)、クラス以前の問題でした。
fscanfはvfscanfと引数が違ってva_listを認識しないので、
fscanfにそのままva_listを渡してもうまく動きません。
fscanfは「複数の引数」を期待する。
vfscanfがあるなら、「複数の引数リストをさす単一の引数」を期待する。
ようは、ポインタと、ポインタとポインタの違いみたいなものですが、これが合わないとうまく取れません。
出力のほうは、ポインタのポインタの先頭がポインタに暗黙で変換されれば出力可能なはずです。
二つ以上の引数を渡しても、正しく出力しますか?ひとつだけで試してませんか?
で、手間をかけさせてしまったこともあり、ちょっと説明コードも書いてみました。(VC6はもう捨てたのでVC7.1ですが…)
単純にintの単一変数を取るだけなら、こんな感じで取れるかと思います。
void foo_scan(FILE* file, const char* const format, ...)
{
va_list arg;
va_start(arg, format);
fscanf(file,format, *reinterpret_cast<int*>(arg));
va_end(arg);
}
ただ、このコード、きめ打ちでintですし(こっちはまだアドレスサイズが同一でformatの指定が正しければ動くはず)、
引数の展開をまともにやってないので、二個目以降の値はうまく取れないと思います(理由は前述)。
で。もしもfscanfを使うということであれば、
va_listの内容を自前でスタックにつみなおして、
さも引数が合ったかのようにfscanfを呼ぶなどの細工が必要になるわけです。
VCは不準拠ですが最新のC99にはvfscanfがありますし、
(ライセンスが許せば)このソースなどは参考になるでしょう。
# あえてC言語風で自作する理由は何ですか?
# vfscanfがないなら結構実装は面倒だと思いますし、
# Mustでないなら大体策を考えたほうがよい気もします。
# iostream風にするとか、boost::formatを使ってみるとか。
参考URL:http://boost.cppll.jp/HEAD/libs/format/doc/forma …
>fscanfはvfscanfと引数が違ってva_listを認識しないので、
>fscanfにそのままva_listを渡してもうまく動きません。
知りませんでした。
>二つ以上の引数を渡しても、正しく出力しますか?
二つ以上の引数を渡してみました。
// 定義
VOID CTextFileIO::Write(LPCSTR lpFormat,...)
{
va_list arg;
va_start(arg,lpFormat);
vfprintf(m_hFile,lpFormat,arg);
va_end(arg);
}
// 実験
Write("%c %d %s",'Y',5000,"String");
// 結果
Y 5000 String
二個目以降の値も取れているようです。
># あえてC言語風で自作する理由は何ですか?
std::iostreamに不慣れなのが理由ですが、Mustでないので少し学んでfstreamで代替しようと思います。
サンプルコードありがとうございます。試してみると正しくint値が取れました。
ご丁寧にありがとうございます。
No.6
- 回答日時:
'v'で始まる関数はva_list対応版なのでvfprintfはOKです。
fprintfではNGになります。同様に、vfscanfがあればva_listがOKで、fscanfでは現状のようなNGとなります。
fprintfで試してみると失敗するはずで、
「"fprintfに"二つ以上の引数を渡しても、正しく出力しますか?」等と書くべきでした。すみません。
なお、
> ctextfileio.cpp(90) : error C2018: 文字 '0x81' は認識できません。
これらのエラーは、全角空白を含む場合に出るものです。半角に置換してください。
そのままコピペすると掲示板での見やすさの為に全角空白でインデントされていると思います。
>> ctextfileio.cpp(90) : error C2018: 文字 '0x81' は認識できません。
>これらのエラーは、全角空白を含む場合に出るものです。
おっしゃる通りです。初歩的ミスでお恥ずかしい。。。
ありがとうございました。
No.5
- 回答日時:
VC++6.0決め打ちでよいのであれば、scanf系の共通下請け関数である_input関数を直接呼び出せば簡単に解決しそうな気がします。
# もしかすると、ライブラリをスタティックリンクした場合しか使えない?
別の方法としては、可変個実引数の個数が決まっているのであれば、多重定義か省略時実引数を使って、見かけ上scanf系関数と同じように振舞うようにするのも一つの手です。
この場合、呼び出された時点で個数が分かるはずですし、実引数に渡されるのはポインタに決まっているので、すべてconst volatile void*で受けてからfscanf関数に渡してやれば、どうにかなります。
CTextFileIO::Read(LPCSTR lpFormat, const volatile void* arg)
{
...
fscanf(this->hFile,lpFormat, arg);
...
}
CTextFileIO::Read(LPCSTR lpFormat, const volatile void* arg1, const volatile void* arg2)
{
...
fscanf(this->hFile,lpFormat, arg1, arg2);
...
}
ただ、可変個実引数はC++とは相性がよくありませんから、std::istreamに置き換えるなどした方が、本当はよいと思います。
ところで、#3で紹介されているboost::formatは出力側しかできないので(つまりprintf系の代わり)、入力側(つまりscanf系の代わり)には使えなかったと思います。
ご提示いただいたコードをコンパイルすると以下のエラーが出ました。何かオプション指定が必要なのでしょうか。
ctextfileio.cpp(90) : error C2018: 文字 '0x81' は認識できません。
ctextfileio.cpp(90) : error C2018: 文字 '0x40' は認識できません。
>std::istreamに置き換えるなどした方が、本当はよいと思います。
そうですね。fscanf()にこだわった理由は、1行で異なる型の値を一気に読込めると便利だと考えたからですが、
fstream<<5000<<'Y'<<"String"<<endl;
こちらのほうが可読性高い分、妥当かと思いました。
ありがとうございました。
No.4
- 回答日時:
#2>arg と i がメモリ上の同じアドレスを指すようにできれば
fscanf(hFile,lpFormat,arg);
を
fscanf(hFile, lpFormat, va_arg(arg,int*));
とはできるんじゃないかと思いますが、
これだと(可変引数にするには)lpFormat の% の切り出しが必要ですね^^;
BLUEPIXYさん、ありがとうございます。
>fscanf(hFile, lpFormat, va_arg(arg,int*));
で int 値が正しく読込めました。
ただ、可変引数に対応させるより std::fstream 使ったほうがスマートだと納得できましたので、fstream をそのまま使うか、継承して使おうと思います。
No.2
- 回答日時:
検証して無くて申し訳ないですが、
仮にvfscanfがあったとしても多分va_listがcdeclcall前提なので、
クラスの非スタティックなメンバ関数(thiscall)だとスタックの調整でうまく動かない可能性があるはず。
スタックの位置はあってますか。
一度、クラス以外のグローバル関数でも試してみてもらえますか。
それでうまくいくようなら、自力でスタックごにょごにょしないと、
クラスのメンバ関数ではうまくいかないと思われます。
この回答への補足
自己レスです。
>arg と i がメモリ上の同じアドレスを指すようにできれば、i にファイルからの
>読取値が正しく取得できるかどうか試してみたいと思います。
そもそも arg と i は別々の変数ですから同じアドレスを指すことは不可能ですよね。試したかったのは、va_listが内部で管理している可変個引数へのポインタ(どのように管理しているかは知りませんが)が、i のアドレスを指すように調整することです。
fscanf() をクラスのメンバ関数でラップした場合に動作しない原因が、スタックの問題だとすると、vfprintf() を同じようにクラスのメンバ関数でラップ(メンバ関数内でva_listを宣言・使用)した場合に動作する理由がわかりません。
MrBanさん、ご回答ありがとうございます。
>クラス以外のグローバル関数でも試してみても
>らえますか
以下のように試しましたが、結果は同じでした。
// グローバル関数として定義
void Read(FILE* hFile,LPCSTR lpFormat,...){
va_list arg; // (2)
va_start(arg,lpFormat); // (3)
fscanf(hFile,lpFormat,arg);
va_end(arg);
}
// Test5.datの内容
5
// 実験
void main(){
FILE* hFile = fopen("Test5.dat","r");
int i=0; // (1)
Read(hFile,"%d",&i); // (4)
printf("i = %d",i);
}
// 結果
i = 0
>スタックの位置はあってますか
上記(1)~(4)の各行の直後で、arg と i へのポインタが指すアドレスを調べると以下のようになりました。
(1)iのアドレス 0x0012fdf4
(2)argのアドレス 0xcccccccc
(3)argのアドレス 0x0012fd20
(4)iのアドレス 0x0012fdf4
arg と i がメモリ上の同じアドレスを指すようにできれば、i にファイルからの読取値が正しく取得できるかどうか試してみたいと思います。
検討違いのことを試しているようでしたら、ご指摘いただけると幸いです。
No.1
- 回答日時:
> fscanf(this->hFile,lpFormat,arg);
関数 vfscanf があるなら、これに取り替えれば動作するでしょうね。
epistemeさん、アドバイスありがごうございます。
残念ながら<stdio.h>に vfscanf はありませんでした。
vfprintf があるなら vfscanf もあるだろうと思ったのですが。。。
ワイド文字版の fwscanf を試しましたが、やはり値を読込むことができませんでした。
ラップせずに直接
int i;
vfscanf(hFile,"%d",&i);
とすれば問題なく i に値が取得できるのですが。
お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!
似たような質問が見つかりました
- C言語・C++・C# c言語の問題の説明、各所ごとに 5 2023/07/26 11:03
- 大学・短大 C言語線形リストの問題です 3 2022/12/22 00:45
- C言語・C++・C# c言語の問題です 2 2023/07/21 10:51
- C言語・C++・C# c言語の問題です 3 2023/01/10 16:15
- C言語・C++・C# C言語 3 2022/10/04 15:07
- C言語・C++・C# 至急教えてください。プログラミングの問題です。 malloc関数を使ってください!お願いします! 最 1 2022/07/21 09:28
- C言語・C++・C# 至急お願いします。プログラミングの問題です。 最初に正の整数nの入力を受け付け、次に分数の分子と分母 3 2022/07/19 17:09
- C言語・C++・C# 至急教えてください。プログラミングの問題です。 最初に正の整数nの入力を受け付け、次に分数の分子と分 1 2022/07/19 17:03
- C言語・C++・C# C言語の課題が出たのですが自力でやっても分かりませんでした。 要素数がnであるint型の配列v2の並 3 2022/11/19 17:41
- Visual Basic(VBA) VBAでの共有パスにつきまして 1 2023/03/04 17:24
関連するカテゴリからQ&Aを探す
おすすめ情報
デイリーランキングこのカテゴリの人気デイリーQ&Aランキング
-
セグメントエラー
-
fopne で失敗する原因
-
init関数の意味
-
Run-Time Check Failure #3とい...
-
C言語のポインタに直接アドレス...
-
C言語でのconstを返す関数
-
C言語の関数と配列に関する質問
-
戻り値で構造体を返すことは可...
-
LPSTR型の初期化について
-
パスからファイル名を抽出
-
【なぜポインタを使うのか】
-
ハンドル、アドレス、ポインタ...
-
C++とWIN32APIとゲームプログラ...
-
コンストラクタでnewを失敗した...
-
ポインタを使うことのメリット...
-
AESのC言語による実装
-
NULLとブランクの違い
-
C言語でポインタを使ってピタ...
-
TCHAR文字列内の検索について
-
単方向リスト
マンスリーランキングこのカテゴリの人気マンスリーQ&Aランキング
-
C言語のポインタに直接アドレス...
-
C言語の関数と配列に関する質問
-
戻り値で構造体を返すことは可...
-
fopne で失敗する原因
-
init関数の意味
-
Run-Time Check Failure #3とい...
-
LPSTR型の初期化について
-
セグメントエラー
-
アプリを32bitから64bit移行
-
コンストラクタでnewを失敗した...
-
ExcelVBAでのkernel32(64bit)
-
Cで作成したDLL関数をVBから呼...
-
ハンドルはポインタか
-
DLL<->VB間での受け渡し(文字...
-
C言語でのconstを返す関数
-
ポインタについて
-
参照型で受け取った引数をポイ...
-
TCHAR文字列内の検索について
-
デバイスハンドルとは?
-
基本アルゴリズムの『返す』の...
おすすめ情報