プロが教える店舗&オフィスのセキュリティ対策術

【環境】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() のラッパー関数を試した場合は正しく動作しました。

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

A 回答 (6件)

# 誤解させました(というか問題を読み違えて誤解してた…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 …
    • good
    • 0
この回答へのお礼

>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値が取れました。

ご丁寧にありがとうございます。

お礼日時:2006/10/11 00:40

'v'で始まる関数はva_list対応版なのでvfprintfはOKです。

fprintfではNGになります。
同様に、vfscanfがあればva_listがOKで、fscanfでは現状のようなNGとなります。
fprintfで試してみると失敗するはずで、
「"fprintfに"二つ以上の引数を渡しても、正しく出力しますか?」等と書くべきでした。すみません。

なお、
> ctextfileio.cpp(90) : error C2018: 文字 '0x81' は認識できません。
これらのエラーは、全角空白を含む場合に出るものです。半角に置換してください。
そのままコピペすると掲示板での見やすさの為に全角空白でインデントされていると思います。
    • good
    • 0
この回答へのお礼

>> ctextfileio.cpp(90) : error C2018: 文字 '0x81' は認識できません。
>これらのエラーは、全角空白を含む場合に出るものです。
おっしゃる通りです。初歩的ミスでお恥ずかしい。。。

ありがとうございました。

お礼日時:2006/10/12 14:53

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系の代わり)には使えなかったと思います。
    • good
    • 0
この回答へのお礼

ご提示いただいたコードをコンパイルすると以下のエラーが出ました。何かオプション指定が必要なのでしょうか。

ctextfileio.cpp(90) : error C2018: 文字 '0x81' は認識できません。
ctextfileio.cpp(90) : error C2018: 文字 '0x40' は認識できません。

>std::istreamに置き換えるなどした方が、本当はよいと思います。

そうですね。fscanf()にこだわった理由は、1行で異なる型の値を一気に読込めると便利だと考えたからですが、
fstream<<5000<<'Y'<<"String"<<endl;
こちらのほうが可読性高い分、妥当かと思いました。

ありがとうございました。

お礼日時:2006/10/11 01:00

#2>arg と i がメモリ上の同じアドレスを指すようにできれば


fscanf(hFile,lpFormat,arg);

fscanf(hFile, lpFormat, va_arg(arg,int*));
とはできるんじゃないかと思いますが、
これだと(可変引数にするには)lpFormat の% の切り出しが必要ですね^^;
    • good
    • 0
この回答へのお礼

BLUEPIXYさん、ありがとうございます。

>fscanf(hFile, lpFormat, va_arg(arg,int*));

で int 値が正しく読込めました。
ただ、可変引数に対応させるより std::fstream 使ったほうがスマートだと納得できましたので、fstream をそのまま使うか、継承して使おうと思います。

お礼日時:2006/10/11 01:12

検証して無くて申し訳ないですが、


仮にvfscanfがあったとしても多分va_listがcdeclcall前提なので、
クラスの非スタティックなメンバ関数(thiscall)だとスタックの調整でうまく動かない可能性があるはず。
スタックの位置はあってますか。

一度、クラス以外のグローバル関数でも試してみてもらえますか。
それでうまくいくようなら、自力でスタックごにょごにょしないと、
クラスのメンバ関数ではうまくいかないと思われます。

この回答への補足

自己レスです。

>arg と i がメモリ上の同じアドレスを指すようにできれば、i にファイルからの
>読取値が正しく取得できるかどうか試してみたいと思います。

そもそも arg と i は別々の変数ですから同じアドレスを指すことは不可能ですよね。試したかったのは、va_listが内部で管理している可変個引数へのポインタ(どのように管理しているかは知りませんが)が、i のアドレスを指すように調整することです。

fscanf() をクラスのメンバ関数でラップした場合に動作しない原因が、スタックの問題だとすると、vfprintf() を同じようにクラスのメンバ関数でラップ(メンバ関数内でva_listを宣言・使用)した場合に動作する理由がわかりません。

補足日時:2006/10/10 00:12
    • good
    • 0
この回答へのお礼

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 にファイルからの読取値が正しく取得できるかどうか試してみたいと思います。
検討違いのことを試しているようでしたら、ご指摘いただけると幸いです。

お礼日時:2006/10/09 22:45

> fscanf(this->hFile,lpFormat,arg);



関数 vfscanf があるなら、これに取り替えれば動作するでしょうね。
    • good
    • 0
この回答へのお礼

epistemeさん、アドバイスありがごうございます。

残念ながら<stdio.h>に vfscanf はありませんでした。
vfprintf があるなら vfscanf もあるだろうと思ったのですが。。。

ワイド文字版の fwscanf を試しましたが、やはり値を読込むことができませんでした。
ラップせずに直接

int i;
vfscanf(hFile,"%d",&i);

とすれば問題なく i に値が取得できるのですが。

お礼日時:2006/10/09 18:47

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!