6134


プログラムで確保されるメモリ領域には、スタック領域とヒープ領域があると思います。

スタック領域は関数内などで定義される変数によって確保される領域で、  
コンパイルをした時点でメモリの大きさが決まり、確保や解放もOS任せになります。

それに対してヒープ領域は動的確保(mallocとかnew)で確保される領域のことで、  
プログラマが解放しない限りそこに残り続けます。

ここで質問なのですが、(上の解釈が間違っている時はご指摘ください)  
例えば以下のコード(C++)があったとします。

[code]

    void Hoge()
    {
        int hoge1 = 0;
    
        for( int i = 0; i < 10; ++i ){
            hoge1+=i;
        }
    }
    
[/code]

ここで変数`hoge1`、for文内の変数`i`はどちらもスタック領域に確保されると思います。

また、変数`i`のスコープはfor文内のみで、ほかからはアクセス出来ません。  
質問というのは、for文を抜けた時点で、変数`i`はメモリ上には残るのでしょうか？

何が知りたいかというと、変数のスコープが小さければ小さいほど、  
メモリの節約になるのかということです。  
よろしくお願いします。


CC BY-SA 3.0
2015-02-08T05:27:15.967
6133
2017-04-27T20:15:14.673
2015-02-09T14:56:19.690
7499
7499
question
c++
プログラムで確保されるメモリについて


> for文を抜けた時点で、変数iはメモリ上には残るのでしょうか？

ほとんどのケースで残ります。通常スタックの増減は関数に入る時と出る時に実施されます。BLUEPIXY さんのコメントにもあるように、
_スコープに合わせてスタックを増減する_
可能性もゼロではありませんが、スタックの増減は決して安価な操作ではないので、スコープという単位で行うのは現実的ではありません。

> 何が知りたいかというと、変数のスコープが小さければ小さいほど、メモリの節約になるのかということです。

質問の例では節約になりませんが、本質的には節約になる可能性があります。以下の様なコードを考えてみてください。

[code]

    void Foo() {
        int bar = 0;
    
        for (int i = 0; i < 10; ++i) {
            bar += i;
        }
        for (int j = 10; j > 0; --j) {
            bar += j;
        }
    }
    
[/code]

前半のループで使用している `i` と、後半のループで使用している `j` は、スコープが分かれていてお互いに独立しています。このような時、`i` と
`j` へ同じメモリ領域を用いて/再利用して、この関数が使用するスタックメモリの総量を少なくする、コンパイラがそういう最適化を行う余地が生まれます。


CC BY-SA 3.0
2015-02-08T06:03:30.010
6134
2015-02-08T06:48:21.930
2015-02-08T06:48:21.930
208
208
6133
answer


試しに簡単なプログラムを書いてみました。

[code]

    #include <cstdio>
    
    void f(int n){
        if(n == 0) return;
        int i;
        printf("i(%d):%p\n", n, &i);
        {
            int j;
            printf("j(%d):%p\n", n, &j);
            //f(n-1);//(1)
        }
        f(n-1);//(2)
    }
    
    int main(void) {
        f(3);
        return 0;
    }
    
[/code]

* * *

(2)で関数`f`を呼び出す場合(1)で関数を呼び出す場合とスコープが異なるので、それぞれの場合で表示されるアドレスのパターンの違いになって現れると予想されます。  
(1)のプログラムと(2)のプログラムが同じ（パターンの）出力となるならあらかじめ確保されていると考えてよいと思います。


CC BY-SA 3.0
2015-02-08T06:20:42.880
6136
2015-02-08T06:27:38.767
2015-02-08T06:27:38.767
5044
5044
6133
answer


関数呼び出しにまとめてスタック領域に割り当てられ、関数を抜けるとスタック領域から解放されるケースがほとんどです。  
また、コンパイラの最適化により、CPUレジスタに割り当てられるケースもあります。

実行環境が何かは、分かりませんが、仮にPC端末で稼働するプログラムであれば、  
変数程度に割り当てられるメモリを気にする必要は、まずありません。  
可読性を優先して下さい。無闇な変数使い回しなどは、保守性の低下につながります。

下手に手動で最適化すると返って効率悪くなる可能性もあります。  
コンパイラが最適化するので、細かいことを気にするよりは、コンパイラに任せたほうが良いでしょう。


CC BY-SA 3.0
2015-02-08T06:33:17.720
6137
2015-02-08T06:33:17.720
2207
6133
answer


BLUEPIXY さんのコメントの、

> コンパイラが吐き出‌​したアセンブラソースを調べる必要があります。

に関して、gcc を使って、アセンブラコードを眺めてみました。多少見やすくするために、`Hoge` 関数を以下の様に書き換えています。

[code]

    void Hoge()
    {
        int hoge1 = 2;
        int i = 1;
    
        for( int i = 0; i < 10; ++i ){
            hoge1+=i;
        }
        i++;
    }
    
    int main(int argc, char *argv[]) {
      Hoge();
      return 0;
    }
    
[/code]

これを、最適化を行わないでコンパイルしてアセンブラコードを出力させます。

[code]

    $ g++ -Wall -S hoge.cc
    
[/code]

結果から、関連のある部分だけを取り出したものが以下です。

[code]

      # auto 変数をセット(初期化)
      movl  %esp, %ebp    # %esp = stack pointer/%ebp = base pointer
      subl  $16, %esp
      movl  $2, -12(%ebp) # hoge1
      movl  $1, -4(%ebp)  # i
      movl  $0, -8(%ebp)  # for 文内の i
      jmp  .L2
    .L3:  # for 文の中身と ++i
      movl  -8(%ebp), %eax
      addl  %eax, -12(%ebp)
      addl  $1, -8(%ebp)
    .L2:  # for 文の条件部
      cmpl  $9, -8(%ebp)
      jle  .L3
      # 最後の i++
      addl  $1, -4(%ebp)
      leave
    
[/code]

特に説明をしなくても、処理の流れはお分かりかと思います。Hoge 関数内の auto 変数を宣言・初期化している部分に対応するのは以下です。

[code]

        movl    $2, -12(%ebp)
        movl    $1, -4(%ebp)
        movl    $0, -8(%ebp)
    
[/code]

`$2` は immediate value(即値)で、`-12(%ebp)` は stack pointer の位置からの offset 値です(ここでは
base pointer に stack pointer の値をセットしています)。int 型なので、-4 byte づつずらして stack
領域にセットしています。  
gcc の場合ですと、関数内の auto 変数はスコープに関係なく全て最初に stack 領域に確保されることになります。つまり、for 文で宣言された変数
i は for 文を抜けても stack 領域に存在していることになります。

余談ですが、このソースコードを gcc に最適化オプションを付けてコンパイルするとどうなるのでしょうか。皆さんお分かりかと思いますが、こうなります。

[code]

    _Z4Hogev:
    .LFB0:
      .cfi_startproc
      rep ret
      .cfi_endproc
    
[/code]

Hoge 関数そのものがなくなります。理由は、、説明の必要はありませんよね。

追記:

gcc の場合、`asm` 文を使うことができますので、Hoge 関数を以下の様に書き換えてみます。なお、以下で埋め込んでいるアセンブラコードは Intel
32bit CPU のニーモニックです。

[code]

    void Hoge()
    {
      int hoge1 = 2;
      int i = 1;
    
      for( int i = 0; i < 10; ++i ){
        hoge1+=i;
      }
    
      printf("i = %d\n", i);    
      asm("movl -16(%ebp), %eax");
      asm("movl %eax, -12(%ebp)");
      printf("i = %d\n", i);
    }
    
[/code]

これをコンパイル・リンクして実行すると、

[code]

    $ g++ -Wall -o hoge hoge.cc
    $ ./hoge
    i = 1
    i = 10
    
[/code]

となります。まぁ、アセンブラコードレベルではスコープなんてありませんので…


CC BY-SA 3.0
2015-02-08T07:13:25.870
6140
2015-02-08T07:49:00.020
2015-02-08T07:49:00.020
6133
answer


メモリーの節約を目指しているようですが、

1.一般にメモリーの節約はユーザーにとって迷惑である。

という点に留意しなければなりません。

一般的に、ユーザーはその処理を早く終わらせたいのであって、実行時のメモリーのコンパクトさには関心がないのが普通です。  
この例外となるのは組み込み系と常駐型のコードですが、これらも提示の様なコードに配慮するよりも、全体的な設計でメモリー使用量と実行速度のトレードオフをすべきだと考えられます。

変数のスコープを限定してスタックの部分的な使用量を低減することは、コンパイラの最適化により排除されると予測できますが、たとえ可能であっても、スタックポインタの制御に余計な時間がかかるだけで、単に実行速度の悪化となると考えられ、結果的にユーザーは少しも喜ばないと予測できます。


CC BY-SA 3.0
2015-02-09T07:30:15.883
6182
2015-02-09T07:30:15.883
3793
6133
answer


forブロックではなくブロックでも同じですね。

## 私も素人なのですが、以下のようなサンプルを作ってみました。

# include <iostream>

int main(){  
{int i = 10;  
std::cout << "&i=" << &i << std::endl;  
}  
{int j;  
std::cout << "j=" << j << std::endl;  
std::cout << "&j=" << &j << std::endl;  
}  
return 0;

## }

私の環境ではiとjが同じアドレスとなりました。  
また、jの値は10（iの値と同じ）になりました。これはiの値が残っていたのでしょう。  
ブロックが重ならないため同じで構わず、この場合はその分メモリが節約され、iの値も残ったわけですが、これは環境依存（コンパイル・オプションでも違うだろう）ですから、常にこうなる保障はまったくありません。  
メモリ節約になる「場合もある」ということだと思います。


CC BY-SA 3.0
2017-04-27T20:15:14.673
34334
2017-04-27T20:15:14.673
22824
6133
answer