質問をすることでしか得られない、回答やアドバイスがある。

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

ただいまの
回答率

90.50%

  • C++

    3455questions

    C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

  • メモリリーク

    47questions

    メモリリークは、プログラムファイルがメモリの解放に失敗した時に起こります。

  • パフォーマンス

    22questions

    コード効率の向上や計算に関する質問には、このタグを使ってください。

  • スコープ

    12questions

    スコープとは、プログラム内で変数名など、参照可能な有効範囲のことを指します。

ループ内での変数宣言について

解決済

回答 7

投稿

  • 評価
  • クリップ 1
  • VIEW 677

tuyudaku

score 18

シンプルな質問です
C++でループ内で変数宣言を行った場合
1ループごとにその変数は解放されますか?
それとも完全にループが終了するまで新たに領域を確保し続けて、最終的に全てを解放しますか?
それとも領域は使い回しですか?

出来たらそれが明記してある文書などをご存知でしたら
それも教えていただけるとありがたいです

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

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

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

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 7

+2

まあ、実装の問題もありますが、
関数内で宣言される変数は、関数の入り口でまとめて領域確保されます
ループ内で宣言されるとかの事情は関知されません
ですんで、
変数の開放は、関数の終了時にされます
領域は使いまわしです

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/07/26 12:44

    >関数内で宣言される変数は、関数の入り口でまとめて領域確保されます
    びっくりです、なるほどどこで宣言しようが領域は最初に確保されるのですね。
    あくまでスコープを限定するだけであって
    ループを抜けたら解放されるとかでもないのですね...
    勉強になります!
    例えば
    while(1){int x = 0;}
    というのはx = 0というのを繰り返すだけであって
    領域がその度に確保されるというわけでは無いということで間違いないでしょうか

    キャンセル

  • 2018/07/26 13:18

    そういうことですね
    アセンブラソースを出し、それを読むとそこら辺の事情がよく分かると思います

    キャンセル

  • 2018/07/26 13:33

    なるほど
    やはりアセンブラを読むのは大事ですね...
    頑張ってみます!
    回答ありがとうございました!

    キャンセル

+2

C++でループ内で変数宣言を行った場合
1ループごとにその変数は解放されますか?

「解放」の意味にもよりますが、スマートポインタなどクラス型の変数をブロック内で宣言していた場合、デストラクタはスコープアウト時点で実行されます

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/07/26 12:48

    スマートポインタなどはまた違ってくるのですね...
    ちなみに、ループ内でのインスタンス生成はどうなってくるのでしょうか?

    キャンセル

+2

変数が確保されるのは関数の先頭(実装の話)

変数を使えるのはscopeの中(規格)

変数のctor(コンストラクタ)/dtor(デストラクタ)の呼び出しはscopeで(規格)

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/07/27 11:35

    >変数のctor(コンストラクタ)/dtor(デストラクタ)の呼び出しはscopeで(規格)
    スコープでコンストラクタとデストラクタが呼び出されるとは知りませんでした

    回答ありがとうございました!

    キャンセル

  • 2018/07/27 20:58

    あー厳密には最適化で順序が変わることを許容していると思ったので依存しないように(今の所気にする必要はないけど時間計測とかするときに注意)

    キャンセル

checkベストアンサー

+1

本題に入る前にCについて述べます。CにはVLAがあるため、それぞれのループで必ずしも同じ領域が確保されるとは限りません。

#include <stdio.h>

int main(void)
{
    for (int i = 0; i < 10; i++) {
        int x;
        int y[i + 1];
        int z[i + 2];
        printf("%d: %p, %p, %p\n", i, (void *)&x, (void *)y, (void *)z);
    }
}

Wandboxでの実行結果
※ GCCなどVLAが使用できるコンパイラで最適化無し(-O0)で実行してください。

領域は一つのループに対して、開始する度に確保され、終了する度に開放されますが、解放された領域は再び使用可能であるため、通常は次のループでも同じ領域を確保します。しかし、VLAのような取得する大きさが毎回異なる場合は、その大きさに合わせて確保される領域もずれていくことになります。

さて、上はCの場合です。C++にはVLAはありません。そして、私が知る限り、VLAのように実行の度に確保する領域の大きさが異なる仕組みは存在しません。
※ ここで変数によって確保される領域はスタック領域であって、mallocnew等で確保されるヒープ領域のことではないことに注意してください。

では、C++では確保も解放も何もされないのかというとそうではありません。なぜなら、コンストラクタとデストラクタの呼び出しは、毎回行われているからです。

#include <iostream>

class A
{
public:
    int v;
    A(int v) : v(v)
    {
        std::cout << "run construct: " << this->v << std::endl;
    }
    ~A() { std::cout << "run destruct: " << this->v << std::endl; }
};

int main()
{
    for (int i = 0; i < 10; ++i) {
        A a(i);
        std::cout << reinterpret_cast<void *>(&a) << std::endl;
    }
}

Wandboxでの実行結果

確保と解放がコンストラクタとデストラクタの呼び出しも意味するのであれば、ループ毎に毎回行われます。そして、スタック領域は再利用されるため、常に同じメモリ領域をしようしているように見えます。では、同じスタック領域を使用することが保証されていると言えるのでしょうか?

まず、GCCやClangではC++でもVLAが使用できるように独自拡張を行っています。VLAが使われている場合は、必ずしも同じとは限らない事は既に見たとおりです。でも、純粋なC++とは言えませんので、これを反例とするにはいささか弱いと思われます。

次に最適化によってそもそもスタックに入らずレジスタのみで完結している可能性です。ですが、それを検証する手段がありません。なぜなら、&などのアドレス演算子を使ってしまうとレジスタのみで完結するコードが生成されないからです。

int等の未初期化のまま使うと動作未定義になる変数を使って検証することには意味がありません。なぜなら、その時点で動作未定義であり、何が起きてもC++の仕様としては満たすことになるからです。

結局の所、たとえ同じ領域を使っていても、使っていなくても、動作未定義を含まない意味のあるコードを作ることは出来ないと考えています。つまり、保証されまいが、保証されていようが、それがコードに影響することはないという考えです。領域再利用を前提で、次のようなコードを考えるかも知れません。

#include <iostream>

int main()
{
    int *p = nullptr;
    for (int i = 0; i < 10; ++i) {
        if (p != nullptr) {
            std::cout << p << ": " << *p << std::endl;
            (*p)++;
        }
        int a;
        if (p == nullptr) {
            a = 0;
        } else {
            a = *p;
        }
        p = &a;
    }
}

このコードはうまく再利用しているように動作しますし、aは必ず代入してから使用しているように見えます。しかし、このコードは動作未定義です。なぜなら、aはループ毎に終了しておりpに保存されたアドレスを用いてアクセスすることは未定義な動作になってしまうからです。さらに、デストラクタで解放処理が行われている場合は、動作を予想するのは困難です。

#include <iostream>
#include <string>

int main()
{
    std::string *p = nullptr;
    for (int i = 0; i < 10; ++i) {
        if (p != nullptr) {
            std::cout << p << ": " << *p << std::endl;
            *p += "+";
        }
        std::string a;
        if (p == nullptr) {
            a = "+";
        } else {
            a = *p;
        }
        p = &a;
    }
}

上のコードを予測できる人は少ないと思います(少なくとも私の予測は外れていました)。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/07/27 11:22

    >C++にはVLAはありません。
    驚きました。コンパイラが上手いことやってくれていたのですね

    >&などのアドレス演算子を使ってしまうとレジスタのみで完結するコードが生成されないからです。
    そうだったのですね...

    >aはループ毎に終了しており、pに保存されたアドレスを用いてアクセスすることは未定義な動作になってしまうからです
    これは気をつけないと普通にやってしまいそうなコードですね...

    >上のコードを予測できる人は少ないと思います(少なくとも私の予測は外れていました)。
    これは、本当に予想外です...

    こんな短い質問に、ここまで丁寧な回答ありがとうございます!
    どれも勉強になることばかりでとてもためになりました

    回答ありがとうございました!

    キャンセル

  • 2018/07/27 20:52

    これって Cの定番バグ?

    main() {
    char *str;
    str = FuncA()
    /* FuncB() */
    printf(str);
    }

    char *FuncA() {
    char str[20] = "Func A Str\n";
    return str;
    }

    これは、一見、動くように見え、実際にも問題無く動く事が多いのですが、 FuncB() の呼び出しを有効にした途端、 不正な動きをする。
    パッと見、気が付かない事が多い昔からの定番バグ。

    キャンセル

+1

こんにちは。

C++でループ内で変数宣言を行った場合
1ループごとにその変数は解放されますか?

1ループ毎にブロック・スコープを抜けますので、1ループ毎に開放される建前です。
標準規格書では、「3.3 Scope」のあたりに書いて有りそうです。(C++11でよく使われるドラフト文書N3337の場合)
これを江添氏が解説されています。(規格書に忠実に解説されているようです。)
規格書は難解ですので、この解説の該当箇所を見たほうが分かりやすいと思います。

なお、実装的にはスタックにメモリが確保されますが、1ループ毎にスタック・ポインタを増減しているかというと、やってない筈です。

wandbox

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/07/27 11:25

    とても参考になるサイトを貼っていただきありがとうございます!

    キャンセル

+1

C++ コンパイル出力は詳しく見たこと無いですが、、C の上方互換とすると、

変数が使えるのが、スコープの中という理解は良いと思います。ただ、デバッグ時は要注意。 例えば、以下の場合、

int a, b;
a = 0;
...(a を使ったなんかの処理) ...
...(これ以降は、aは使われない)
b = 2;
...(b を使ったなんかの処理) ...

デバッガ等で、bを使った処理の中で、止めた場合、 aの値は正しく取得できるとは限りません。 なぜならば、 a の値は、b = 2; 以降には使われないので、同じ領域を bが使う事があるからです。 (コンパイラによる最適化)
特に、割り当てられているのがレジスタだったりすると、アウトの可能性が高いです。

ご注意を

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/07/27 09:10

    >デバッガ等で、bを使った処理の中で、止めた場合、 aの値は正しく取得できるとは限りません。
    なるほど...
    確かにこれは、場合によっては意識が必要な部分ですね...

    とても勉強になりました!
    ありがとうございます!

    キャンセル

+1

イメージとしては、変数を定義した時点で変数の「格納場所を確保」し、スコープを抜けるところで「解放」します。

ただし、それをどのように実装するかはC/C++では規定していません。コンパイラー任せです。コンパイラーは、なるべくメモリを無駄遣いしないようによしなに取り計らってくれます。ですので、少なくともループが終了するまで領域を確保し続けるなどということはせず、領域は使い回しです。

実際のところ、C/C++においては変数の領域がどれだけ必要かはコンパイル時に判るので、関数の開始時点で必要な分の領域をまとめて確保し、それを使い回して関数を抜ける際にまとめて解放しています。
さらには、最適化により変数を省いたりレジスターを使ったりして「メモリ領域を消費しない」ということもあります。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/07/27 10:34

    >イメージとしては、変数を定義した時点で変数の「格納場所を確保」し、スコープを抜けるところで「解放」します。
    メモリを意識する必要がない場合
    このイメージだけで十分そうですね

    >それをどのように実装するかはC/C++では規定していません
    なるほど、本当に必要になった場合
    自分の環境で確かめてみるしかないのですね

    >最適化により変数を省いたりレジスターを使ったりして「メモリ領域を消費しない」ということもあります。
    レジスタのことを考えてしまうと
    余計なことをせずにコンパイラに任せたほうがいいのかも
    とか思ったりもしちゃいますね...w

    回答ありがとうございました!

    キャンセル

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

  • ただいまの回答率 90.50%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る

  • C++

    3455questions

    C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

  • メモリリーク

    47questions

    メモリリークは、プログラムファイルがメモリの解放に失敗した時に起こります。

  • パフォーマンス

    22questions

    コード効率の向上や計算に関する質問には、このタグを使ってください。

  • スコープ

    12questions

    スコープとは、プログラム内で変数名など、参照可能な有効範囲のことを指します。