web-dev-qa-db-ja.com

アレイインデックスのコンテンツは1つのメモリアドレスに格納されますか?

Cでは、次の1つの項目の配列があるとします。

int a[] = {2000};

2000バイナリでは:

11111010000

各メモリアドレスが1バイト(8ビット)のデータを保持できる場合、チュートリアルによっては、各配列のインデックスが1つのメモリアドレスに格納されると記載されていますか? 2000は11ビットで、少なくとも2つのメモリアドレスを格納する必要があるため、これは不可能です。

2つ目の質問は、2つ(またはそれ以上)のメモリアドレスに格納されている場合、CPUはメモリアドレスのビットの読み取りを停止するタイミングをどのようにして知るのですか?上記の変数aの終わりに達したことをどのようにして知るのですか?

2
Joseph a

次のプログラムを検討してください。

_#include <stdio.h>

int main(int argc, char* argv[]) {
    int a[] = {2000, 3000};
    printf("%p %p\n", &a[0], &a[1]);
    return 0;
}
_

これは、配列の要素のアドレスを出力します。私のマシンで1回実行する場合:

_0x7ffc963400c0 0x7ffc963400c4
_

それらが4つ離れていることに注意してください。 int配列の場合、各配列インデックスはsizeof(int)メモリアドレスをスキップします。これは、structsの配列を含むすべてのタイプの配列で同じです。配列インデックスは要素を識別し、バイトを識別しません。

5
Karl Bielefeldt

各メモリアドレスが1バイト(8ビット)のデータを保持できる場合

これは正確ではありません。メモリが1バイトのサイズのボックスの配列に分割されているわけではありません。つまり、1バイトがメモリの最小単位addressableです。より大きな増分でメモリをアドレス指定できます。

したがって、_char *p_などのアドレスを含むポインタ_0x12345670_がある場合、それは本質的に単なるメモリへのオフセットです-start ofを指し、単一の領域を構成する可能性がありますバイト、または整数、配列、構造体などの複数バイト。 (フラットアドレススペースとして表示されるのは実際には仮想メモリオペレーティングシステムカーネルによって物理メモリにマッピングされるため、実際には少し複雑ですが、この説明の目的のため、違いが生じる・異なる。)

アドレスpの値_0xAABBCCDD_の32ビット整数は、単に4バイトを占有します。これらのバイトは、CPUによってbig-endianの順序で配置されます。ここで、mostの有効ビットは最下位アドレスに格納されます。

_0x1234566F …
0x12345670 0xAA
0x12345671 0xBB
0x12345672 0xCC
0x12345673 0xDD
0x12345674 …
_

またはリトルエンディアン、ここで最小有効ビットは最下位アドレスに格納されます:

_0x1234566F …
0x12345670 0xDD
0x12345671 0xCC
0x12345672 0xBB
0x12345673 0xAA
0x12345674 …
_

Cのようなプログラミング言語は、これを多少抽象化して、さまざまなサイズのオブジェクトをアドレス指定する便利な方法を提供します。 32ビット整数の配列aがあり、pが最初の要素のアドレスであるとします:_p = &a[0]_。アセンブリで、この配列を反復処理する場合は、pを4ずつインクリメントして、次の整数に移動する必要があります。

_&a[0] == p
&a[1] == p + 4
&a[2] == p + 8
&a[3] == p + 12
&a[4] == p + 16
…
_

Cでは、_p + 1_のような式は、pの値にbytesの数を追加するだけでなく、オブジェクトサイズの倍数sizeof(*p) —したがって、pが_uint32_t *p_として入力された場合、_a[1]_は_p + 1_にあり、_a[2]_は_p + 2_にあります。オン。内部的には、_p + n_は_(char *)p + n * sizeof(*p)_のようになります。

cPUは、メモリアドレスのビットの読み取りをいつ停止するかをどのようにして知るのですか?

整数のようなプリミティブ型は常に固定サイズです。 _*pi += 42_を書き込んで_42_をpiによって参照される32ビット整数の内容に追加すると、これは特に32ビットの間接追加命令に変換されます。配列のような複合型は、オブジェクトサイズの倍数であるアドレスの一連の値にすぎません。プログラムは、配列の境界内でのみアクセスする必要があります。高水準言語は、実行時またはコンパイル時に自動チェックを挿入して、とりわけ無効な配列アクセスを防止することによってメモリの安全性を保証します。

mallocの結果のように動的に割り当てられる値は、アロケータが制御できるメモリ領域にすぎず、オペレーティングシステムから取得されます。アロケーターによって付与されたリージョン内でのみアクセスする限り、カスタム構造体の配列など、必要なタイプにキャストできます。

1
Jon Purdy

各メモリアドレスが1バイト(8ビット)のデータを保持できる場合、チュートリアルによっては、各配列のインデックスが1つのメモリアドレスに格納されると説明されていますか? 2000は11ビットで、少なくとも2つのメモリアドレスを格納する必要があるため、これは不可能です。

はい、各メモリアドレスは1バイトを保持できます。ただし、多くの場合、メモリブロックを使用して物を格納します。つまり、連続したメモリアドレスのメモリロケーションを使用して、1バイトより大きいものを格納します。たとえば、intは通常4バイト長です。ただし、配列は言うまでもなく、さらに大きな構造体とオブジェクトを格納することもできます。

2つ目の質問は、2つ(またはそれ以上)のメモリアドレスに格納されている場合、CPUはメモリアドレスのビットの読み取りを停止するタイミングをどのようにして知るのですか?上記の変数aの終わりに達したことをどのようにして知るのですか?

CPUは、複数のデータタイプをサポートしています。つまり、バイトロード、ワードロード、ロングワードロードの命令エンコーディングと、浮動小数点ロードがあります。構造体やオブジェクトなどの大きな項目になると、コンパイラーは複数の命令を生成します(おそらくインラインで、おそらくループで、またはおそらくライブラリのヘルパー関数を呼び出すことによって、つまりmemmoveのようなものをコピーしてより大きな構造)。

CPUは、コンパイラーによって生成されたコードによって実行するように指示されていることを単純に実行します。コンパイラーは、変数の保管場所とレイアウトを選択します。その後、コンパイラーはそれらの保管場所にアクセスするための命令を生成します。

CPUはすべて、(コンパイラーまたはアセンブリー)プログラムされた命令を実行するだけであることに注意してください。これは、どのデータを小さなステップで操作するかを指示します。CPUは変数自体の位置とレイアウトを知らないため、ほとんど実行されません。指示(本当に速い)。

したがって、ソースコードの意味に忠実なCPUの一貫した命令ストリームを生成するのはコンパイラの仕事です。言語の定義(C標準など)は、ソースコードにある言語構成の意味をコンパイラー(またはコンパイラー作成者)に伝え、これによりマシンコードへの変換がガイドされます。

1
Erik Eidt

@ジョセファは、「大丈夫ですが、1つのメモリアドレスは1バイトだけではないのですか?」と言いました。

Cにはありません。C言語は、ハードウェアアーキテクチャのオーバーレイである抽象的なマシンを定義します。コンパイラーは、抽象マシンで実行しているように見えるように調整しますが、その特定のアーキテクチャーに必要なコードを発行します。 Cには仮想または物理アドレス空間はなく、オブジェクトへのポインタとvoidへのポインタのみが標準で指定されています。 Cでポインタを操作する場合、メモリアドレスではなく、格納場所を表すトークンがあります。そのポインター値は、実際にはアドレスバスのビットパターンのビットパターンと一致するビットパターンを持っている可能性がありますが、ここでは関係ありません。

Intへのポインタがある場合、それはsizeof物理メモリセルではなく、sizeof(int)を持つオブジェクトを指します。

0
jwdonahue