ページ 11

sscanf_sの使い方について

Posted: 2014年12月15日(月) 21:49
by はーむ
初めまして、Microsoft Visual Studio Express 2013 for Windows Desktopにて、C++でプログラミングを作っていて困ったことがあったので質問させていただきました。知識としては、学校で構造体の辺りまでのC言語の基礎を学んだところです。

今回作ろうとしていたプログラムは、ファイルに書き込まれている問題と答えを読み込んでクイズをするプログラムです。
そのプログラムを作る過程で
10
1+1=?,2
1+2=?,3
1+3=?,4
2+1=?,3
2+2=?,4
1+5=?,6
4+9=?,13
3+4=?,7
10+1=?,11
10-4=?,6
と書き込まれたファイルから、最初の一行で問題の数を取得し、カンマでquesとanswerの構造体のメンバに代入していこうというプログラムを以下のように書きました。

コード:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <iostream>
#include <fstream>
#include <string>
using namespace std;

#define MAX 140

/* 構造体の宣言 */
typedef struct Quiz {
	char ques[MAX], answer[MAX];
public:
} Quiz;

/* 関数のプロトタイプ宣言 */
void load(Quiz *pQ, FILE *fp);


/* ファイルを読み込んで構造体へ入れる */
void load(Quiz *pQ, char *fp){
	int i,line;
	ifstream ifs(fp);
	char str[256];

	/*エラー処理*/
	if (!ifs){
		cout << "Error: cannot open file" << endl;
		exit(1);
	}

	ifs.getline(str, 260);
	line = atoi(str); /* 行数を取得 */
	pQ = (Quiz *)malloc(sizeof(Quiz)*(line + 1));
	for (i = 0; i < line; i++){
		ifs.getline(str, 260);
		sscanf_s(str, "%s,%s", (pQ + i)->ques, (pQ + i)->answer); /* 構造体へ代入 */
	}	
	
}

int main(void){

	Quiz quiz[MAX];
	char file[256];
	
	cin >> file;
	
	load(quiz, file);

	return 0;
}
コンパイルしたところ、ファイル名を入力した直後に動作が止まってしまいました。
ブレークポイントを使ってデバックしたところ、ques側に1+1=?,2と代入されていて、answer側には何も代入されていないという状況でした。

おそらくsscanf_sでの処理が上手くいっていなかったのだと推測しましたが、調べてみてもこの関数の使い方がよくわからなかったので質問しました。
sscanfはセキュリティが甘いとのエラーが出てきてつかえなかったのですが、sscanf_sでも同じ引数の指定の仕方ではいけなかったのでしょうか…

または、もっと効率の良い方法がある場合は教えていただけるとありがたいです。

Re: sscanf_sの使い方について

Posted: 2014年12月15日(月) 22:01
by nullptr
質問するのは結構ですが、まず自分でドキュメントを読む癖を付けましょう。嫌味ではなく、問題が起きた時にまず行うべき対応を身につけることが大事だからです。

_sのついた関数はMicrosoftが標準ライブラリをセキュリティ面で強化した関数で、元の関数とは使い方に必ず差があります。
公式ドキュメントは以下です。
http://msdn.microsoft.com/ja-jp/library/t6z7bya3.aspx
英語版からの引用になりますが、こう書かれています。
Unlike the less secure version sscanf, a buffer size parameter is required when you use the type field characters c, C, s, S, or string control sets that are enclosed in [].
日本語版では、
より安全なバージョンである sscanf とは異なり、型フィールド文字 c、C、s、S、または [] で囲まれた文字列のコントロール セットを使用する場合にバッファー サイズのパラメーターが必要です。
なかなか頓珍漢な変換ですが、要は、%s、%S、%c、%Cを使う場合は、引数に文字のサイズを指定する必要がある、ということです。
例えば、

コード:

char aa[10];
sscanf_s(a,"%s",aa,sizeof(aa));
のように、ポインタの直後にバッファーサイズを指定します。

Re: sscanf_sの使い方について

Posted: 2014年12月15日(月) 22:14
by みけCAT
はーむ さんが書きました:ブレークポイントを使ってデバックしたところ、ques側に1+1=?,2と代入されていて、answer側には何も代入されていないという状況でした。
これはscanfでも同じはずです。
書式文字列を"%s,%s"ではなく、"%[^,],%s"とするといいかもしれません。

Re: sscanf_sの使い方について

Posted: 2014年12月15日(月) 22:17
by みけCAT
はーむ さんが書きました:

コード:

	char str[256];

// 中略

	ifs.getline(str, 260);
	line = atoi(str); /* 行数を取得 */
	pQ = (Quiz *)malloc(sizeof(Quiz)*(line + 1));
	for (i = 0; i < line; i++){
		ifs.getline(str, 260);
		sscanf_s(str, "%s,%s", (pQ + i)->ques, (pQ + i)->answer); /* 構造体へ代入 */
	}
ここのgetlineの使い方は危険です。
strは実際には256バイトしか確保されていないにもかかわらず、
ifs.getlineに260バイトまで入れていいよ、と伝えているので、バッファオーバーランのリスクがあります。

コード:

ifs.getline(str, sizeof(str));
とするのがいいでしょう。

Re: sscanf_sの使い方について

Posted: 2014年12月16日(火) 00:22
by はーむ
皆さんの仰るとおりにやったら無事成功しました。ありがとうございます。

>>新ゝ月様
確かにmsdnライブラリは読み飛ばしがちになっていたことが多かったので反省します…もう少し根気強く読めばしっかり知りたいことは書いてあるのですね、大変良い勉強になりました。

>>みけCAT様
"%[^,],%s"とした方が、c++との相互性がよいのですね、そのあたりも改めて調べてみようと思います。
今回、私は要素数のケアレスミスをしてしまいましたが、sizeofを使えばその間違えも少なくなることも教えていただきありがとうございました。