この人頭いいなと思ったエピソード

sprintf関数について教えて下さい。

#include <stdio.h>
int main(void)
{
int i;
char ns[20][6];
for (i = 0; i < 20; i++)
sprintf(ns[i], "No.%02d", i + 1);
for (i = 0; i < 2; i++)
printf("%s\n", ns[i]);
return 0;
}
上のプログラムでは結果が
No.01
No.02
(中略)
No.19
No.20

ですが、sprintf関数内の書式指定を"No.%02d\n"に変更した場合、
結果が
No.01
No.02
(中略)
No.19
No.20

No.02
No.03
(中略)
No.19
No.20

No.03
No.04
(中略)
No.19
No.20

(中略)

No.19
No.20

No.20

になります。
どうしてこのようなことが起きてしまうのか自分では説明できません。
sprintf関数の説明も読んで納得はしたのですが
上のようになることがわかりません。

長々と書いてしまいましたが
よろしくお願い致します。

A 回答 (3件)

まず


char ns[20][6];
と宣言したときnsの占めるメモリ領域は
ns[0][0],ns[0][1],ns[0][2],ns[0][3],ns[0][4],ns[0][5],ns[1][0],ns[1][1],...,ns[19][5]
のように並びます。
sprintfで"No.%02d"を使って設定すると設定される文字列は
ns[0][0]='N',ns[0][1]='o',ns[0][2]='.',ns[0][3]='0',ns[0][4]='1',ns[0][5]='\0',...
のようになり、特に問題なく設定されていきますが
sprintfで"No.%02d\n"を使うと
ns[0][0]='N',ns[0][1]='o',ns[0][2]='.',ns[0][3]='0',ns[0][4]='1',ns[0][5]='\n',ns[1][0]='\0',...
のように範囲をあふれて設定されます。
このns[1][0]='\0'の部分は次のループで'N'で上書きされるため、ns[19]を除くns[i]は文字列の終わりがない状態になり、後のループのprintfは一回ごとにns[i]の初めからns[19]の直後のメモリ領域に書き込まれた'\0'までを一つの文字列とみなして表示します。
その結果が質問で書かれた表示です。
    • good
    • 0
この回答へのお礼

納得しました。
有難う御座いました。

お礼日時:2010/01/14 15:56

これは単にsprintfの問題では無いですね。



最初に二次元配列(ns)を宣言してる部分がありますが、

ns[20][6]だと5文字しか入力できません。
(文字列終端文字[\0]を含むので)

"No.%02d"の場合は[\0]を含んで6文字ですが、

"No.%02d\n"の場合は[\0]を含んで7文字になります。

なので、メモリオーバーフローが起きているので、おかしな結果がでているようです。

二次元配列を宣言するときに、

char ns[20][7]

とすればいけますよ。

/////////////////////////////////////////////////////////

#include <stdio.h>
int main(void)
{
int i;
char ns[20][7];
for (i = 0; i < 20; i++)
sprintf(ns[i], "No.%02d", i + 1);
for (i = 0; i < 20; i++)
printf("%s\n", ns[i]);
return 0;
}

/////////////////////////////////////////////////////////
    • good
    • 0
この回答へのお礼

有難う御座いました。

お礼日時:2010/01/14 15:55

>for (i = 0; i < 2; i++)


20では?

sprintfで格納される文字数が6を超えます。

>"No.%02d"
は 5文字 + 終端文字('\0') で6文字必要です。

>"No.%02d\n"
は先ほどより '\n'分文字が必要なので7文字必要です。

>char ns[20][6];
より
ns[i]は6文字分しか入らないので、はみ出した1文字は
ns[i + 1][0]に格納されます。
(2次元配列の場合連続した領域が割り当てられる。
ns[0][0] ns[0][1] ・・・・・ n[0][5] n[1][0] n[1][1] ・・・ n[19][5])
※ここで、ns[19]にsprintfで格納時、はみ出た1文字は
 nsの領域外に設定されてしまいます→不正アクセスで落ちる可能性がある。

printfで書式文字 %s を指定した場合、\0 までを表示するので、\0はループの中で次々と上書きされているわけなので、
出現するのは ns[19][5]の次になるすなわち全ての文字が一度のprintfで表示されるのです。

>char ns[20][6];

char ns[20][7];

>for (i = 0; i < 2; i++)
>printf("%s\n", ns[i]);

for (i = 0; i < 20; i++)
printf("%s", ns[i]);
とすると同じ結果になるでしょう。

この回答への補足

すいません、訂正します。
プログラム中の2番目のfor文ですが
(誤)for (i = 0; i < 2; i++)
(正)for (i = 0; i < 20; i++)

です。

補足日時:2010/01/14 15:25
    • good
    • 0
この回答へのお礼

有難うございました。
最初の2次元配列の定義部分を見落としていました。

お礼日時:2010/01/14 15:53

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!


おすすめ情報