Help us understand the problem. What is going on with this article?

scanf_sをscanfの代わりに使うときのやりかた

More than 1 year has passed since last update.

scanf_sの使い方まとめ

scanf_sの使い方まとめ.c
#include<stdio.h>

int main(void)
{
    int number;
    float fnumber;
    double dnumber;
    char c;             
    char string[10];   /*文字列の読み込みのときは複雑なので下に特に詳しく書きました*/


    scanf_s("%d",&number);
    scanf_s("%f",&fnumber);
    scanf_s("%lf",&dnumber);
    scanf_s("%c",&c)
    scanf_s("%9s",string,10);

    printf("%d",number)
}

このページの説明

このページではC言語でプログラミングを始めたばかりの方がVisualStudioを使用してプログラムを書き、そのプログラムをVisualStudioからコンパイルし実行しようとしたときに、どんなエラーが出やすいか?の項で紹介する、初心者が詰まりやすそうなエラー(私は詰まりました)が出て困ってしまった場合に、それを解決する方法を最優先に書いたため、最初に長々とエラーの原因が書いてあることをご了承下さい。ゆえにscanf_sの引数の違いについての見出しからが本編です。
なおVisualStudio2017を使用して確認を行いました

どんなエラーが出やすいか?

入門書にもよりますが、その内容通りにVisualStudioを使用しながらCのプログラミングを書いていると次の[1],[2]のような、scanfやscanf_s関連のエラーが発生するときがあると思います。

[1] C4996  'scanf': This function or variable may be unsafe. Consider using scanf_s instead. To disable deprecation, use _CRT_SECURE_NO_WARNINGS. See online help for details. がVisualStudioの下のほうに表示されて、コンパイルができない。

[2] scanf_sを用いて文字列を読み込むプログラムを書き、それを実行できたが、文字列を入力した後エンターを押すと、次の[2-1],[2-2]いずれかの状態になってしまう。

[2-1] stdio.hの中身がVisualStudioに表示され、例外がスルーされましたとstdio.hの中にポップが表示されている。(VisualStudioでデバッグの開始(ローカルWindowsデバッガー)で実行したときに発生することを実際に確認しました)

[2-2] ファイル名.exeは動作を停止しましたと書かれたウィンドウが表示される(「デバッグなしで開始」で実行したときに発生することを実際に確認しました)

どうして以上のようなエラーが発生するのか?

C言語を始めた皆さんはご存知かもしれませんが、Cはハードに近いところもプログラミングできるため、メモリの扱い方もある程度決めることができます。このような性質はCにさまざまな良いことをもたらしていますが、このようにコンピュータを操る自由度が高くなると、コンピューターに悪影響を与える操作を行ってしまう事があります。このような悪影響として領域破壊1というバグがありますが、scanf関数はこの領域破壊を行ってしまう可能性があります。このようにscanf関数は少し危険なのですが、どんなエラーがあるか?で述べたエラーもこのようなscanf関数の特性のため出ています。

[1]のエラーは何なのか?

scanfが少し危険な関数であると紹介したので、「[1]のエラーが出ている今の状態は危険なのでは?」と心配されている方もいらっしゃるかもしれませんが、実は[1]のエラーはコンパイルエラーであり、プログラムが何か悪さをしたから発生したわけではありません。scanf関数は以上のような悪影響を及ぼす可能性があるプログラムを作り出す可能性があるので、VisualStudioはWindowsにはscanf関数を「安全にした」2scanf_s関数がありますからそちらを使ってくださいとエラーを出してくれたため[1]のような「エラーが表示された」3のです。
ゆえに、そのエラーの通りにscanfのところをscanf_sにかえてあげるとプログラムをコンパイルすることができることもありますが、scanfとscanf_sは読み込みたい型によっては引数などの指定が違うため、実行時に[2]のようなエラーが出ることがあります。

[2]のエラーは何なのか?

こちらのエラーは危険です。これらのエラーはどちらも領域破壊についてのエラーなのですが、(1)の場合はデバッグで実行したのでデバッガーが処理を止めてくれましたが、(2)では実際に領域破壊を引き起こそうとしたのでプログラムがOSに止められてしまった、もしくは実際に起こしてしまったのでプログラムが動くことができなくなってしまったから表示された物です。
このようなエラーを初心者がscanf_sで出してしまうのは、文字列を読み込むときの引数がscanfとscanf_sの時では大きく変わっていることが理由であることが多いので、その時の対処法は文字列を読み取るときのscanf_sの使い方の項に詳しく書きましたのでそちらを参照してください。

scanf_sの引数の違いについて

数字(数字文字としてではない)を入力するとき

整数の読み込みの見本.c
#include<stdio.h>

int main(void)
{
    int number;
    float fnumber;
    double dnumber;

    scanf_s("%d", &number);
    scanf_s("%f", &fnumber);
    scanf_s("%lf",&dnumber);

    printf("%d\n", number);
    printf("%f\n", fnumber);
    printf("%f\n", dnumber);

    return 0;
}

以上のように整数を読み取るときはscanfとscanf_sの引数の指定の仕方は同じです。

文字列を入力するとき

文字列の読み込みの見本.c
int main(void)
{
    char string1[10];
    char string2[10];
    char string3[10];

    scanf_s("%s",string1,10);    /*書き方1:abcdefghijklmnと入力する*/
    scanf_s("%5s",string2);      /*書き方2:abcと入力する*/
    scanf_s("%5s",string3,10);    /*書き方3:abcdefghijklmnと入力する*/

    printf("%s",string1);        /*第三引数で指定した数以上を入力すると何も表示されない*/
    printf("%s",string2);        /*どんな入力をしてもエラー*/
    printf("%s",string3);        /*abcdeと5文字表示される*/
}

以上のようにscanf_sで文字列をchar型の配列に入力する際には、入力として受け取れる文字列の最大長を必ず第三引数で指定する必要がありますが、それぞれの書き方でも入力によって配列の中身に違いが生じるので以下にそれを示します。

書き方1について

入力1:第三引数に指定した数-1(例では9)までの入力があった場合
入力された内容を第2引数に指定された配列に入力し、その次の要素にナル文字を代入します。(ナル文字が入っても、その後に配列の空きがある場合はそこに・が入ります4)

入力2:第三引数に指定した数(例では10)以上の入力があった場合
第三引数に指定した数(例では10)以上があった場合入力第二引数に指定された配列には何も代入されません。(ゆえにprintfの%s指定で変な値が出力される、またはエラーが出るかというとそうではないため、たぶんナル文字が1文字目(s[0])に代入される?と思われます。また1文字目は何も表示されませんが、2文字目(s[1])以降をprintfで%c指定して出力すると入力1のように・が表示されます4)

書き方1.c
#include<stdio.h>

int main(void)
{
    char string1[10];
    char string2[10];

    scanf_s("%s",string1,10);    /*書き方1の入力1:abcdeと入力する*/
    scanf_s("%s",string2,10);    /*書き方1の入力2:abcdefghijkと入力する*/


    printf("%s",string1);        /*abcdeと表示 */
    printf("%s",string2);        /*何も表示されない*/

    for(i=0;i<10;i++)
        printf("%c",string1[i]);     /*abcde ・・・・*/

    for(i=0;i<10;i++)
        printf("%c",string2[i]);     /* ・・・・・・・・・*/

書き方2について(エラーしか出ない)

どんな入力をしてもエラーが出るのでダメ!!

書き方3(おすすめ)

入力1:第1引数で指定した数(例では5)未満の入力があった場合
入力された内容を第2引数で指定した配列に入力し、その最後に入力された文字の次の要素にナル文字を挿入します。
(ナル文字が入っても、その後に配列の空きがある場合はそこに・が入ります4)

入力2:第1引数で指定した数(例では5)以上~第三引数に指定した数-1(例では9)までの入力があった場合
第1引数で指定した数(例では5)だけ文字が配列に代入されます。(ナル文字以降の配列の空き要素に・が入ります4)

書き方2.c
#include<stdio.h>

int main(void)
{
    char string1[10];
    char string2[10];

    scanf_s("%5s",string1,10);    /*書き方1の入力1:abcdeと入力する*/
    scanf_s("%5s",string2,10);    /*書き方1の入力2:abcdefghijkと入力する*/


    printf("%s",string1);        /*abcdeと表示 */
    printf("%s",string2);        /*abcdeと表示 */

    for(i=0;i<10;i++)
        printf("%c",string1[i]);     /*abcde ・・・・*/

    for(i=0;i<10;i++)
        printf("%c",string2[i]);     /* abcde ・・・・*/

文字を代入するとき

文字の読み込みの見本.c
#include<stdio.h>

int main(void)
{
    char c;

    scanf_s("%c", &character);    /*リファレンスでは scanf_s("%c", &c, 1); となっているが左の例でも可能 */

    printf("%c", character);     /*(何文字入力しても)1文字だけ表示*/
}

文字を代入する方法のときは、私もどういう理屈が正しいのかわからないので、fgetc関数など他の方法を使ったほうが良いかもしれません。


  1. 詳しくはリターンアドレスやバッファオーバーフローという言葉を調べてみてください 

  2. 人によって「scanf_sを導入しても危険性は変わらない」または「scanf_sは素晴らしい」などさまざまな意見があります。(今回はscanf_sをscanfの代わりに使う事だけを書きたいのでここではその議論はしません) 

  3. (Visual Studioでscanfを使用したいときは「エラーで表示されている内容にある_CRT_SECURE_NO_マクロを使う」ことや「VisualStudioの設定を変える」ことでエラーがでないようにすることもできるらしいので、そちらも調べてみてはいかがでしょうか) 

  4. リファレンスを探してみてもこのようになるとは書かれていなかったので、もしかしたら処理系により動作が違うかもしれません(私の見落としだったらごめんなさい:bow_tone1:)ゆえに・が入っているところにはアクセスしないほうが良いようにしたほうが良いと思われます。 

softnakikaiya
機械系制御屋見習いが、ソフト分野を学んでいく途中に気づいたことや役立ちそうなことなどを気ままに残しております。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away