sscanf の一般的な落とし穴と安全な使用方法
sscanf は C 言語において非常に便利な関数であり、文字列からフォーマットされたデータを簡単に抽出することができます。しかし、その便利さには非常に見落とされがちな落とし穴が伴います。もしその動作メカニズムを理解していなければ、一見正常に見えるが実際には深刻な脆弱性を持つコードを書いてしまう可能性があります。特に組み込み<rt>Embedded</rt>システムやマイコン<rt>Microcontroller</rt>の開発において、このような問題はプログラムのフリーズや異常な動作を引き起こすことがよくあります。
本ドキュメントは、sscanf の最も中核的な落とし穴を要約し、より安全で堅牢な代替案を提供することを目的としています。
核心的な誤解:sscanf の戻り値が 0 より大きければ、フォーマット文字列全体が一致したと思っている。
事実:sscanf は最初の不一致な文字に遭遇すると直ちに停止し、停止前に正常に代入できた変数の数を返します。
コードが以下のようで、入力が "LED1:OFF" であると仮定します。
sscanf が "LED%hhu:ON" に一致させようとするとき、その内部処理は以下のようになります。
L, E, D -> 一致成功。%hhu -> 数字 1 に一致し、led_temp に正常に代入されます。(成功代入数:1): -> 一致成功。O -> 一致成功。N -> 'N' が期待されますが、入力は 'F' です。不一致!直ちに停止!最終結果:sscanf は停止前に 1 つの変数 (led_temp) に正常に代入したため、その戻り値は 1 になります。if (1 == 1) 条件が成立し、プログラムは誤って "ON" の処理ロジックに入ってしまいます。
sscanf は変数の範囲を超える数値を処理する際、エラーを通知しません。
核心的な誤解:sscanf は数値の範囲問題を自動的に処理してくれると思っている。
事実:sscanf は範囲チェックを行いません。もし提供された数字が変数型の格納範囲を超えている場合、「ラップアラウンド(回り込み)」が発生し、完全に間違った値になりますが、関数の戻り値は依然として 1 であり、変換が成功したと誤認させてしまいます。
led_temp が uint8_t (範囲 0-255) であり、入力が "LED999:ON" であると仮定します。
これは非常に危険な論理バグです。なぜなら、プログラムは誤ったデータを処理していますが、それ自身は全く気づいていないからです。
sscanf にはこれほど多くの問題があるため、どのようにして堅牢な解析コードを書けばよいのでしょうか?
「文字列フォーマットの検証」と「数値の抽出」という2つのステップを分けます。
長所:コードの論理が明確で、読みやすく、保守が容易です。
strtol 系の関数を使用するあらゆる深刻なプロジェクト、特に外部入力を扱う場合、strtol (string to long) および strtoul (string to unsigned long) が最良の選択肢です。これらは詳細なエラーチェックメカニズムを提供します。
長所:極めて安全です。オーバーフロー、不正なフォーマット、余分な文字をチェックできます。
| いつ使用する? | 推奨関数 | 理由 |
|---|---|---|
| 外部入力(シリアルポート、ネットワーク、ユーザー)を処理する場合 | strtol系 / 先に検証してから解析 | 外部入力を絶対に信用してはいけません! 最も厳格なチェックを行う必要があります。 |
| 内部のフォーマットが固定された単純な文字列を解析する場合 | sscanf | 入力フォーマットが100%信頼できることが保証されている場合、利便性のために使用しても問題ありません。 |
| 複数の固定フォーマットを持つコマンドを解析する場合 | 先に strstr/strcmp で検証し、その後に sscanf で解析する | コードの可読性が高く、論理が明確であり、安全性と利便性のバランスが取れています。 |
最後の忠告:sscanf の能力を過大評価せず、またユーザーが「サプライズ」データを入力する能力を過小評価しないでください。