🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

プログラミング言語

プログラミング言語はパソコン上で実行することができるソースコードを記述する為に扱う言語の総称です。

Q&A

解決済

4回答

1194閲覧

scanfの入力に関する質問

suugaku_nyumon

総合スコア37

C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

プログラミング言語

プログラミング言語はパソコン上で実行することができるソースコードを記述する為に扱う言語の総称です。

0グッド

0クリップ

投稿2021/03/24 16:08

質問内容

scanfの入力について、指定した文字数以上の文字を入力すると格納できない、つまりはみ出した文字が**「後ろの領域」**に上書きされるということを聞いたことがあり、その後ろの領域というものを知りたいためにコードを組んで実験していたのですが、後ろの領域というのは1つ前に定義した文字ということで間違いはないのでしょうか?

例えば、a、b、cの順番に文字列を定義した場合に、scanfを用いてbに入力した文字列の文字数が、bで定義した文字数を超えている場合は、その分の文字列がaの方に上書きで入力されるという認識で良いのでしょうか。

(C言語は始めたばかりなので、文章が分かりにくければ申し訳ございません。)

コード

example1

1#include<stdio.h> 2 3int main(void) 4{ 5 char a[10] = "aaaaaaaaa"; 6 char b[10] = "bbbbbbbbb"; 7 char c[10] = "ccccccccc"; 8 9 scanf(" %[^,],%s",&b,&c); 10 printf("a=%s\n",a); 11 printf("b=%s\n",b); 12 printf("c=%s\n",c); 13 14}

example2

1#include<stdio.h> 2 3int main(void) 4{ 5 char b[10] = "bbbbbbbbb"; 6 char a[10] = "aaaaaaaaa"; 7 char c[10] = "ccccccccc"; 8 9 scanf(" %[^,],%s",&b,&c); 10 printf("a=%s\n",a); 11 printf("b=%s\n",b); 12 printf("c=%s\n",c); 13 14}

プログラムの実行と結果

それぞれのコードをコンパイルして作成した実行ファイルを実行して、scanfの入力については以下のように記入します。

bb,cccccccccc

するとexample1のコードをコンパイルした方は

a=aaaaaaaaa b=c c=ccccccccccc

と出力されて、example2のコードをコンパイルした方は

bb,ccccccccccc a=c b=bb c=ccccccccccc

と出力されました。

私の考察

example1の方はa,b,cの順番に定義して、まずbにbbが上書き保存されて、その後cに"ccccccccccc\n"を代入しようとするが、"c\n"が溢れてbに再度上書き保存されて、bは最終的に"c\n"と入力されたことになると考えました。

また、example2の方はb,a,cの順番で定義しており、bにbbが上書き保存されて、cに"ccccccccccc\n"を代入しようとするが、"c\n"が溢れてcの1つ前に定義したaに溢れた"c\n"が代入されて、a=cとなったと考えました。

その考察から、質問内容であるscanfで定義された文字数以上の文字を入力すると後ろの領域、つまり1つ前に定義した変数に代入されるのではないかという予想をしています。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答4

0

まず大前提として確保されていない領域への読み書きは未定義動作なのでコンパイラは鼻から悪魔を召喚しようが何をしようが許されるのでそもそもこの考察自体無意味となります。もう一度大事なことなので強調しておくと結論は考察が無意味です。

・・・で、例えばコンパイラの最適化を切り、コンパイラがこの未定義動作を最適化に活用しなかった場合、どうなるか考えてみます。つまりは特定の処理系についてさらに極めて限定的な条件をつけてどうなるか観測して見るわけです。

別にscanfである必要はないのでもっと簡略化した例を考えます。

c

1void f() { 2char s[10]; 3 4for(size_t i = 0; i < 12; ++i) s[i] = i % 10 + '0'; 5}

これは明らかに配列の範囲外を参照しています。

ここで今回考える処理系では関数の呼び出しと関数内の自動変数を次のように扱うと仮定します。

  1. 関数を呼び出すとき、その関数を呼び出し終えたあとに実行するべき命令が置かれたメモリー領域へのポインタをスタックにつむ。(以下return address=ret addr, 仮に4byte)
  2. その後で関数内での自動変数の領域をスタックに積む

すると先の例で関数fが呼び出された直後、こういうメモリー配置になる可能性が考えられます。

<--1byte-> | s[0] | | s[1] | | s[2] | | s[3] | | s[4] | | s[5] | | s[6] | | s[7] | | s[8] | | s[9] | |ret addr| |ret addr| |ret addr| |ret addr|

(上に行くほどメモリー上の番地が若い、スタックの上の方というイメージで、もちろんそんな概念C/C++にはない)

このとき先の例のfor文で範囲外アクセスが行われた場合、s[9]を超えてret addrの一部分にまで書き換えが及びそうです。

すると関数の呼び出し元に戻ることすらできなくなるという可能性が考えられます。


さてここまでの話を踏まえ次を考えます。

その考察から、質問内容であるscanfで定義された文字数以上の文字を入力すると後ろの領域、つまり1つ前に定義した変数に代入されるのではないかという予想をしています。

大事なことなので3度めですが言及すると考察が無意味です。しかし今回の処理系はたまたまそういう振る舞いをしていると観測することができました、ということはできます。

例えば今回の質問者さんの例でscanfmallocした領域を渡したらどうなるのかなど観測してみるのも面白いかもしれません。


処理系によっては未定義動作を積極的に活用して最適化に利用したり、タイムトラベルしたり、あるいは範囲外アクセスを検出するために確保したメモリー領域間になにか特殊な詰め物をして上書きを検出しようとするかもしれません。


しかし確実に言えるのはそもそも未定義動作を踏むな、踏んだ状態についてなんらかの考察をするなということです。

投稿2021/03/24 17:14

編集2021/03/24 17:26
yumetodo

総合スコア5852

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

suugaku_nyumon

2021/03/25 16:33

yumetodoさん。ご回答ありがとうございます。
guest

0

解決済みですが、一言。
バッファオーバーランすればはみ出した文字が「後ろの領域」に上書きされる・・・その通りですが、こういうことを考えるなら変数(配列も変数)がメモリに割り付けられることを明確に意識したほうが良いです。どの変数(含配列)を、どういう順序で、メモリに割り付けるかは、コンパイラ任せ・コンパイラの裁量次第なので、プログラムコードだけ見て「考察」しても無駄だからです。

では何を見るか。変数が割り当てられたメモリアドレスを見れば良いのです。変数(配列)のアドレスを知れば考察するまでもないことなんです。変数のアドレスはデバッガを使ってもわかりますが、printf() の変換指定子「%p」を活用しましょう。いわゆるprintfデバッグです。
例えば、次のように、各配列のアドレスと値(文字列)を一緒に表示してみるとか。

C

1 printf("a: %p [%s]\n", a, a); 2 printf("b: %p [%s]\n", b, b); 3 printf("c: %p [%s]\n", c, c);

example1 を手元のコンパイラ(gcc 9.3.0)で実行したらこうなりました。

$ ./a.out bb,cccccccccc a: 0x7ffd05a6b92a [aaaaaaaaa] b: 0x7ffd05a6b934 [bb] c: 0x7ffd05a6b93e [cccccccccc]

表示されたアドレスから、a の後が b、b の後が c という順序だと分かりますが、この順序は質問者のコンパイラが割り当てた順序と違うのではありませんか。

簡単に確認できるアドレスを確認せずに考察しても詮無いこと。そして、このようにコンパイラの違いを知ることもC言語を知ることだと思います。

投稿2021/03/26 03:33

rubato6809

総合スコア1382

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

suugaku_nyumon

2021/03/27 00:49

まだC言語を習いたてで%pの存在を知りませんでした。 簡単にアドレス自体は確認できたのですね。 ご回答ありがとうございます。
guest

0

ベストアンサー

yumetodoさん、y_waiwaiさんの意見も一理ありますが、私の意見はかなり違います。

suugaku_nyumonさん、あなたのやっていることはとても大事なことです。

世の中には、普通に使っている人たちもいれば、その下のインフラストラクチャーを作る人たちもいます。
もっとわかりやすく言うと、自動車を運転する人と、乗り物を設計する人がいるということです。

自動車を運転する人にとって、「なぜガソリンを入れるのだろう」を考えるよりも大事なことがいっぱいあります。
しかし、乗り物を設計する人は、「ガソリンじゃなくて電池だったらどうだろう」とか、「昔の蒸気機関はどうだったのだろう」とか「原子力自動車はどうだろう」とか考えながら新しいものを作るのです。

現在のCPUでC/C++などの処理系では、suugaku_nyumonさんが考えているように動いているものが多いです。しかし、過去にはそうではないものもありましたし、言語の種類が違えばいまでも違うものもあります。
今考えていらしゃることの原因を調べていくと、自動変数がなかった時代のことも調べてみなくてはなりませんし、スタックポインタと呼ばれるハードウェアがなぜ作られるようになったかも調べなければなりません。
そういう人たちの中から新しいプログラミング言語や新しいCPUを設計する人が出てくるのです。

他人の言うことは気にせずに、自分が面白いと思うことをどんどん調べてください。

なお私の答えは、

そういう風に作られていればそう動く、です。
人の創ったものは、だいたいは創造者の考えているように動くのです。
設計をするということは、世界を創ることなので、楽しいですよ。

追加です。

自動変数の場合は、実験結果のような順になりますが、以下も実験してみてください。
外部変数、静的変数、mallocで取った変数。
mallocで取った変数の場合はfreeするときにエラーが起きることが多いのですが、その原因を考えるのも楽しいですよ。

投稿2021/03/24 23:59

編集2021/03/25 03:01
ppaul

総合スコア24670

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

yumetodo

2021/03/25 01:45

私としてもC/C++という抽象化レイヤーからはそれは観測する意味はないよという話で、こういうことを考えることは大事だと思います。
ppaul

2021/03/25 02:58

私も、言語仕様的には実装に関する問題は任意だというのはそう思います。 今回の質問が、scanfでたまたま変なことが起きたのでどうしましょう、というような質問であれば、私もお二方と同じような回答をしたでしょう。 「その後ろの領域というものを知りたいためにコードを組んで実験していたのですが」という文を読んで、suugaku_nyumonさんは、こういう実験をしたい人なのだなあと思い、そういう人は、面白く化けるかもしれないので、さらに実験を勧めてみました。
yumetodo

2021/03/25 03:08

malloc実験は絶対楽しいのでやったほうがいいですね。
suugaku_nyumon

2021/03/25 16:20

ppaulさん。ご回答ありがとうございます。 確かに自動変数でしかまだ試してはいないので、他の変数でも試してみたいと思います。
guest

0

この動作のことについて思い悩まくても、コンパイルオプションでアセンブリコードを出す事ができますんで、それを読むことで、そこでなにをどうしてるというのがわかります。

しかし、別回答にもありますが、この動作というのは保証されたものではありません。
このハードウエアのこの実装、このコンパイラ、このコンパイルオプションでは(たまたま)こうなってると言うだけで、実装やコンパイラ、オプションが変わるとまた動作が変わるかも知れません

ということで、これについて考察したところで、無駄でしかない、ということになろうかと思われます。

投稿2021/03/24 22:13

y_waiwai

総合スコア88038

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

suugaku_nyumon

2021/03/25 16:31

y_waiwaiさん。ご回答ありがとうございます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.36%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問