• 締切済み

for文内での変数定義

以前にC++の講義を受けた際に for(int i = 0; i < hoge ; i++){    int j;    ・    ・    ・ } のようなコードを書くと、jがhoge分だけ"生成されて しまう"のでよろしくありません。と教えられました。 しかし、最近別の方からこの部分に関しては、jをfor文の 中に定義しようが、最適化?によりfor文の外に出された バイナリが生成されると聞きました。そのため、jが 必要となる直前でjを定義しても(for文の中に定義しても) 問題ない。と教えられました。 どちらが正しいのでしょうか? 近年にC++の仕様変更があったとしたら、それに伴い 変更されたのでしょうか? また、どのようなコードが望ましいなどありましたら 教えてください。

みんなの回答

  • Tacosan
  • ベストアンサー率23% (3656/15482)
回答No.6

「j が hoge 分だけ生成される」というのはちょっと語弊はあるけど, 「j が hoge 回生成される」という意味ならその通り. ただし, 「同時に hoge 個存在する」ということはあり得ないし, それが「良くない」かどうかは別の話. だいたい, これが「良くない」なら「(main 以外の) 関数内部のローカル変数」や「関数の引数」も「良くない」ことになってしまう. で, プログラミングのスタイルとしては「変数のライフタイムはなるべく短くする」というのを原則にしていいと思います. だから, 「for の 1回の繰り返しで生きていればいい (各繰り返しごとに違う変数と思う)」なら for の中で定義することになるし, 「1回の繰り返しを超えて値を保持しなければならない」なら for の前で定義することになります (というかそうするしかない). いずれにせよ「プログラムの通りに動作しているように見えればコンパイラがどのように変えても問題ない」ので, この点について仕様の変更はないはずです. おまけですが, 変数の定義をループ本体に入れた場合, この仕様からコンパイラがどう最適化しても「複数回生成される, ように見える」必要はあります. int だと大して問題ないけど, コンストラクタ (とデストラクタ) をもつクラスでは毎回コンストラクタ/デストラクタを呼び指すことになります.

upanepa
質問者

お礼

ご回答ありがとうございます。

回答No.5

まずは、そんなアホな事を教えている先生を首に・・・ は、おいておいて プログラミングの基本で説明しておきます A.int j;   for (i=0; i<hoge; i++) {    .    .   } というのと B.for (i=0; i<hoge; i++) {    int j;    .    .   } というのはプロからみた場合圧倒的に意味が異なります Aの場合は「このfor以降でjを使用する部分があるので、注意せよ」というのを暗に示しているし(だからjなんて変数名つけるのはバカ) Bの場合は「このループの中で一時的に使用しているワーク変数なので、あんまり気にしなくていい」というのが裏にあります プログラムを「動けばいい」なんて教えている人がいますが、私は間違っていると思います 「美しく、読みやすい」のが最高と思います

upanepa
質問者

お礼

ご回答ありがとうございます。 >「美しく、読みやすい」のが最高と思います 私もそう思います。

  • rentahero
  • ベストアンサー率53% (182/342)
回答No.4

> for(int i = 0; i < hoge ; i++){ >    int j; >    ・ >    ・ >    ・ > } > のようなコードを書くと、jがhoge分だけ"生成されて > しまう"のでよろしくありません。と教えられました。 この例の場合、jは"複数個"生成されるわけではありません。 ただし、この場合において(明示的に)最適化をまったく行わない設定でコンパイルすると、 ループの終了時に変数jが破棄され、(結果として)同じメモリ位置に再度変数jが設定されることになるでしょう。 当然、このコードでは変数jは初期化されていませんので、(再度設定された)変数jの値は直前にループ終了時に入っていた値が 入っているはずです。 また、この(最適化しない)場合では、ループが終了しない(=継続する)場合、破棄した変数を直後に(同じ位置に)設定するという (それ自体にあまり意味のない)処理が起こりうることになります。 つまり、"複数回"生成される、という説明に(一応は)嘘はありません。 ただし、これは「最適化をまったく行わない」ケースにおいてのみです。 実際には最小限の最適化を行うことがふつうです。 ですから > 最近別の方からこの部分に関しては、jをfor文の中に > 定義しようが、最適化?によりfor文の外に出された > バイナリが生成されると聞きました。 お聞きになった内容というのは、つまりコンパイラによる最適化によって、先のコードは {   int j; //←変数を設定する   for(int i = 0; i < hoge ; i++){     ・     ・     ・   } //←ここでは変数を破棄しない } //←ここで変数をスタックから破棄する こう書いても同じ機械語コードが生成されることになるはずだという意味です。 当然 for(int i = 0; i < hoge ; i++){    int j=1; //←変数を設定すると同時に初期化    ・    ・    ・ } //←変数を破棄する こうなっている(変数を初期化している)場合は {   int j; //←変数を設定する   for(int i = 0; i < hoge ; i++){     j=1; //←初期化     ・     ・     ・   } //←ここでは変数を破棄しない } //←ここで変数を破棄する こうなるであろうことは容易に想像できます。 > どちらが正しいのでしょうか? 上記の理由により、「どちらも正しいが、前提条件が違うため比較することに意味がない」といえるでしょう。 もっと突っ込んでいうと、最適化をまったく行わないコンパイラは通常存在しないため、 「複数回生成される」という説明のほうが「非現実的」であるといえるでしょう。 > また、どのようなコードが望ましいなどありましたら教えてください。 この点については、あなたが理解しやすいコードであるべきです。 現在のコンパイラの最適化機能は大変優れています。 コーディングに多少工夫を凝らしたところで、同じ機械語コードに翻訳されたり、 場合によってはかえって効率の悪いコードが生成されることすらあります。

upanepa
質問者

お礼

ご回答ありがとうございます。

回答No.3

コンパイラには通称「as if」ルールというのがあります。 これは 「結果が変わらなければ、コンパイラは何をしてもよい」 というものです。 つまり、二人目の方が仰られているように、int j;がforの 外に置かれようが、中に置かれようが、結果が変わらなければ コンパイラはどっちに置いても良い。ということになります。 スタックにintを置くコストが多少はあるので、今回のような 場合は、forの外に出して「最適化」をコンパイラが"してもいい" ということになります。 さて、どのようなコードが望ましいかについてですが、 「一般的にfor文中に入れたほうが良い」ということになっています。 これは、Code Completeの上巻で述べられていることなのですが、 変数のBindTime(変数を変化させられる時間)は「実際に利用する時間」 に近ければ近いほど良い、つまりfor文の中でしかint j;を利用しない のであればそれより大きいスコープ(for文の外)には置くべきではない ということになっています。 (そして私もそう思います)

upanepa
質問者

お礼

ご回答ありがとうございます。 基本型については、for文の中に入れても 問題なさそうですが、クラス変数については、 場合によってはfor文の外に宣言したほうがいいのかな・・ と思いました。(後番の回答者さんが記述されているように、 コンストラクタとデストラクタがfor文の1回に実行中に 1回づつコールされてしまうため)

  • eroermine
  • ベストアンサー率18% (83/444)
回答No.2

>のようなコードを書くと、jがhoge分だけ"生成されて そんなアホなコンパイラはこの世にありません。 自動変数の自動を動的生成と勘違いしたのでしょう。 自動変数の自動は関数が呼ばれたときに自動的にスタック上に作られる、 ということで関数が呼ばれなければ作られません。 Cも同様。 簡単な関数のアセンブラ出力を眺めれば一目瞭然かと。 ただし、 この世にはスタックの無いコンビューター IBM360系等もありまして、 その場合は動的生成ですね。たぶん。でもうまくやってるでしょう。

upanepa
質問者

お礼

ご回答ありがとうございます。

  • notnot
  • ベストアンサー率47% (4900/10358)
回答No.1

実装の話なので、正解は「色々ある」ですが、一般論で「普通はこう」というのを書きます。 ローカル変数はスタック上に生成しますが、それは関数の入り口でまとめて行います。jへのアクセスはforのブロック内でしかできませんが、変数自体は関数の開始から終了まで存在します。 もちろん、int j=1; などと初期化式を書いた場合はそれは繰り返し回数だけ実施されます。

upanepa
質問者

お礼

ご回答ありがとうございます。

関連するQ&A