web-dev-qa-db-ja.com

intの配列を宣言する

これらの2つの宣言に違いはありますか?

int x[10];

vs.

int* x = new int[10];

前者の宣言(後者の宣言と同様)はポインター宣言であり、両方の変数を同じように扱うことができると思います。本質的に同じという意味ですか?

62
Meysam
_#include<iostream>    

int y[10];


void doSomething()
{
    int x[10];
    int *z  = new int[10];
    //Do something interesting

    delete []z;
}

int main()
{
    doSomething();

}
_

しょー@ sho011221 10 Oct 2018

_int x[10]; 
_

-スタック上にサイズ10の整数の配列を作成します。
-スタックが巻き戻されるとメモリがなくなるため、このメモリを明示的に削除する必要はありません。
-そのスコープは関数doSomething()
に制限されます

_int y[10];
_

-BSS/Dataセグメントにサイズ10の整数の配列を作成します。
-このメモリを明示的に削除する必要はありません。
-globalと宣言されているため、グローバルにアクセスできます。

_int *z = new int[10];
_

-ヒープにサイズ10整数の動的配列を割り当て、このメモリのアドレスをzに返します。
-この動的メモリは、使用後に明示的に削除する必要があります。使用:

_delete[] z;
_
74
Alok Save

の間で似ている唯一のもの

_int x[10];
_

そして

_int* x = new int[10];
_

_int*_が期待されるいくつかのコンテキストで使用できることです。

_int* b = x;   // Either form of x will work

void foo(int* p) {}

foo(x);      // Either form will work
_

ただし、_int*_が期待されるすべてのコンテキストで使用することはできません。具体的には、

_delete [] x;  // UB for the first case, necessary for the second case.
_

コアの違いのいくつかは、他の回答で説明されています。その他の主な違いは次のとおりです。

差1

_sizeof(x) == sizeof(int)*10   // First case

sizeof(x) == sizeof(int*)     // Second case.
_

差2

_&x_のタイプは、最初のケースではint (*)[10]です

_&x_のタイプは、2番目のケースでは_int**_です

与えられた機能

_void foo(int (&arr)[10]) { }
_

2番目のxではなく、最初のxを使用して呼び出すことができます。

_foo(x);     // OK for first case, not OK for second case.
_
8
R Sahu

標準に従って、実際には3種類の配列宣言を区別する必要があります。

_int x[10];

void method() {
     int y[10];
     int *z = new int[10];
     delete z;
}
_

最初の宣言_int x[10]_は、 cppreference で定義されるstaticストレージ期間を使用します。オブジェクトのストレージは、プログラムの開始時に割り当てられ、プログラムの終了時に割り当て解除されます。オブジェクトのインスタンスは1つしか存在しません。

2番目の_int y[10]_は、automaticストレージ期間を使用し、 cppreference で次のように定義されます。オブジェクトは、囲んでいるコードブロックの最初に割り当てられ、最後に割り当て解除されます。静的、extern、またはthread_localとして宣言されたオブジェクトを除き、すべてのローカルオブジェクトにはこの保存期間があります。

3番目の_int *z = new int[10]_は、通常dynamicメモリ割り当てと呼ばれ、実際には2ステップのシーケンスです。

  • まず、演算子newが呼び出され、標準ライブラリのデフォルトの割り当て方法またはユーザー定義の実装のいずれかを使用してメモリが動的に割り当てられます(newは実行時にオーバーライドできるため)。割り当てられたメモリは、割り当てられたN個の要素と、指定された割り当てのメタデータを保持するために必要な追加のメモリに適合するのに十分です(後で正常に解放できるように)。
  • 第二に、最初のステップが成功した場合、配列内の各オブジェクトの初期化または構築に進みます。

他のコメントですでに述べたように、これらのタイプの宣言には微妙な違いがありますが、最も一般的なものは次のとおりです。

  1. 最新のオペレーティングシステムの場合:

  2. 動的に割り当てられたメモリは、プログラマによって明示的にdelete- edされる必要がありますが、静的および自動ストレージ変数は「環境」によって処理されます

  3. 静的および自動ストレージ変数は特定のスコープに制限されますが、動的に割り当てられたメモリには境界がありません。つまり、1つのモジュールで宣言された変数は、同じアドレス空間で動作する他のモジュールに渡すことができます

  4. _new[]_を使用して配列を割り当てる場合、サイズは0になります

  5. (@R Sahuが既に指摘したように)_&x_と_&z_の型は異なります:

    • _&x_はint (*)[10]です
    • _&z_は_int **_です
6
Daniel Trugman

最初は、サイズが_10_のintの配列です。スタック上に作成されたと言って間違っています。標準はそれを保証しないためです。その実装定義。その保存期間は、xグローバル変数かローカルかどうかに応じて静的または自動になります。変数。

2番目のタイプでは、タイプ_int*_のポインターを作成します。必ずしもヒープ上に作成されるわけではありませんが、標準ではそうではありません。割り当てられたメモリは10 * sizeof(int)バイトに及びます。このためには、次のように記述して、自分でメモリの割り当てを解除する必要があります。

_delete [] x; 
_

この場合、ポインターxのメモリは動的に割り当てられ、動的に割り当て解除されるため、そのようなオブジェクトは動的ストレージ期間を持つと言われます。

6
Nawaz

宣言はまったく異なります。

最初のケース、

_int x[10];
_

xを_10_整数の配列として宣言しますが、2番目の場合、

_int* x = new int[10];
_

xintへのポインターとして宣言します-値がintのアドレスに等しい変数であり、新しい式の結果へのポインターを初期化します(_new int [10]_) 10個の整数の配列を動的に割り当てます。

違いにかかわらず、2つは同様の方法でusedにすることができます。

  • 配列構文(例えば_x[i]_、ここでiは_0_と_9_の間の整数値です)は、上記の構文でそれぞれの配列の値を設定または取得するために使用できます;
  • ポインター演算を使用して、配列の要素のアドレスを取得できます(たとえば、_x + i_は_&x[i]_と_0_の間のiの_10_と同等です。 [はい、「終わりを過ぎた」アドレスを取得することが可能です];
  • ポインターの逆参照と配列アクセスは同等です。すなわち、*(x+i)と_x[i]_は同等です。iの場合、_0_と_9_の間[「one end the end」ポインターを参照解除すると未定義の動作が得られます]。

ただし、たとえば、いくつかの重要な違いもあります。

sizeof演算子の結果sizeof(x)は、2つの場合に異なる値を与えます。

  1. 最初の場合はsizeof(x) == sizeof(int)*10sizeof(int)は実装定義の値を提供しますが、sizeof(x)/sizeof(*x)は常に配列内の要素数を提供します(つまり、値_std::size_t_の_10_)。
  2. 2番目のsizeof(x) == sizeof(int *)-実装定義の値です。 sizeof(x)/sizeof(*x)の値は、_10_の値を生成することはほとんどありません。つまり、この手法を使用して要素数を取得することはできません。

ライフタイム

  1. 最初の場合、xの有効期間は、宣言が発生するスコープに依存します。宣言がファイルスコープ(つまり、コンパイルブロック内、関数ブロック外)で発生する場合、xには静的な保存期間があります(プログラムが実行されている限り存在します)。宣言がブロック内で発生した場合、x-およびそのすべての要素-は、ブロックが終了したときに存在しなくなります。例えば

    _{
        int x[10];
    
    }    //  x and all its elements cease to exist here
    _
  2. 2番目のケースでは、スコープに依存するライフタイムを持つのはポインターxだけです。動的に割り当てられたメモリ(_new x[10]_の結果)は割り当て解除されません。これは、xの有効期間と、それが参照する(動的に割り当てられた)配列の有効期間が分離されることを意味します。

割り当ての結果配列を再割り当てすることはできません。ポインターは(適切にconst修飾されていない限り)再割り当てできません。

のコンテキストを考えます

_ // x as previously defined in one or the other form

 int y[10];
 int z;

 x = y;
 x = &z;
_

最初のケースでは、両方の割り当てによりコンパイラの診断が行われます-割り当ては無効です。 2番目の場合、割り当ては有効であり、xが(_の最初の要素)yのアドレスとzのアドレスをそれぞれ指すようにします。 xの値が再割り当ての前に別のポインターに格納されていない限り、新しい式(_new int [10]_)によって割り当てられたメモリがリークされます。プログラムからアクセスできなくなりますが、解放されません。

3
Peter

配列のサイズを動的に変更したい場合、例えば:

void doSomething(int size)
{
    int x[size];               // does not compile
    int *z  = new int[size];
    //Do something interesting ...
    doMore(z, size);
}

xはC++でコンパイルされないため、zを使用する必要があります。良いニュースは、ほとんどの場合、静的に割り当てられているかのようにzを使用できるようになったことです。

void doMore(int anArray[], int size)
{
    // ...
}

zを引数として受け取り、ポインターを配列として扱います。

0

両方のxが10個の整数の配列の最初のメモリアドレスを指す限り同じですが、その点で非常に異なります

int x[10] 

静的ランダムアクセスメモリでメモリを宣言し、キーワード「new」でヒープを使用してメモリを動的に作成します。これは、cでmallocを使用して動的に配列を作成するのとほぼ同じです。

それだけでなく、(理論をテストしていないと思う)次の可能性があります:

int* x = new int[10];

失敗する可能性があり、コンパイラーによっては、エラーまたはNULLポインターを返す可能性があります。 c ++コンパイラがANSI/ISO標準に準拠している場合、例外をスローするのではなく、割り当てが失敗した場合にnullを返す「no-throw」形式のnewをサポートします。

もう1つの違いは、「新しい」演算子をオーバーロードできることです。

ただし、どちらかが(c ++で)nullで終了する配列を作成するかどうかはわかりません。 cで、少なくとも使用するコンパイラでは、境界を過度に拡張することなくそれらを反復処理できると予想される場合は、必ず文字列または配列に\ 0を追加する必要があります。

ちょうど私の$ .02の価値。 :)

0
Steven S

最初のケース:xは、それが非静的ローカル変数であるか静的/グローバル変数であるかに基づいて、スタック/データセグメント上に作成されます。また、xのアドレスは変更できません。

2番目のケース: 'x'は、ヒープ(フリーストア)で作成された配列を指すポインターです一般的に。他の何かを指すxも変更できます。さらに、delete[] x;を使用して割り当てを解除する必要があります

0
iammilind