C言語プログラミング:フォーマット済み文字列を解析するsscanf_s関数


sscanf_s 関数は、フォーマット指定子に従って、文字列 (ストリング) からデータを解析し、指定された変数に格納します。これは、ファイル入力において、ファイル内容を構造化データとして読み込む際に役立ちます。

利点

従来の sscanf 関数と比較して、以下の利点があります。

  • マルチスレッド対応: スレッドセーフ設計により、マルチスレッド環境での安全な使用が可能です。
  • 引数チェック: 引数の型とサイズを厳密にチェックするため、プログラムクラッシュのリスクを軽減します。
  • バッファオーバーフロー対策: sscanf_s は、バッファオーバーフロー脆弱性を防ぐように設計されており、セキュリティ上のリスクを低減します。

構文

int sscanf_s(
    const char *str,
    const char *format,
    ...
);

引数

  • ...: 解析結果を格納する変数のアドレス
  • format: フォーマット指定子を含む文字列 (ストリング) を格納するポインタ
  • str: 解析対象の文字列 (ストリング) を格納するポインタ

フォーマット指定子

フォーマット指定子は、解析対象のデータ型を指定します。主なフォーマット指定子は以下の通りです。

  • %[type]: 特定の文字列 (例: %[^\n]s は改行文字までの文字列)
  • %s: 文字列
  • %c: 文字
  • %f: 浮動小数点
  • %d: 整数

戻り値

成功した場合は、解析された項目数を返します。失敗した場合は、0 を返します。

ファイル入出力での活用例

以下の例では、data.txt ファイルから整数を 2 つ読み込み、変数 xy に格納します。

#include <stdio.h>
#include <stdlib.h>

int main() {
  FILE *fp;
  int x, y;

  fp = fopen("data.txt", "r");
  if (fp == NULL) {
    perror("fopen failed");
    exit(1);
  }

  if (fscanf_s(fp, "%d %d", &x, &y) != 2) {
    printf("読み込みエラー\n");
    fclose(fp);
    exit(1);
  }

  printf("x = %d, y = %d\n", x, y);

  fclose(fp);
  return 0;
}
  • ファイルポインタが適切に開かれていることを確認する必要があります。
  • 解析対象の文字列 (ストリング) の長さが十分であることを確認する必要があります。
  • フォーマット指定子と変数の型が一致していることを確認する必要があります。
  • sscanf_s 関数は、Visual Studio 2005 以降の C ランタイムライブラリでサポートされています。


構造体の定義

まず、データ格納用の構造体を定義します。

#include <stdio.h>

typedef struct Person {
  char name[32];
  int age;
  double height;
} Person;

ファイルからのデータ読み込み

次に、data.txt ファイルから構造体 person にデータを格納するコードです。

int main() {
  FILE *fp;
  Person person;

  fp = fopen("data.txt", "r");
  if (fp == NULL) {
    perror("fopen failed");
    exit(1);
  }

  if (fscanf_s(fp, "%s %d %lf", person.name, &person.age, &person.height) != 3) {
    printf("読み込みエラー\n");
    fclose(fp);
    exit(1);
  }

  printf("名前: %s\n年齢: %d歳\n身長: %.2fcm\n", person.name, person.age, person.height);

  fclose(fp);
  return 0;
}

ファイルへのデータ書き込み

続いて、構造体 person の内容を output.txt ファイルへ書き込むコードです。

#include <stdio.h>

typedef struct Person {
  char name[32];
  int age;
  double height;
} Person;

int main() {
  FILE *fp;
  Person person = {"Taro", 20, 170.0};

  fp = fopen("output.txt", "w");
  if (fp == NULL) {
    perror("fopen failed");
    exit(1);
  }

  if (fprintf(fp, "%s %d %lf\n", person.name, person.age, person.height) < 0) {
    printf("書き込みエラー\n");
    fclose(fp);
    exit(1);
  }

  printf("書き込み完了\n");

  fclose(fp);
  return 0;
}
  • fprintf 関数は、構造体の各メンバ変数の値をファイルへ書き込みます。
  • fscanf_s 関数は、ファイルから構造体の各メンバ変数に対応する値を読み込みます。
  • main 関数では、ファイルの開閉、データの読み書き、エラー処理などを記述しています。
  • 構造体 Person は、名前、年齢、身長を格納するメンバ変数を持つように定義されています。
  • エラー処理をより詳細に行うこともできます。
  • ファイルパスやフォーマット指定子は、ご自身の環境に合わせて変更してください。
  • 上記のコードはあくまで一例であり、必要に応じて修正することができます。


fscanf 関数

  • セキュリティ面で sscanf_s より劣る
  • バッファオーバーフローなどの脆弱性リスクがある
  • sscanf_s とほぼ同じ構文と機能
  • 標準的なフォーマット済み文字列解析関数

strtok 関数と atoi / atof 関数

  • バッファオーバーフローのリスクはない
  • sscanf_s よりコードが煩雑になる場合がある
  • 柔軟性が高いが、複雑なフォーマットには不向き
  • 文字列をトークンに分割し、個別に解析

正規表現ライブラリ

  • コードが冗長になりやすい
  • 学習曲線と処理時間が sscanf_s よりも大きい
  • 複雑なフォーマットにも柔軟に対応
  • PCREBoost.Regex など

自作の解析関数

  • テストとデバッグが複雑になる
  • 開発・保守コストが高い
  • 高度な制御と柔軟性を実現

sscanf_s の代替方法を選ぶ際の考慮事項

  • 処理速度: 処理速度が重要であれば、sscanf_s または strtokatoi / atof が一般的に高速です。
  • コードの簡潔性: コードの簡潔性を重視する場合は、sscanf_s または fscanf がおすすめです。
  • フォーマットの複雑さ: シンプルなフォーマットであれば fscanf または strtokatoi / atof で十分な場合があります。複雑なフォーマットの場合は、正規表現ライブラリが適しています。
  • セキュリティ: バッファオーバーフローなどの脆弱性対策が必要であれば、sscanf_s または正規表現ライブラリが推奨されます。

各代替方法の例

fscanf 関数

#include <stdio.h>

int main() {
  char str[] = "100 20.5 太郎";
  int age;
  double height;
  char name[32];

  if (fscanf(str, "%d %lf %s", &age, &height, name) != 3) {
    printf("解析エラー\n");
    return 1;
  }

  printf("年齢: %d歳\n身長: %.2fcm\n名前: %s\n", age, height, name);

  return 0;
}

strtok と atoi / atof 関数

#include <stdio.h>
#include <string.h>

int main() {
  char str[] = "100 20.5 太郎";
  char *token;
  int age;
  double height;
  char name[32];

  token = strtok(str, " ");
  if (token != NULL) {
    age = atoi(token);
  }

  token = strtok(NULL, " ");
  if (token != NULL) {
    height = atof(token);
  }

  token = strtok(NULL, " ");
  if (token != NULL) {
    strcpy(name, token);
  }

  printf("年齢: %d歳\n身長: %.2fcm\n名前: %s\n", age, height, name);

  return 0;
}
#include <stdio.h>
#include <pcre.h>

int main() {
  const char *str = "100 20.5 太郎";
  pcre *re;
  pcre_extra *extra;
  int ovec[30];
  int nmatch;

  re = pcre_compile("^([0-9]+) ([0-9.]+) ([\\w\\p{Han}]+)$", 0, &extra, &errptr, &errno, NULL);
  if (re == NULL) {
    printf("正規表現コンパイルエラー: %d\n", errptr);
    return 1;
  }

  nmatch = pcre_exec(re, extra, str, strlen(str),