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

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

新規登録して質問してみよう
ただいま回答率
85.48%
スコープ

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

パフォーマンス

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

メモリリーク

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

C++

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

Q&A

解決済

7回答

6334閲覧

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

tuyudaku

総合スコア75

スコープ

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

パフォーマンス

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

メモリリーク

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

C++

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

1グッド

1クリップ

投稿2018/07/26 02:48

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

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

aaaaa22222👍を押しています

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

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

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

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

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

guest

回答7

0

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

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

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

投稿2018/07/26 05:20

yumetodo

総合スコア5850

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

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

tuyudaku

2018/07/27 02:35

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

2018/07/27 11:58

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

0

C++でループ内で変数宣言を行った場合

1ループごとにその変数は解放されますか?

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

投稿2018/07/26 03:00

maisumakun

総合スコア145183

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

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

tuyudaku

2018/07/26 03:48

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

0

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

投稿2018/07/26 02:53

編集2018/07/26 02:59
y_waiwai

総合スコア87747

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

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

tuyudaku

2018/07/26 03:44

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

2018/07/26 04:18

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

2018/07/26 04:33

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

0

ベストアンサー

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

C

1#include <stdio.h> 2 3int main(void) 4{ 5 for (int i = 0; i < 10; i++) { 6 int x; 7 int y[i + 1]; 8 int z[i + 2]; 9 printf("%d: %p, %p, %p\n", i, (void *)&x, (void *)y, (void *)z); 10 } 11}

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

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

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

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

C++

1#include <iostream> 2 3class A 4{ 5public: 6 int v; 7 A(int v) : v(v) 8 { 9 std::cout << "run construct: " << this->v << std::endl; 10 } 11 ~A() { std::cout << "run destruct: " << this->v << std::endl; } 12}; 13 14int main() 15{ 16 for (int i = 0; i < 10; ++i) { 17 A a(i); 18 std::cout << reinterpret_cast<void *>(&a) << std::endl; 19 } 20}

Wandboxでの実行結果

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

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

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

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

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

C++

1#include <iostream> 2 3int main() 4{ 5 int *p = nullptr; 6 for (int i = 0; i < 10; ++i) { 7 if (p != nullptr) { 8 std::cout << p << ": " << *p << std::endl; 9 (*p)++; 10 } 11 int a; 12 if (p == nullptr) { 13 a = 0; 14 } else { 15 a = *p; 16 } 17 p = &a; 18 } 19}

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

C++

1#include <iostream> 2#include <string> 3 4int main() 5{ 6 std::string *p = nullptr; 7 for (int i = 0; i < 10; ++i) { 8 if (p != nullptr) { 9 std::cout << p << ": " << *p << std::endl; 10 *p += "+"; 11 } 12 std::string a; 13 if (p == nullptr) { 14 a = "+"; 15 } else { 16 a = *p; 17 } 18 p = &a; 19 } 20}

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

投稿2018/07/26 13:31

編集2018/07/26 13:33
raccy

総合スコア21735

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

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

tuyudaku

2018/07/27 02:22

>C++にはVLAはありません。 驚きました。コンパイラが上手いことやってくれていたのですね >&などのアドレス演算子を使ってしまうとレジスタのみで完結するコードが生成されないからです。 そうだったのですね... >aはループ毎に終了しており、pに保存されたアドレスを用いてアクセスすることは未定義な動作になってしまうからです これは気をつけないと普通にやってしまいそうなコードですね... >上のコードを予測できる人は少ないと思います(少なくとも私の予測は外れていました)。 これは、本当に予想外です... こんな短い質問に、ここまで丁寧な回答ありがとうございます! どれも勉強になることばかりでとてもためになりました 回答ありがとうございました!
pepperleaf

2018/07/27 11:52

これって Cの定番バグ? main() { char *str; str = FuncA() /* FuncB() */ printf(str); } char *FuncA() { char str[20] = "Func A Str\n"; return str; } これは、一見、動くように見え、実際にも問題無く動く事が多いのですが、 FuncB() の呼び出しを有効にした途端、 不正な動きをする。 パッと見、気が付かない事が多い昔からの定番バグ。
guest

0

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

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

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

投稿2018/07/26 12:58

catsforepaw

総合スコア5938

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

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

tuyudaku

2018/07/27 01:34

>イメージとしては、変数を定義した時点で変数の「格納場所を確保」し、スコープを抜けるところで「解放」します。 メモリを意識する必要がない場合 このイメージだけで十分そうですね >それをどのように実装するかはC/C++では規定していません なるほど、本当に必要になった場合 自分の環境で確かめてみるしかないのですね >最適化により変数を省いたりレジスターを使ったりして「メモリ領域を消費しない」ということもあります。 レジスタのことを考えてしまうと 余計なことをせずにコンパイラに任せたほうがいいのかも とか思ったりもしちゃいますね...w 回答ありがとうございました!
guest

0

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

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

int a, b;

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

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

ご注意を

投稿2018/07/26 10:44

pepperleaf

総合スコア6383

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

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

tuyudaku

2018/07/27 00:10

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

0

こんにちは。

C++でループ内で変数宣言を行った場合

1ループごとにその変数は解放されますか?

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

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

wandbox

投稿2018/07/26 05:13

Chironian

総合スコア23272

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

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

tuyudaku

2018/07/27 02:25

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問