scanfについて
scanfについて
scanfについて
scanfは通常、キーボードからの入力を受け取る関数ですが
注意すべきことがあります。
それはscanfはバグに関係することが多い関数であることです。
想定された範囲内の入力が保障されている場合のみに使うべきです。
その意味でテストや学習段階では大いに役立つ関数と言えます。
しかし、実用プログラムでは使うべきではないと考えます。
scanfはファイルを読み込む場合にも使えますが、この場合にも
使い方を慎重にしなければなりません。確かにキーボードとは
違い入力されたデータは限定されていますのでその分バグは
少ないですが、異常データが入っていた場合はファイルを
ぐじゃぐじゃにし兼ねません。
皆さんはscanfについてどのような取り扱いをなさっていますか。
ご意見が聞きたいと思い掲載しました。
scanfは通常、キーボードからの入力を受け取る関数ですが
注意すべきことがあります。
それはscanfはバグに関係することが多い関数であることです。
想定された範囲内の入力が保障されている場合のみに使うべきです。
その意味でテストや学習段階では大いに役立つ関数と言えます。
しかし、実用プログラムでは使うべきではないと考えます。
scanfはファイルを読み込む場合にも使えますが、この場合にも
使い方を慎重にしなければなりません。確かにキーボードとは
違い入力されたデータは限定されていますのでその分バグは
少ないですが、異常データが入っていた場合はファイルを
ぐじゃぐじゃにし兼ねません。
皆さんはscanfについてどのような取り扱いをなさっていますか。
ご意見が聞きたいと思い掲載しました。
Re: scanfについて
こんにちは。C言語初心者です。
確かにVC++などでscanfと打つと、警告が出ますね。
例えば1=>遊ぶ 2=>寝る 3=>終わり といった表示をして、数値の入力が欲しい際は
僕はgetsで文字列を受け取り、sscanfで数値に変換・・・といった風にしています。
もしかしたらもっと良い方法があるかもしれません。
確かにVC++などでscanfと打つと、警告が出ますね。
例えば1=>遊ぶ 2=>寝る 3=>終わり といった表示をして、数値の入力が欲しい際は
僕はgetsで文字列を受け取り、sscanfで数値に変換・・・といった風にしています。
もしかしたらもっと良い方法があるかもしれません。
Re: scanfについて
scanfの問題は2つあると思います。
後者は%256sのようにサイズを指定すればオーバーランを防ぐことができます。
標準入力なので、わたしはツールで設定ファイルやスクリプトを読み込むような使い方しかしませんが、とうぜん想定外のデータを読み込む可能性を考慮してコードを書いてますよ。
scanfは基本的な書式に合っているかどうかを判断してくれるので便利だと思います。
勝手に空白文字で区切られるので文字列を扱いにくい、とか、改行も空白文字扱いなので行単位の処理ができない、といったことは単にscanfの仕様に合致しない使い方をしようとしているだけなので、使ってはいけないということにはならないですよね。
どんな問題があるのか具体的に教えていただけないでしょうか。
- 書式指定子に合致しない入力があるとバッファに溜まったままになるので無限ループになる
- %sがバッファ(配列)サイズを超えて文字列を書き込む可能性がある
後者は%256sのようにサイズを指定すればオーバーランを防ぐことができます。
標準入力なので、わたしはツールで設定ファイルやスクリプトを読み込むような使い方しかしませんが、とうぜん想定外のデータを読み込む可能性を考慮してコードを書いてますよ。
scanfは基本的な書式に合っているかどうかを判断してくれるので便利だと思います。
勝手に空白文字で区切られるので文字列を扱いにくい、とか、改行も空白文字扱いなので行単位の処理ができない、といったことは単にscanfの仕様に合致しない使い方をしようとしているだけなので、使ってはいけないということにはならないですよね。
どんな問題があるのか具体的に教えていただけないでしょうか。
Re: scanfについて
私の意見を言わせていただくと...ISLe さんが書きました:どんな問題があるのか具体的に教えていただけないでしょうか。
・%dや%f等で数値を読み込む場合、オーバーフローが発生すると未定義の動作を引き起こします。
・%lcや%lsでワイド文字(列)を読み込む場合、元のデータに不正なシーケンスが含まれると未定義の動作を引き起こします。
結局安全に読み込めるのは、(ワイドではない)文字または文字列しかありません。
Re: scanfについて
Windowsのコンソールアプリを開発した経験がありませんので
MSDOSと同じと考えて良いかは分かりませんが、
私の場合はgetch()を使いました。当初はgetche()も併用しましたが
使い勝手からgetch()一本に絞りました。
この関数はキーボードバッファから順に1コードづつ取り出すことが
できます。
取り出したコード毎に指定の範囲内のコードかを判断し、
範囲外のコードは捨てます。
例えば数字の0~9までの数字ならOKとする訳です。
これらを順に桁順に文字列に取り込みます。
文字配列は全桁を事前にヌル(0x00)で初期化しておきます。
もちろん最大桁が8桁なら9桁分の文字列を用意しておきます。
入力の完了を示すコード(通常はenterの(0x0d))が来た時に
そのコード(0x0d)は取り込まずに文字列を数値に変換すれば
完了です。
入力回数を制限するカウントはこの例では8となります。
オーバーする場合はエラー処理することが必要です。
私の場合は後の利用(表示)を考えて敢えて数値に変換せず、
文字列のまま取り出すようにしています。
int tenkey(char *mojiretu,int cnt)となります。
Windowsの場合は1キー毎に制御をWindowsに戻すために、
getch()も、ローカル変数も使えません。工夫が必要になります。
しかし、考え方は同じです。
MSDOSと同じと考えて良いかは分かりませんが、
私の場合はgetch()を使いました。当初はgetche()も併用しましたが
使い勝手からgetch()一本に絞りました。
この関数はキーボードバッファから順に1コードづつ取り出すことが
できます。
取り出したコード毎に指定の範囲内のコードかを判断し、
範囲外のコードは捨てます。
例えば数字の0~9までの数字ならOKとする訳です。
これらを順に桁順に文字列に取り込みます。
文字配列は全桁を事前にヌル(0x00)で初期化しておきます。
もちろん最大桁が8桁なら9桁分の文字列を用意しておきます。
入力の完了を示すコード(通常はenterの(0x0d))が来た時に
そのコード(0x0d)は取り込まずに文字列を数値に変換すれば
完了です。
入力回数を制限するカウントはこの例では8となります。
オーバーする場合はエラー処理することが必要です。
私の場合は後の利用(表示)を考えて敢えて数値に変換せず、
文字列のまま取り出すようにしています。
int tenkey(char *mojiretu,int cnt)となります。
Windowsの場合は1キー毎に制御をWindowsに戻すために、
getch()も、ローカル変数も使えません。工夫が必要になります。
しかし、考え方は同じです。
Re: scanfについて
前者は、言語(この場合ライブラリ?)仕様に、「変換指定は,入力項目を,変換指定子に対して適切な型に変換する。」とあるのですが、「変換結果が指定されたオブジェクト内で表現できない場合」に当てはまってしまうのでしょうか。たかぎ さんが書きました:・%dや%f等で数値を読み込む場合、オーバーフローが発生すると未定義の動作を引き起こします。
・%lcや%lsでワイド文字(列)を読み込む場合、元のデータに不正なシーケンスが含まれると未定義の動作を引き起こします。
こちらも入力幅を指定して最大値を絞ってもダメですかね。
後者は、さすがに印字可能なテキストデータを読ませるという前提まで崩れるとどうしようもないというか。
どこかのハードが壊れているのではないかと疑いたくなりますが。
個別のプログラムにいちいちそこまで対応を求めるのは厳しいというか。
テキストデータとしての妥当性チェックはプリプロセスとして分離したほうが効率良い気がします。
そうなるとfgetsで読み込んでsscanfでも解決にはならないということですね。
scanfがダメな理由はどこですか?needsueda さんが書きました:しかし、考え方は同じです。
そもそも(scanfと言うより)標準入力に対して間違った印象を持っておられるように感じます。
Re: scanfについて
そういうことです。ISLe さんが書きました:前者は、言語(この場合ライブラリ?)仕様に、「変換指定は,入力項目を,変換指定子に対して適切な型に変換する。」とあるのですが、「変換結果が指定されたオブジェクト内で表現できない場合」に当てはまってしまうのでしょうか。
型の表現範囲よりかなり小さい範囲に絞ってよいならある程度は可能ですが、符号の有無や冗長な0、指数表現等まで考慮に入れると、思い通りに制御するのは無理です。ISLe さんが書きました:こちらも入力幅を指定して最大値を絞ってもダメですかね。
エンコーディング間違っているだけで、簡単に未定義の動作を引き起こします。ISLe さんが書きました:後者は、さすがに印字可能なテキストデータを読ませるという前提まで崩れるとどうしようもないというか。
どこかのハードが壊れているのではないかと疑いたくなりますが。
その通りです。ISLe さんが書きました:そうなるとfgetsで読み込んでsscanfでも解決にはならないということですね。
Re: scanfについて
例えば%dはstrtolと同等の変換をして、結果をオブジェクトに代入すると書いてあったので、自分でstrtolを使ってチェックするのと変わらないと思い込んでました。たかぎ さんが書きました:そういうことです。ISLe さんが書きました:前者は、言語(この場合ライブラリ?)仕様に、「変換指定は,入力項目を,変換指定子に対して適切な型に変換する。」とあるのですが、「変換結果が指定されたオブジェクト内で表現できない場合」に当てはまってしまうのでしょうか。
確かに(VC++2012で)実験してみたところ結果が異なりました。
#未定義の動作だから同じ可能性もあったわけですが。
ツールプログラムから吐き出したテキストデータを読み込むのに楽なんですけどね。
緊急時にはテキストエディタで手直しもできるし。
これは想定された範囲内のデータだから良いのか。
でも緊急時に不具合が出るととても困るので、即席使い捨ての簡易ツールであろうと、scanfで業務に使うプログラム作るのは自殺行為ということですね。
肝に銘じます。
Re: scanfについて
そうでもないです。ISLe さんが書きました:でも緊急時に不具合が出るととても困るので、即席使い捨ての簡易ツールであろうと、scanfで業務に使うプログラム作るのは自殺行為ということですね。
(ワイドではない)文字や文字列の読み込みを行う分には問題ありませんので、scanfを使っても安全です。
fgetsで読み込んでから改行文字を取り除くよりscanfを使う方が便利なこともあれば、0~9の数字の並びだけで構成される文字列を読み込みたい場合などもscanfの方が便利だったりします。
Re: scanfについて
私がscanfを問題提起したのは、scanfはとても手軽に使える関数であるだけに
その危険性を警鐘したかったからです。十分に意識して使えれば
コンソールプログラムではこれほど便利な関数はありません。
私の業務用プログラム(DOS版)ではキーボードの入力にはscanfは使わず
先に記述しているgetch()を利用した専用関数tenkeyを経由して取り込んでいます。
但しファイルを読み込む場合にはscanfの仲間のsscanfを使っています。
キーボードからとは違い、範囲が限定されるため、
異常データに出会う確立はかなり低いと言う理由からです。
直接の読み込みはread()を使い文字列に取り込みます。
・・・何故read()を使うのかは別の所で説明したいと思います。
この文字列から実際に使う変数に代入するためにsscanf()を使います。
ただこうした手段でも、sscanf()がファイルをぐじゃぐじゃにする危険性は
残ります。データファイルは主にテキストファイルですが、壊した経験を
持っています。幸い壊れたのは一部でしたので、何とか修復することができました。
原因は文字データが崩れたことでした。壊れることを再現するのに1週間は
かかりましたが、何とか原因を突き止め、プログラムを修正しました。
文字列を書き込む時は左詰めにし、読み込む時は%S(Sの前には取り込む数字あり)
ではなく%C(Cの前には取り込む数字あり)で崩れないことを確認しました。
以後ファイルが崩れることは一度もありません。現在はこの手法を継承しています。
その危険性を警鐘したかったからです。十分に意識して使えれば
コンソールプログラムではこれほど便利な関数はありません。
私の業務用プログラム(DOS版)ではキーボードの入力にはscanfは使わず
先に記述しているgetch()を利用した専用関数tenkeyを経由して取り込んでいます。
但しファイルを読み込む場合にはscanfの仲間のsscanfを使っています。
キーボードからとは違い、範囲が限定されるため、
異常データに出会う確立はかなり低いと言う理由からです。
直接の読み込みはread()を使い文字列に取り込みます。
・・・何故read()を使うのかは別の所で説明したいと思います。
この文字列から実際に使う変数に代入するためにsscanf()を使います。
ただこうした手段でも、sscanf()がファイルをぐじゃぐじゃにする危険性は
残ります。データファイルは主にテキストファイルですが、壊した経験を
持っています。幸い壊れたのは一部でしたので、何とか修復することができました。
原因は文字データが崩れたことでした。壊れることを再現するのに1週間は
かかりましたが、何とか原因を突き止め、プログラムを修正しました。
文字列を書き込む時は左詰めにし、読み込む時は%S(Sの前には取り込む数字あり)
ではなく%C(Cの前には取り込む数字あり)で崩れないことを確認しました。
以後ファイルが崩れることは一度もありません。現在はこの手法を継承しています。
Re: scanfについて
getch ってどういう関数ですか。
ISO/IEC 9899:1999 (C99) をあたりましたが,そのような関数はありません。
7.19.7.5 に getc が,7.19.7.6 に getchar が定義されていますが,それらではないんですよね。
read ってどういう関数ですか。
同じく C99 にはありませんでした。
index 上でも,rank の次が real floating type conversion ですから,read から始まる関数は存在しません。
7.19.8.1 で定義される,fread でも当然ないんですよね。
非標準の関数を標準関数である scanf (7.19.6.4) の代替に使っていると言われると,それはそれで不思議な感じがします。
それとも,C11 で追加された標準関数なのでしょうか。
個人的には,C で標準入力相手に Format されたータを取り扱うことがないことから,scanf は最近とんと使っていないです。
# 正規表現つかって解析するか,対象が XML というレベルになるので scanf は力不足であることが多い。
ISO/IEC 9899:1999 (C99) をあたりましたが,そのような関数はありません。
7.19.7.5 に getc が,7.19.7.6 に getchar が定義されていますが,それらではないんですよね。
read ってどういう関数ですか。
同じく C99 にはありませんでした。
index 上でも,rank の次が real floating type conversion ですから,read から始まる関数は存在しません。
7.19.8.1 で定義される,fread でも当然ないんですよね。
非標準の関数を標準関数である scanf (7.19.6.4) の代替に使っていると言われると,それはそれで不思議な感じがします。
それとも,C11 で追加された標準関数なのでしょうか。
個人的には,C で標準入力相手に Format されたータを取り扱うことがないことから,scanf は最近とんと使っていないです。
# 正規表現つかって解析するか,対象が XML というレベルになるので scanf は力不足であることが多い。
- softya(ソフト屋)
- 副管理人
- 記事: 11677
- 登録日時: 13年前
- 住所: 東海地方
- 連絡を取る:
Re: scanfについて
getch()やread()は両方共POSIX 関数ですね。Unix文化の産物でMS-DOSの亡霊です。getch()は現在のPOSIXでも載っていないのでいつの規格にあったか不明。
「POSIX - Wikipedia」
http://ja.wikipedia.org/wiki/POSIX
当然ながらISO Cには含まれませんがMS-DOSなどの過去のしがらみの互換上Windowsで利用できます。
まぁマイクロソフトでは今は非推奨です。
「使用を推奨されていない CRT 関数」
http://msdn.microsoft.com/ja-jp/library ... s.80).aspx
ある程度年季の入った人だとC言語の標準規格関数と同一扱いしている可能性があります。[ちょっと修正]
[追記]確かMS-DOSの定番コンパイラMS-Cのマニュアルには普通にgetch()が載っていたはずです。
ちなみにread()に関してはUnix/Linuxで現役です。
「Man page of READ」
http://linuxjm.sourceforge.jp/html/LDP_ ... ead.2.html
「POSIX - Wikipedia」
http://ja.wikipedia.org/wiki/POSIX
当然ながらISO Cには含まれませんがMS-DOSなどの過去のしがらみの互換上Windowsで利用できます。
まぁマイクロソフトでは今は非推奨です。
「使用を推奨されていない CRT 関数」
http://msdn.microsoft.com/ja-jp/library ... s.80).aspx
ある程度年季の入った人だとC言語の標準規格関数と同一扱いしている可能性があります。[ちょっと修正]
[追記]確かMS-DOSの定番コンパイラMS-Cのマニュアルには普通にgetch()が載っていたはずです。
ちなみにread()に関してはUnix/Linuxで現役です。
「Man page of READ」
http://linuxjm.sourceforge.jp/html/LDP_ ... ead.2.html
by softya(ソフト屋) 方針:私は仕組み・考え方を理解して欲しいので直接的なコードを回答することはまれですので、すぐコードがほしい方はその旨をご明記下さい。私以外の方と交代したいと思います(代わりの方がいる保証は出来かねます)。
Re: scanfについて
「0~9の数字の並びだけで構成される文字列を読み込みたい場合」の意味はあくまで文字列として読み込むということですよね。たとえばたかぎ さんが書きました:fgetsで読み込んでから改行文字を取り除くよりscanfを使う方が便利なこともあれば、0~9の数字の並びだけで構成される文字列を読み込みたい場合などもscanfの方が便利だったりします。
//標準入力から0から9だけで構成される部分を読み込んで1行ずつ標準出力に印字する
#include <stdio.h>
int main(void)
{
int n;
char s[256];
scanf("%*[^0123456789]");
while ((n = scanf("%10[0123456789]", s)) != EOF) {
if (n == 1) {
printf("%s", s);
} else {
scanf("%*[^0123456789]");
putchar('\n');
}
}
return 0;
}
結局、scanfは(ワイドではない)文字列や文字を読み込むのはOKだけど、文字列をパースしてintやdoubleに代入するような使い方は避けたほうがいいということですかね。
Re: scanfについて
printfで出力した結果をパイプで読み込む場合などは良いのではないでしょうか。
その場合でも運用でカバーの部分が大きいので、想定された範囲内の入力が保障されているとは言えませんが。
わたしはそういう使い方しかしませんし、キーボード入力対応の方がオマケだと思ってます。
#PerlやRubyが使えるときなどはそちらを使いますし。
scanf自体にも問題がありますが、入門書では他に簡単に使えるものがないのでscanfを使うのを、それをキーボード入力にはscanfを使うものだと思い込んでしまうひとが多いのがさらに問題を大きくしていると思います。
getchはDOSコールが由来ではなかったでしたっけ。
もともとハードウェアレベルでキーボードバッファの内容を読み取るものなのでそもそもの用途が異なります。
コンソールアプリのゲームを作るときにkbhitと組み合わせて使ったりしますが、MS-DOS(やウィンドウズ)でしかまともに動かないものです。
Linuxではコンソールの仕組みが根本的に違うので即時性を期待できません。
その場合でも運用でカバーの部分が大きいので、想定された範囲内の入力が保障されているとは言えませんが。
わたしはそういう使い方しかしませんし、キーボード入力対応の方がオマケだと思ってます。
#PerlやRubyが使えるときなどはそちらを使いますし。
scanf自体にも問題がありますが、入門書では他に簡単に使えるものがないのでscanfを使うのを、それをキーボード入力にはscanfを使うものだと思い込んでしまうひとが多いのがさらに問題を大きくしていると思います。
getchはDOSコールが由来ではなかったでしたっけ。
もともとハードウェアレベルでキーボードバッファの内容を読み取るものなのでそもそもの用途が異なります。
コンソールアプリのゲームを作るときにkbhitと組み合わせて使ったりしますが、MS-DOS(やウィンドウズ)でしかまともに動かないものです。
Linuxではコンソールの仕組みが根本的に違うので即時性を期待できません。
Re: scanfについて
私が一番の基本教科書としているのは
実習C言語 三田典玄氏 アスキー出版です。
既に古典に類するかも知れませんね。
ぼろぼろになるまで熟読しました。今でも本棚に置いています。
NBasicからプログラミングを始めて、QC、MS-C7に進み
VC6++でWindows上で開発しています。
getch()についてはコンソールから1文字読み込む関数として使用しています。
(P268記載 DOS専用関数 MSC7では_getch())
当時はMSDOSでの開発になりますから、これを使用しました。
標準関数ではないではないかの指摘がありますが、
使用しているコンパイラーに標準として使用できるなら、標準関数並に
使って差し支えないと考えます。他のコンパイラーで使用する際に利用できない
ことがあることを予め留意すればそれで良いのではないでしょうか。
POSIX 関数云々も同じ考え方で良いのではないでしょうか。言葉よりも
実際に使うことができるかどうかではないかと考えます。
下記に初版を開示します。現在はWindows用に改変されています。考え方は同じです。
実習C言語 三田典玄氏 アスキー出版です。
既に古典に類するかも知れませんね。
ぼろぼろになるまで熟読しました。今でも本棚に置いています。
NBasicからプログラミングを始めて、QC、MS-C7に進み
VC6++でWindows上で開発しています。
getch()についてはコンソールから1文字読み込む関数として使用しています。
(P268記載 DOS専用関数 MSC7では_getch())
当時はMSDOSでの開発になりますから、これを使用しました。
標準関数ではないではないかの指摘がありますが、
使用しているコンパイラーに標準として使用できるなら、標準関数並に
使って差し支えないと考えます。他のコンパイラーで使用する際に利用できない
ことがあることを予め留意すればそれで良いのではないでしょうか。
POSIX 関数云々も同じ考え方で良いのではないでしょうか。言葉よりも
実際に使うことができるかどうかではないかと考えます。
下記に初版を開示します。現在はWindows用に改変されています。考え方は同じです。
/* DOS PC9800 当初版 QCで開発した */
int tenkey(int max,char *sumoji) /* 0-9までの数字入力 max最大桁数 */
{
int i,sw;
int c;
sw=0;
cur_on();
cur_save();
while(1)
{
cur_load();
spcn_str(sumoji,max+1); /* 1行クリアからスペースに変更 97.05.19 */
printf("%s",sumoji);
cls_str(sumoji,max+2); // 0x00で埋める
cur_load();
for (i=0;i<max+1;i++)
{
c=getche(); // 最初はエコー付きであったが後日はgetch()に変更される
if( c==0xd)
{
sw=i;
break;
}
if( i==max)
{
sw=0;
break;
}
if((c<'0') || (c>'9'))
{
sw=0;
break;
}
sumoji[i]=(char)c;
}
if(sw!=0){
break;
}
beep();beep();
}
cur_off();
return(sw);
}
- softya(ソフト屋)
- 副管理人
- 記事: 11677
- 登録日時: 13年前
- 住所: 東海地方
- 連絡を取る:
Re: scanfについて
codeタグをお使いください。掲示板での可読性が全然違います。
今回は私が変更しておきました。
needsuedaさんの考えとしては、何時無くなるか保証がないgetch()やread()は今は使えるから無くなってから考えると言うことでしょうか?
ただ、今の人がgetch()やread()を使うのを推奨する理由にはならないと考えます。
つまり、第三者に例として教えるのは良くないのではないでしょうか?
>標準関数ではないではないかの指摘がありますが、使用しているコンパイラーに標準として使用できるなら、標準関数並に使って差し支えないと考えます。
他のコンパイラーとの間の行き来をしていたので私の見解は異なりますね。
ちなみに私はDOS時代からscanf系の未定義動作をいちいちに気にするのが嫌なので、まったく利用していません。
今回は私が変更しておきました。
needsuedaさんの考えとしては、何時無くなるか保証がないgetch()やread()は今は使えるから無くなってから考えると言うことでしょうか?
ただ、今の人がgetch()やread()を使うのを推奨する理由にはならないと考えます。
つまり、第三者に例として教えるのは良くないのではないでしょうか?
>標準関数ではないではないかの指摘がありますが、使用しているコンパイラーに標準として使用できるなら、標準関数並に使って差し支えないと考えます。
他のコンパイラーとの間の行き来をしていたので私の見解は異なりますね。
ちなみに私はDOS時代からscanf系の未定義動作をいちいちに気にするのが嫌なので、まったく利用していません。
by softya(ソフト屋) 方針:私は仕組み・考え方を理解して欲しいので直接的なコードを回答することはまれですので、すぐコードがほしい方はその旨をご明記下さい。私以外の方と交代したいと思います(代わりの方がいる保証は出来かねます)。
Re: scanfについて
あ,もちろんgetchやreadの存在は知っていますよ。softya(ソフト屋) さんが書きました:getch()やread()は両方共POSIX 関数ですね。Unix文化の産物でMS-DOSの亡霊です。getch()は現在のPOSIXでも載っていないのでいつの規格にあったか不明。
どこまでPOSIXか,とかは認識していませんが。
ただ,scanfと並列に論じていたので,「その出所不明の非標準関数は何だ」ということを言いたくなった次第です。
# 非標準関数を話すなら環境を定義しろ,ということです。ここの掲示板ローカルでは,DxLibは標準の仲間扱いでよいでしょうけれども。
「他の環境 (バージョン違い含む) で使えない可能性があること」を認識して,needsueda さんが書きました:標準関数ではないではないかの指摘がありますが、
使用しているコンパイラーに標準として使用できるなら、標準関数並に
使って差し支えないと考えます。他のコンパイラーで使用する際に利用できない
ことがあることを予め留意すればそれで良いのではないでしょうか。
かつそれを前提として使うのは構わないと思います。
ただ,scanfを問題視しておきながら,特定の環境でしか使えない関数を環境の定義もせず書いていますよね。
・使えることは確実,ただし使い方に注意
と
・出所不明の何をしているのかわからない関数
では,後者の方が問題です。
どの環境で話をしているのかを書いて,初めて非標準関数は定義されます。
Re: scanfについて
処理系を特定すればscanfだって安全に使えることになりますね。needsueda さんが書きました:使用しているコンパイラーに標準として使用できるなら、標準関数並に
使って差し支えないと考えます。他のコンパイラーで使用する際に利用できない
ことがあることを予め留意すればそれで良いのではないでしょうか。
Re: scanfについて
ソースの表記codeの仕方がよく分からなかったものですから
そのままにしました。お手数をお掛けし申し訳けありませんでした。
さて、随分とgetch()にこだわってしまったように思います。
開発当初の環境がたまたまDOSであり、開発がQCで行われたから
getch()を使っただけです。表題はそちらではなかったんですが。
目的はキーボードバッファから1バイト分づつ取り出すことにあります。
たまたま手じかにあったgetch()を使っただけなのです。
ですから、もし他に別の関数が適当とお考えならそれを使えば
良いと思います。ご自分が理解できる関数を使う方が良いと思います。
その意味で標準関数云々は間違っているよと申し上げたのです。
さて、scanfの戻値をチェックすることで、うまく取り込めたかの
判断はできます。ですが、作成されたプログラムを使う人の立場から
考えると、再入力を促したりせねばならず、負担を掛けることになります。
例えば数字キーのみを入力する事を前提とすると、
当人は全て数字キーのみを押すつもりで入力するはずです。
その中に別のキーコードが紛れ込んだとするとどうでしょうか。
エラーとなり、もう一度再入力をせねばなりません。
それよりは、入力したキーコードを1コード毎に事前にチェックし
OKのみの数字を数字文字列にセットして行く方がベターだと思いませんか。
なお、Windows版では押し下げたキーのコードを1コード毎Windowsから
提供されますので当然getch()等の関数は必要ありません。
参考までに下記に要点のみ記載します。行数は数行で要点のみですのでcodeは付けていません。
悪しからず。
//ウインドウプロシージャ
LRESULT CALLBACK WndProc(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam)
{
// 中略
switch (message)
{
case WM_KEYDOWN:
// キー入力
key_sw = wParam;
// 後略
となりますので、1コード毎にkey_swをチェックしながら取り込むことになります。
1コード毎に一旦この関数WndProc()を出ますので、文字列はローカル変数ではなく、
グローバル変数になる違いが出ます。
(コンソールではなくシングルウインドウでのソース例)
そのままにしました。お手数をお掛けし申し訳けありませんでした。
さて、随分とgetch()にこだわってしまったように思います。
開発当初の環境がたまたまDOSであり、開発がQCで行われたから
getch()を使っただけです。表題はそちらではなかったんですが。
目的はキーボードバッファから1バイト分づつ取り出すことにあります。
たまたま手じかにあったgetch()を使っただけなのです。
ですから、もし他に別の関数が適当とお考えならそれを使えば
良いと思います。ご自分が理解できる関数を使う方が良いと思います。
その意味で標準関数云々は間違っているよと申し上げたのです。
さて、scanfの戻値をチェックすることで、うまく取り込めたかの
判断はできます。ですが、作成されたプログラムを使う人の立場から
考えると、再入力を促したりせねばならず、負担を掛けることになります。
例えば数字キーのみを入力する事を前提とすると、
当人は全て数字キーのみを押すつもりで入力するはずです。
その中に別のキーコードが紛れ込んだとするとどうでしょうか。
エラーとなり、もう一度再入力をせねばなりません。
それよりは、入力したキーコードを1コード毎に事前にチェックし
OKのみの数字を数字文字列にセットして行く方がベターだと思いませんか。
なお、Windows版では押し下げたキーのコードを1コード毎Windowsから
提供されますので当然getch()等の関数は必要ありません。
参考までに下記に要点のみ記載します。行数は数行で要点のみですのでcodeは付けていません。
悪しからず。
//ウインドウプロシージャ
LRESULT CALLBACK WndProc(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam)
{
// 中略
switch (message)
{
case WM_KEYDOWN:
// キー入力
key_sw = wParam;
// 後略
となりますので、1コード毎にkey_swをチェックしながら取り込むことになります。
1コード毎に一旦この関数WndProc()を出ますので、文字列はローカル変数ではなく、
グローバル変数になる違いが出ます。
(コンソールではなくシングルウインドウでのソース例)
Re: scanfについて
WM_KEYDOWN の wParam は仮想キーコードですよ。needsueda さんが書きました: なお、Windows版では押し下げたキーのコードを1コード毎Windowsから
提供されますので当然getch()等の関数は必要ありません。
参考までに下記に要点のみ記載します。行数は数行で要点のみですのでcodeは付けていません。
悪しからず。となりますので、1コード毎にkey_swをチェックしながら取り込むことになります。//ウインドウプロシージャ LRESULT CALLBACK WndProc(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam) { // 中略 switch (message) { case WM_KEYDOWN: // キー入力 key_sw = wParam; // 後略
1コード毎に一旦この関数WndProc()を出ますので、文字列はローカル変数ではなく、
グローバル変数になる違いが出ます。
(コンソールではなくシングルウインドウでのソース例)
Aのキーを押しても、常に 0x41 です。Shiftキーを押すと 0x11 なので、
WM_KEYUP と合わせて、シフト状態を自分で覚えておかないと小文字の
'a' のコード 0x61 は取得できません。
あなたは本当に WM_KEYDOWN を使ってコードを書いているのですか?
WM_CHAR を使えというのならまだわかるのですが。
scanf についても Windows Message についても不確かな知識しか持たずに
scanf はダメだと言っているのではありませんか?
Re: scanfについて
おっしゃる通りです。Windowsについてはあまり知識はありません。
ただ、早くDOS版のプログラムをWindows版に移植する必要がありましたので
細かい点は無視して、必要な個所の知識のみで作りました。
製作期間はプロトタイプを造るまでおよそ3ヶ月しかありませんでした。
対象プログラムの総ソース行数はおよそ15万行。
1から作る時間的余裕はありません。
これまでのDOS用のソースをあまり作り直すことなく、
そのまま使うことが必要となりました。
DOSとWindowsの違いを修正することで期間短縮を狙った訳です。
開発はVC6++で行い、WinAPIでシングルウィンドウを選択しました。
VC++はこれまで扱った経験がないので、なるべくCのみで行うようにしました。
最大の問題はキーボードの入力をどうするかでした。
それに関連したのがご指摘のWM_KEYDOWNです。
候補は二つ、WM_CHAR、WM_KEYDOWNでした。
ポイントはコードを識別符号として利用している点にあります。
数字として入力するケースを考えますと、
キー「1」が必ずしも1として取り込む必要がなく、
キー「A」を1として取り込んでも良いと言う点にあります。
WM_CHARを使うとSIFTの有無で対象コードが増えてしまい、キーの特定が
面倒になるのでWM_KEYDOWNを選択しました。
通常のプログラムではWM_CHARでしょう。文字入力は日本語入力を含めて
WM_CHARを使います。
さて、移植したプログラムはいささか特殊なプログラムでして、
POSレジスタです。
通常は専用のキーボードを使いますのでこうした配慮は不要ですが、
PCキーボードをレジキーボードとして使えないかと考えた訳です。
つまりPCキーボードをレジキーボードに見立てるためです。
実際にどう処理したかは、説明が長くなり難しいのでここでは省きますが、
例えて言うと、_getch()は井戸です。Windowsは井戸が使えないので
代わりに水道を作ったと言うことになります。取り込まれるコードが
1コードを水1滴とイメージすると分かりやすくなります。
WndProc()関数のwPramを水源と考えて、
水1滴を所定の場所まで届ければ水道は完成します。
ただ、早くDOS版のプログラムをWindows版に移植する必要がありましたので
細かい点は無視して、必要な個所の知識のみで作りました。
製作期間はプロトタイプを造るまでおよそ3ヶ月しかありませんでした。
対象プログラムの総ソース行数はおよそ15万行。
1から作る時間的余裕はありません。
これまでのDOS用のソースをあまり作り直すことなく、
そのまま使うことが必要となりました。
DOSとWindowsの違いを修正することで期間短縮を狙った訳です。
開発はVC6++で行い、WinAPIでシングルウィンドウを選択しました。
VC++はこれまで扱った経験がないので、なるべくCのみで行うようにしました。
最大の問題はキーボードの入力をどうするかでした。
それに関連したのがご指摘のWM_KEYDOWNです。
候補は二つ、WM_CHAR、WM_KEYDOWNでした。
ポイントはコードを識別符号として利用している点にあります。
数字として入力するケースを考えますと、
キー「1」が必ずしも1として取り込む必要がなく、
キー「A」を1として取り込んでも良いと言う点にあります。
WM_CHARを使うとSIFTの有無で対象コードが増えてしまい、キーの特定が
面倒になるのでWM_KEYDOWNを選択しました。
通常のプログラムではWM_CHARでしょう。文字入力は日本語入力を含めて
WM_CHARを使います。
さて、移植したプログラムはいささか特殊なプログラムでして、
POSレジスタです。
通常は専用のキーボードを使いますのでこうした配慮は不要ですが、
PCキーボードをレジキーボードとして使えないかと考えた訳です。
つまりPCキーボードをレジキーボードに見立てるためです。
実際にどう処理したかは、説明が長くなり難しいのでここでは省きますが、
例えて言うと、_getch()は井戸です。Windowsは井戸が使えないので
代わりに水道を作ったと言うことになります。取り込まれるコードが
1コードを水1滴とイメージすると分かりやすくなります。
WndProc()関数のwPramを水源と考えて、
水1滴を所定の場所まで届ければ水道は完成します。
- softya(ソフト屋)
- 副管理人
- 記事: 11677
- 登録日時: 13年前
- 住所: 東海地方
- 連絡を取る:
Re: scanfについて
すいません。話の内容がすごくズレて感じます。
実務上時間がないので、やむなく実装したことは分かるのですが話がそれに終始していますので、scanfから離れていく一方です。
getch()が出てきた辺りからズレていると思うのですが、そこに戻ってgetch()とscanfを絡めた部分の説明と仕様上の問題で論じたいのか、実装上の問題で論じたいのか、話の持っていく方向を説明してもらえますか?
Windowsでのキー検知の問題はズレていくだけだと思います。この話は別にトピックを立ててはどうでしょうか?
実務上時間がないので、やむなく実装したことは分かるのですが話がそれに終始していますので、scanfから離れていく一方です。
getch()が出てきた辺りからズレていると思うのですが、そこに戻ってgetch()とscanfを絡めた部分の説明と仕様上の問題で論じたいのか、実装上の問題で論じたいのか、話の持っていく方向を説明してもらえますか?
Windowsでのキー検知の問題はズレていくだけだと思います。この話は別にトピックを立ててはどうでしょうか?
by softya(ソフト屋) 方針:私は仕組み・考え方を理解して欲しいので直接的なコードを回答することはまれですので、すぐコードがほしい方はその旨をご明記下さい。私以外の方と交代したいと思います(代わりの方がいる保証は出来かねます)。
Re: scanfについて
というか、そもそもがコードの移植性を軽視したために環境が変われば案の定問題が発生し、その原因をすり替えるためにscanfを悪者にしたというところではないでしょうか?
Re: scanfについて
そうですね。
話はscanfについてでしたね。
キー入力にscanfを使っていない例文にgetch()を挙げた事が
失敗でしたね。これが横道にそれた原因でしょうね。
本筋に戻しましょう。
scanfを話題にしたのは、余りに安易に使うのは危険だと言う
警鐘を鳴らしたかっただけなのです。当初に読んだ参考書にも
あちこちに書いてあったように思います。
実際に使って何度も失敗を繰り返した苦い経験を持っています。
ただ、昔のことですから具体的にどのような使い方をすると
バグるのかは忘れてしまいました。この経験から学んだことは
キーボード入力としてはscanfは使わない方が無難であると言う
ことでした。scanfを使っても必ずしもトラブル訳ではありません。
むしろ、トラブルことは少ないでしょう。通常のプログラムは
ご自分で使うことが多い。使い方は一定しています。
第一、自分で使っている時にバグれば直ぐにプログラムを修正します。
問題は他人が使った時に起こったケースです。どんな手順で発生したか
を探すのは大変です。こうしたトラブルは数ヶ月に1回起こる程度の
ものですから、それこそ藁の中の針を見つけるようなものです。
それに、バージョンの違いやプログラムサイズによって発生したり
しなかったりすることが絡んで来るともう手が負えない程になります。
バグを修正する動力はプログラミングの3倍以上の時間が掛かると
言われています。できるだけそうしたトラブルを避けたいと思うのが
人情でしょう。
話はscanfについてでしたね。
キー入力にscanfを使っていない例文にgetch()を挙げた事が
失敗でしたね。これが横道にそれた原因でしょうね。
本筋に戻しましょう。
scanfを話題にしたのは、余りに安易に使うのは危険だと言う
警鐘を鳴らしたかっただけなのです。当初に読んだ参考書にも
あちこちに書いてあったように思います。
実際に使って何度も失敗を繰り返した苦い経験を持っています。
ただ、昔のことですから具体的にどのような使い方をすると
バグるのかは忘れてしまいました。この経験から学んだことは
キーボード入力としてはscanfは使わない方が無難であると言う
ことでした。scanfを使っても必ずしもトラブル訳ではありません。
むしろ、トラブルことは少ないでしょう。通常のプログラムは
ご自分で使うことが多い。使い方は一定しています。
第一、自分で使っている時にバグれば直ぐにプログラムを修正します。
問題は他人が使った時に起こったケースです。どんな手順で発生したか
を探すのは大変です。こうしたトラブルは数ヶ月に1回起こる程度の
ものですから、それこそ藁の中の針を見つけるようなものです。
それに、バージョンの違いやプログラムサイズによって発生したり
しなかったりすることが絡んで来るともう手が負えない程になります。
バグを修正する動力はプログラミングの3倍以上の時間が掛かると
言われています。できるだけそうしたトラブルを避けたいと思うのが
人情でしょう。
- softya(ソフト屋)
- 副管理人
- 記事: 11677
- 登録日時: 13年前
- 住所: 東海地方
- 連絡を取る:
Re: scanfについて
実務を考えれば、scanfの動作仕様を知らないことにはscanfで安全には作れませんので初心者には荷が重いでしょう。
既に何らかの処理ライブラリを作ったしまった人にも無用なものだと思います。
あちこちの会社で使用を禁止している所も多かったですし、今時はGUIなので使う機会自体が少ないと思います。
※ scanfの仕様をまともに把握していない人が多いのがそもそもの原因ですが。
ただ、学校や入門書で安易にscanfを使う事がなくなるとは思えません。
文字のチェックや文字数値変換が初心者に荷が重いことには変わりないからです。
ISLeさんの様に自分で使うツールで使う分には問題は、ほぼありません。
客先に出すアプリ・ツールあれば、できるだけ不安要素を取り除きたいのが人情なので避けられても仕方ないと思います。
まぁ、現実として使うことが私の場合は少ないんですよね。特にGUIになってから皆無です。
既に何らかの処理ライブラリを作ったしまった人にも無用なものだと思います。
あちこちの会社で使用を禁止している所も多かったですし、今時はGUIなので使う機会自体が少ないと思います。
※ scanfの仕様をまともに把握していない人が多いのがそもそもの原因ですが。
ただ、学校や入門書で安易にscanfを使う事がなくなるとは思えません。
文字のチェックや文字数値変換が初心者に荷が重いことには変わりないからです。
ISLeさんの様に自分で使うツールで使う分には問題は、ほぼありません。
客先に出すアプリ・ツールあれば、できるだけ不安要素を取り除きたいのが人情なので避けられても仕方ないと思います。
まぁ、現実として使うことが私の場合は少ないんですよね。特にGUIになってから皆無です。
by softya(ソフト屋) 方針:私は仕組み・考え方を理解して欲しいので直接的なコードを回答することはまれですので、すぐコードがほしい方はその旨をご明記下さい。私以外の方と交代したいと思います(代わりの方がいる保証は出来かねます)。
Re: scanfについて
わたしの作るツールは業務に使うものです。
読み込ませるデータを作るのはわたしでないこともあります。
開発業務に使うもので、客先に出すものではないですけどね。
製品レベルで読み込むデータは整合性チェックをしますし、改竄チェック機構も入ります。
scanfが原因でバグっても二重三重のチェックで異常なデータが紛れ込むことが無いようにしています。
ユーザーが使用するアプリケーションプログラムにscanfを使うことはありません。
むしろバグったときはscanfが読み込んだテキストデータを見れば原因が一目瞭然です。
そのためにもわざわざいったんテキスト化してチェックポイントを増やしているのですから。
業務で括ると、開発も業務だし、お客さんが使うのも業務ですね。
プログラマの仕事もいろいろですし。
わたしも20年ほど前にPOSレジの仕事をしたことありますよ。
バーコードリーダーとかあってドライバでキーの打鍵をシミュレートするものですしGUIですから、そもそもscanfの登場の余地はないと思いますが。
一括りに危険だから禁止と言われれば、むしろメリットを活かし品質に対して慎重であるひとのほうが大きく反応するでしょう。
初心者向けでキーボード入力を取得するために、仕方なくscanfを使っているわけですから、初心者に対して分かりやすい説明をしてはいかがですか。
このような論争をたびたび起こして人の目に触れさせるのも悪くはないと思いますが。
横道ついでに。
移植で15万行を3ヵ月というのはわたしの感覚ではかなり余裕のあるスケジュールですね。
10万行程度なら3週間でマスターアップしたことありますし、プロトタイプであれば3日で動くところまで持ってったこともあります。
わたしならgetchをメッセージループを内包した実装に置き換えると思います。
打鍵を監視しているのなら入力コンポーネントも使っていないのでしょう。
井戸の使い勝手で足りるのなら水道(というか蛇口)に変えなくても、井戸はそのままで水源を切り替えれば良いだけです。
読み込ませるデータを作るのはわたしでないこともあります。
開発業務に使うもので、客先に出すものではないですけどね。
製品レベルで読み込むデータは整合性チェックをしますし、改竄チェック機構も入ります。
scanfが原因でバグっても二重三重のチェックで異常なデータが紛れ込むことが無いようにしています。
ユーザーが使用するアプリケーションプログラムにscanfを使うことはありません。
むしろバグったときはscanfが読み込んだテキストデータを見れば原因が一目瞭然です。
そのためにもわざわざいったんテキスト化してチェックポイントを増やしているのですから。
業務で括ると、開発も業務だし、お客さんが使うのも業務ですね。
プログラマの仕事もいろいろですし。
わたしも20年ほど前にPOSレジの仕事をしたことありますよ。
バーコードリーダーとかあってドライバでキーの打鍵をシミュレートするものですしGUIですから、そもそもscanfの登場の余地はないと思いますが。
一括りに危険だから禁止と言われれば、むしろメリットを活かし品質に対して慎重であるひとのほうが大きく反応するでしょう。
初心者向けでキーボード入力を取得するために、仕方なくscanfを使っているわけですから、初心者に対して分かりやすい説明をしてはいかがですか。
このような論争をたびたび起こして人の目に触れさせるのも悪くはないと思いますが。
横道ついでに。
移植で15万行を3ヵ月というのはわたしの感覚ではかなり余裕のあるスケジュールですね。
10万行程度なら3週間でマスターアップしたことありますし、プロトタイプであれば3日で動くところまで持ってったこともあります。
わたしならgetchをメッセージループを内包した実装に置き換えると思います。
打鍵を監視しているのなら入力コンポーネントも使っていないのでしょう。
井戸の使い勝手で足りるのなら水道(というか蛇口)に変えなくても、井戸はそのままで水源を切り替えれば良いだけです。
Re: scanfについて
文章って難しいですね。
1つのことを説明するために用いた反証が、今度は
新しい問題の種になる。実際に顔と顔を突き合わせて話しを
すると問題にはならないのですが。
言訳にはなりますが、当時はWindowsの開発を初めて3ヶ月です。
練習プログラムを組める程度の力しかありませんでした。
できるだけ、修正個所が少ないようにすることがポイントになる。
15万行と例を出したのは、下手をすると最低一年は掛かる危険性を
強調したかったからだけなんです。コピーペーストする程度だけなら
すぐ済みますが、物によっては何ヶ月も掛かってしまいます。
事実、キー入力と表示の同期の部分に1ヶ月も掛かってしまいましたし。
入力用ウインドウを別に作ってそこからもらうことも検討したけれど、
反って時間を食う懸念があったため残念しただけです。
結果として、選択が正しかったと確証を得たのは移植作業が終わってからでした。
scanfにわざと異常データを入れるツールとして使える提案は新鮮でした。
私には思い付かなかったことです。ありがとうございます。どこかで、
テストで挿入して見たいと思います。
scanfについてはこの辺で話を終了したいと思います。
みなさん、色々とありがとうごさいました。楽しかったです。
1つのことを説明するために用いた反証が、今度は
新しい問題の種になる。実際に顔と顔を突き合わせて話しを
すると問題にはならないのですが。
言訳にはなりますが、当時はWindowsの開発を初めて3ヶ月です。
練習プログラムを組める程度の力しかありませんでした。
できるだけ、修正個所が少ないようにすることがポイントになる。
15万行と例を出したのは、下手をすると最低一年は掛かる危険性を
強調したかったからだけなんです。コピーペーストする程度だけなら
すぐ済みますが、物によっては何ヶ月も掛かってしまいます。
事実、キー入力と表示の同期の部分に1ヶ月も掛かってしまいましたし。
入力用ウインドウを別に作ってそこからもらうことも検討したけれど、
反って時間を食う懸念があったため残念しただけです。
結果として、選択が正しかったと確証を得たのは移植作業が終わってからでした。
scanfにわざと異常データを入れるツールとして使える提案は新鮮でした。
私には思い付かなかったことです。ありがとうございます。どこかで、
テストで挿入して見たいと思います。
scanfについてはこの辺で話を終了したいと思います。
みなさん、色々とありがとうごさいました。楽しかったです。
Re: scanfについて
しつこくてすみません。
つまり開発方法をゼロから覚える(あるいは考える)時間も含めての期間です。
コピペ程度で動くのなら苦労はないのですけどね。
#MS-DOS→Windowsではありません。そのように勘違いさせてしまったのならすみません。
練習プログラムを組める程度の力?十分じゃないですか。
はじめてアプリケーションプログラムのコードを書くときにどんな力を使うと言うのですか。
(追記1)
scanfにわざと異常データを流すなんて書いてませんよ。
(追記2)
もしかしてgetchを差し替えると書いたことに対してコピペ程度とおっしゃっているのでしょうかね。
わたしが3週間でマスターアップしたのも、3日でプロトタイプを作ったのも、はじめてそのプラットフォームに触れたときのもので、開発環境が一般には入手できなかったりもします。needsueda さんが書きました: 言訳にはなりますが、当時はWindowsの開発を初めて3ヶ月です。
練習プログラムを組める程度の力しかありませんでした。
できるだけ、修正個所が少ないようにすることがポイントになる。
15万行と例を出したのは、下手をすると最低一年は掛かる危険性を
強調したかったからだけなんです。コピーペーストする程度だけなら
すぐ済みますが、物によっては何ヶ月も掛かってしまいます。
つまり開発方法をゼロから覚える(あるいは考える)時間も含めての期間です。
コピペ程度で動くのなら苦労はないのですけどね。
#MS-DOS→Windowsではありません。そのように勘違いさせてしまったのならすみません。
練習プログラムを組める程度の力?十分じゃないですか。
はじめてアプリケーションプログラムのコードを書くときにどんな力を使うと言うのですか。
(追記1)
scanfにわざと異常データを流すなんて書いてませんよ。
(追記2)
もしかしてgetchを差し替えると書いたことに対してコピペ程度とおっしゃっているのでしょうかね。