web-dev-qa-db-ja.com

cの動的配列と静的配列

次のコードは、malloc関数を使用して配列を作成します。しかし、これはint array [size]だけではるかに簡単に実行できることを私は知っています。これは静的配列だと思います。しかし、malloc関数では、それは動的配列ですか?私はこのコードをネットで見つけました...実際に何が起こっているのか、静的配列と動的配列(およびヒープメモリ間の静的メモリ)の違いは何ですか?実行時に動的配列のサイズを変更できますか?または...私が正確に知らないこと...誰かが説明できれば私は感謝します:)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(void)
{
    int size;
    int i;
    printf("Choose size of array: ");
    scanf("%d",&size);

    /*---MALLOC/FREE---*/
    int *m_array = (int *)malloc((size+1)*sizeof(int));
    memset(m_array,'\0',size+1);

    for(i=0; i<size ; i++)
    { 
        m_array[i]=i;
        printf("%d ",m_array[i]);
    }
    printf("\n");
    free(m_array);

    return 0;
}
4
Cache

配列は、宣言の方法と場所に応じて、いくつかの種類があります。

固定長配列

固定長配列のサイズは、コンパイル時で決定する必要があります。固定長配列の定義後にサイズを変更することはできません。

固定長配列は、次のいずれかの方法で宣言されます。

T a[N];
T a[N] = { /* initializer list */ };
char_type a[N] = "string literal";
T a[]  = { /* initializer list */ };
char_type a[]  = "string literal";

最初の3つのケースでは、N定数式である必要があり、その値はコンパイル時に認識されている必要があります。最初の3つのケースでは、配列のサイズはNから取得されます。最後の2つのケースでは、初期化子リストの要素数または文字列リテラルのサイズから取得されます。

固定長配列の初期内容は、そのストレージ期間および初期化子が提供されているかどうかによって異なります。

配列にstaticストレージ期間があり(つまり、関数本体の外部のファイルスコープで宣言されているか、staticキーワードで宣言されている)、初期化子が存在しない場合、すべての配列要素は0(スカラーの場合)またはNULL(ポインター用)。 Tstructや配列型などの集約型である場合、集約の各メンバーは0またはNULLで初期化されます。 unionタイプも同様にゼロになります。

配列にautoストレージ期間があり(つまり、staticキーワードなしで関数またはブロック内で宣言された)、初期化子が存在しない場合、配列の内容はindeterminate-基本的にガベージです。

配列が初期化子リストで宣言されている場合(ストレージ期間に関係なく)、配列要素の初期値は初期化子に対応します。初期化子の要素が配列より少ない場合(たとえば、Nは10ですが、最初の5つの要素のみを初期化します)、残りの要素は初期化されますあたかも配列の保存期間はstaticでした。 IOW、宣言が与えられた

int a[10] = {0, 1, 2};

その場合、配列の最初の内容は{0, 1, 2, 0, 0, 0, 0, 0, 0, 0}です。

文字列値を含む固定長配列は、文字列リテラルを使用して初期化できます。 Cは「ワイド」文字列を許可するため、char_typecharまたはwchar_tのいずれかになります。ルールは通常の初期化子リストでも同じですが、N(指定されている場合)は、文字列ターミネータを説明するために、文字列の長さより少なくとも1長くなければなりません。

この意味は

char a[10] = "test"; 

{'t', 'e', 's', 't', 0, 0, 0, 0, 0, 0}として初期化され、

char a[] = "test";

{'t', 'e', 's', 't', 0}として初期化されます。

staticの保存期間を持つ配列は、プログラムがロードされるとすぐに使用できるように保存され、プログラムが終了するまで解放されません。これは通常、それらが.data.bss(またはシステムが使用する実行可能形式に相当するもの)のようなメモリセグメントに格納されていることを意味します。

autoストレージ期間の配列は、ブロックまたは関数のエントリで割り当てられ、ブロックまたは関数の出口で解放されるように格納されます(実際には、範囲内のより小さなスコープに制限されているかどうかに関係なく、関数のエントリで割り当てられる可能性があります関数)-これは通常スタックに変換されますが、have toには変換されません。

可変長配列

可変長配列はC99で追加されました-サイズが実行時で確立されることを除いて、固定長配列と同じように動作しますほとんどNは、コンパイル時の定数式である必要はありません。

int n;
printf( "gimme the array size: ");
scanf( "%d", &n );
T a[n]; // for any type T

名前が示すとおり、可変長配列の定義後にサイズを変更することはできません。 「可変長」とは、コンパイル時にサイズが固定されておらず、定義ごとにサイズが変わる可能性があることを意味します。

サイズは実行時まで設定されないため、可変長配列をファイルスコープまたはstaticキーワードで宣言したり、初期化子リストで宣言したりすることはできません。 VLAのスペースをどのように管理するかは、実装次第です。スタックから取得される場合があります(通常は取得されます)が、AFAIKは別の場所から取得される場合があります。

動的配列

動的配列は、少なくともそれらを管理するために使用するオブジェクトのデータ型に関しては、実際には「配列」ではありません。動的配列は、実行時にmalloccalloc、またはreallocのいずれかを使用して割り当てられ、そのストレージはfreeの呼び出しで解放されるまで保持されます。

T *p = malloc( sizeof *p * N ); // where N may be either a compile-time or
                                // run-time expression
...
free( p );

動的配列は、次のようにreallocライブラリ関数を使用してサイズを変更できます。

/**
 * If realloc fails, it will return NULL and leave the original array in 
 * place.  We assign the result to a temporary variable so we don't risk
 * losing our only reference to that memory. 
 */
T *tmp = realloc( p, sizeof *p * new_size );  
if ( tmp )                                    
  p = tmp;                                    

配列要素自体のメモリはヒープ(または任意の動的メモリプール)から取得されますが、ポインタ変数pのメモリは、.bssまたは.dataセグメントから、あるいはスタックから割り当てられます。 pの保存期間(staticまたはauto)に基づきます。

mallocまたはreallocで割り当てられたメモリは初期化されません。そのメモリの内容は不確定になります。 callocで割り当てられたメモリは、ゼロで初期化されます。

配列とポインタ

ある時点で、誰かが「配列は単なるポインタである」と言うでしょう。その人は正しくありません。

配列(固定長または可変長)を宣言すると、その配列の要素用に十分なストレージが確保され、nothing else;配列の長さや最初の要素へのポインタなどのメタデータ用にストレージが確保されることはありません。宣言が与えられた

T a[N];

その場合、ストレージは次のようになります。

    +---+
 a: |   | a[0]
    +---+
    |   | a[1]
    +---+
    |   | a[2]
    +---+
     ...
    +---+ 
    |   | a[N-1]
    +---+

配列要素自体(または、より正確には、オブジェクトais配列の要素)以外にオブジェクトaはなく、式aは割り当てのターゲットではない可能性があります。

だが...

a[i]defined as *(a + i);つまり、ポインタ値aが与えられた場合、そのアドレスからielementsnot bytes!)をオフセットし、結果を逆参照します。しかし、aがポインターでない場合、それはどのように機能しますか?

このように-それがsizeofまたはunary&演算子のオペランドである場合、または宣言で配列初期化子として使用される文字列リテラルである場合を除いて、タイプ "N-Tの要素配列" converted( "decay")は、 "pointer to T"タイプの式になり、式の値は、配列の最初の要素のアドレスになります。

これにはいくつかの意味があります。

  • a&a、および&a[0]はすべて同じvalue(配列の最初の要素のアドレス)を生成しますが、式のtypesは異なります(それぞれT *T (*)[N]、およびT *);
  • 添え字演算子[]は、配列式とポインター式の両方で同等に機能します(実際、ポインター式で機能するのはdefinedです)。
  • 配列式を関数に渡すとき、あなたが何であるか実際に渡すのは、配列全体ではなく、ポインター値です。

動的配列の場合、状況は異なります。与えられた線

T *p = malloc( sizeof *p * N );

その場合、ストレージは次のようになります。

   +---+
p: |   | ---+
   +---+    |
    ...     |
     +------+
     |
     V
   +---+
   |   | p[0]
   +---+
   |   | p[1]
   +---+
    ...   
   +---+
   |   | p[N-1]
   +---+

この場合、pis配列とは別のオブジェクトです。したがって、&pしないpおよび&p[0]と同じ値を与え、そのタイプはT (*)[N]ではなくT **になります。また、pは単なるポインタ変数であるため、新しい値を割り当てることができます(ただし、最初に指すメモリをfreeせずに割り当てると、メモリリークが発生します)。

同様に、sizeof pしないsizeof aのように動作します。ポインタが指す割り当てられたメモリのサイズではなく、ポインタ変数のサイズを返すだけですto

25
John Bode

静的配列にはコンパイル時にメモリが割り当てられ、メモリはスタックに割り当てられます。一方、動的配列には実行時にメモリが割り当てられ、メモリはヒープから割り当てられます。

これは静的整数配列です。つまり、実行前に割り当てられた固定メモリです。

int arr[] = { 1, 3, 4 };

これは動的整数配列、つまり実行時に割り当てられるメモリです

int* arr = new int[3]; 
2
Muhammad Noman

Size + 1の動的配列を使用し、それに要素を追加して(0からsizeまで)、0を返す前に最後にスペースを解放します。したがって、この場合、int * m_arrayはintへのポインターです。 13行目で行っているのは宣言です。

(int *m_array) =...

および割り当て:

...(int *)malloc((size+1)*sizeof(int));

これは動的配列であるため、ヒープに割り当てられ、解放されるまで保持されます(そのため、最後にfree(m_array)があります)。静的な場合は、次の方法で配列を初期化できます。

int m_array[size];

そして、それは静的ストレージ領域に割り当てられ(自動でない限り)、プログラムが終了するとすぐに割り当てが解除されます。 Cでは静的配列のサイズを変更できないため、動的配列を使用する必要があります。動的配列のサイズに変更する場合は、 realloc を使用します。

1
Larry