CやC ++のようなプログラミング言語では、静的および動的なメモリ割り当てを指すことがよくあります。概念は理解できますが、「コンパイル時にすべてのメモリが割り当てられた(予約された)」というフレーズは常に混乱します。
コンパイルは、私が理解しているように、高レベルのC / C ++コードを機械語に変換し、実行可能ファイルを出力します。コンパイルされたファイルでメモリはどのように「割り当て」られますか?メモリは常にすべての仮想メモリ管理のものとともにRAMに割り当てられているのではありませんか?
定義上、メモリ割り当てはランタイムの概念ではありませんか?
C / C ++コードで1KBの静的に割り当てられた変数を作成した場合、実行可能ファイルのサイズは同じ量だけ増加しますか?
これは、「静的割り当て」という見出しの下でフレーズが使用されているページの1つです。
コンパイル時に割り当てられるメモリとは、コンパイラがコンパイル時に解決され、プロセスメモリマップ内で特定のものが割り当てられることを意味します。
たとえば、グローバル配列について考えてみます。
int array[100];
コンパイラは、コンパイル時に配列のサイズとのサイズをint
認識しているため、コンパイル時に配列全体のサイズを認識します。また、グローバル変数にはデフォルトで静的ストレージ期間があります。これは、プロセスメモリスペースの静的メモリ領域(.data / .bssセクション)に割り当てられます。その情報が与えられると、コンパイラはコンパイル中に、その静的メモリ領域のどのアドレスに配列を配置するかを決定します。
もちろん、そのメモリアドレスは仮想アドレスです。プログラムは、独自のメモリ空間全体(たとえば、0x00000000から0xFFFFFFFF)があることを前提としています。そのため、コンパイラは「オーケー、配列はアドレス0x00A33211にあります」のような仮定を行うことができます。実行時に、そのアドレスはMMUおよびOSによって実アドレス/ハードウェアアドレスに変換されます。
値で初期化された静的ストレージのものは少し異なります。例えば:
int array[] = { 1 , 2 , 3 , 4 };
最初の例では、コンパイラは配列を割り当てる場所のみを決定し、その情報を実行可能ファイルに格納します。
値が初期化されたものの場合、コンパイラは配列の初期値も実行可能ファイルに挿入し、プログラムの開始時に配列を割り当てた後、配列にこれらの値を入力する必要があることをプログラムローダーに通知するコードを追加します。
コンパイラによって生成されたアセンブリの2つの例を次に示します(x86ターゲットを使用したGCC4.8.1)。
C ++コード:
int a[4];
int b[] = { 1 , 2 , 3 , 4 };
int main()
{}
出力アセンブリ:
a:
.zero 16
b:
.long 1
.long 2
.long 3
.long 4
main:
pushq %rbp
movq %rsp, %rbp
movl $0, %eax
popq %rbp
ret
ご覧のとおり、値はアセンブリに直接挿入されます。配列a
では、コンパイラは16バイトのゼロ初期化を生成します。これは、静的に格納されたものをデフォルトでゼロに初期化する必要があると標準が定めているためです。
8.5.9(イニシエーター)[注]:
静的ストレージ期間のすべてのオブジェクトは、他の初期化が行われる前に、プログラムの起動時にゼロで初期化されます。場合によっては、追加の初期化が後で行われます。
コンパイラがC ++コードで実際に何をするかを確認するために、コードを逆アセンブルすることを常にお勧めします。これは、ストレージクラス/期間(この質問のような)から高度なコンパイラ最適化に適用されます。コンパイラにアセンブリを生成するように指示することもできますが、インターネット上でこれを友好的に行うためのすばらしいツールがあります。私のお気に入りはGCCExplorerです。
無へのコール-コンパイル時に割り当てられたメモリは、単に実行時に更なる割当てがないことを意味しmalloc
、new
または他の動的割当て方法。常にすべてのメモリを必要としない場合でも、一定量のメモリ使用量があります。
定義上、メモリ割り当てはランタイムの概念ではありませんか?
メモリは実行前には使用されていませんが、実行開始直前に割り当てがシステムによって処理されます。
C / C ++コードで1KBの静的に割り当てられた変数を作成した場合、実行可能ファイルのサイズは同じ量だけ増加しますか?
静的を宣言するだけでは、実行可能ファイルのサイズが数バイトを超えることはありません。ゼロ以外の初期値で宣言すると(その初期値を保持するため)。むしろ、リンカは、実行直前にシステムのローダーが作成するメモリ要件に、この1KBの量を追加するだけです。
コンパイル時に割り当てられるメモリとは、プログラムをロードすると、メモリの一部がすぐに割り当てられ、この割り当てのサイズと(相対的な)位置がコンパイル時に決定されることを意味します。
char a[32];
char b;
char c;
これらの3つの変数は「コンパイル時に割り当てられます」。つまり、コンパイラーはコンパイル時にそれらのサイズ(固定)を計算します。変数a
はメモリ内のオフセットになります。たとえば、アドレス0を指すと、b
アドレス33とc
34を指します(配置の最適化がないと仮定します)。したがって、1Kbの静的データを割り当てても、コード内のオフセットが変更されるだけなので、コードのサイズは大きくなりません。実際のスペースは、ロード時に割り当てられます。
カーネルはそれを追跡し、その内部データ構造(各プロセス、ページなどに割り当てられるメモリの量)を更新する必要があるため、実際のメモリ割り当ては常に実行時に行われます。違いは、コンパイラは使用する各データのサイズをすでに認識しており、プログラムが実行されるとすぐに割り当てられることです。
相対アドレスについて話していることも忘れないでください。変数が配置される実際のアドレスは異なります。ロード時に、カーネルはプロセス用にメモリを予約します。たとえば、アドレスx
で、実行可能ファイルに含まれるすべてのハードコードされたアドレスはx
バイト単位でインクリメントされるためa
、例の変数はアドレスx
、b、アドレスx+33
、など。
Nバイトを占めるスタックに変数を追加しても、(必然的に)ビンのサイズはNバイト増加しません。実際、ほとんどの場合、数バイトしか追加されません。
コードに1000文字を追加すると、ビンのサイズが直線的に増加する例から始めましょう。
1kが1000文字の文字列である場合、そのように宣言されます
const char *c_string = "Here goes a thousand chars...999";//implicit \0 at end
そしてvim your_compiled_bin
、あなたはそうすることになっていました、あなたは実際にどこかのビンのその文字列を見ることができるでしょう。その場合、はい。実行可能ファイルには文字列が完全に含まれているため、実行可能ファイルは1k大きくなります。
ただし、スタックにint
s、char
s、またはlong
sの配列を割り当て、それをループで割り当てる場合、これらの線に沿った何か
int big_arr[1000];
for (int i=0;i<1000;++i) big_arr[i] = some_computation_func(i);
次に、いいえ:ビンは増加しません...1000*sizeof(int)
コンパイル時の割り当てによって、それが意味することを理解するようになりました(コメントに基づいて):コンパイルされたビンには、システムがメモリの量を知るために必要な情報が含まれていますアプリケーションが必要とするスタックサイズに関する情報とともに、実行時に必要となる関数/ブロック。これは、システムがビンを実行するときに割り当てるものであり、プログラムはプロセスになります(ビンの実行は、私が言っていることを理解するプロセスです)。
もちろん、ここでは全体像を描いているわけではありません。ビンには、ビンが実際に必要とするスタックの大きさに関する情報が含まれています。この情報に基づいて(とりわけ)、システムはスタックと呼ばれるメモリのチャンクを予約し、プログラムが一種の自由な支配を獲得します。プロセス(ビンが実行された結果)が開始されると、スタックメモリはシステムによって割り当てられます。次に、プロセスがスタックメモリを管理します。関数またはループ(任意のタイプのブロック)が呼び出される/実行されると、そのブロックにローカルな変数がスタックにプッシュされ、他のユーザーが使用できるように削除されます(スタックメモリは「解放」されます)。関数/ブロック。したがって、宣言int some_array[100]
すると、ビンに数バイトの追加情報が追加されるだけであり、関数Xが100*sizeof(int)
+いくらかの簿記スペースを必要とすることをシステムに通知します。
多くのプラットフォームでは、各モジュール内のすべてのグローバルまたは静的割り当ては、コンパイラによって3つ以下の統合割り当て(1つは初期化されていないデータ(「bss」と呼ばれる)用、もう1つは初期化された書き込み可能データ(「データ」と呼ばれる)用)に統合されます。 )、および定数データ用に1つ( "const"))、プログラム内の各タイプのすべてのグローバルまたは静的割り当ては、リンカーによってタイプごとに1つのグローバルに統合されます。たとえば、int
が4バイトであると仮定すると、モジュールの静的割り当ては次のとおりです。
int a;
const int b[6] = {1,2,3,4,5,6};
char c[200];
const int d = 23;
int e[4] = {1,2,3,4};
int f;
これは、bssに208バイト、「data」に16バイト、「const」に28バイトが必要であることをリンカに通知します。さらに、変数への参照はすべてエリアセレクターとオフセットに置き換えられるため、a、b、c、d、およびeはbss + 0、const + 0、bss + 4、const + 24、dataに置き換えられます。それぞれ+0、またはbss +204。
プログラムがリンクされると、すべてのモジュールのすべてのbss領域が連結されます。同様に、データとconst領域。モジュールごとに、bss相対変数のアドレスは、先行するすべてのモジュールのbss領域のサイズだけ増加します(ここでも、dataとconstを使用します)。したがって、リンカが完了すると、どのプログラムにも1つのbss割り当て、1つのデータ割り当て、および1つのconst割り当てがあります。
プログラムがロードされると、プラットフォームに応じて、通常、次の4つのいずれかが発生します。
実行可能ファイルは、各種類のデータ、および初期化されたデータ領域に必要なバイト数を示します。初期化されたデータ領域では、初期コンテンツが見つかります。また、bss-、data-、またはconst-相対アドレスを使用するすべての命令のリストも含まれます。オペレーティングシステムまたはローダーは、各領域に適切な量のスペースを割り当ててから、その領域の開始アドレスを、それを必要とする各命令に追加します。
オペレーティングシステムは、3種類のデータすべてを保持するためにメモリのチャンクを割り当て、アプリケーションにそのメモリのチャンクへのポインタを提供します。静的データまたはグローバルデータを使用するコードは、そのポインターを基準にして逆参照します(多くの場合、ポインターはアプリケーションの存続期間中、レジスターに格納されます)。
オペレーティングシステムは、バイナリコードを保持するものを除いて、最初はアプリケーションにメモリを割り当てませんが、アプリケーションが最初に行うことは、オペレーティングシステムに適切な割り当てを要求することであり、これは永久にレジスタに保持されます。
オペレーティングシステムは最初はアプリケーションにスペースを割り当てませんが、アプリケーションは起動時に適切な割り当てを要求します(上記のとおり)。アプリケーションには、メモリが割り当てられた場所を反映するために更新する必要があるアドレスを含む命令のリストが含まれますが(最初のスタイルと同様)、アプリケーションにOSローダーによってパッチを適用するのではなく、アプリケーションにパッチを適用するのに十分なコードが含まれます。
4つのアプローチすべてに長所と短所があります。ただし、いずれの場合も、コンパイラーは任意の数の静的変数を固定された少数のメモリー要求に統合し、リンカーはそれらすべてを少数の統合された割り当てに統合します。アプリケーションはオペレーティングシステムまたはローダーからメモリのチャンクを受信する必要がありますが、その大きなチャンクから個々の部分をそれを必要とするすべての個々の変数に割り当てるのはコンパイラとリンカです。
あなたの質問の核心はこれです:「メモリはコンパイルされたファイルでどのように「割り当て」られますか?メモリは常にすべての仮想メモリ管理のものとともにRAMに割り当てられませんか?メモリ割り当ては定義上ランタイムの概念ではありませんか?」
問題は、メモリ割り当てに関係する2つの異なる概念があることだと思います。基本的に、メモリ割り当ては、「このデータ項目はこの特定のメモリチャンクに格納される」と言うプロセスです。最新のコンピュータシステムでは、これには2つのステップのプロセスが含まれます。
後者のプロセスは純粋に実行時ですが、データのサイズが既知であり、固定数が必要な場合は、前者をコンパイル時に実行できます。基本的には次のように機能します。
コンパイラは、次のような行を含むソースファイルを確認します。
int c;
変数「c」用にメモリを予約するように指示するアセンブラの出力を生成します。これは次のようになります。
global _c
section .bss
_c: resb 4
アセンブラが実行されると、メモリの「セグメント」(または「セクション」)の先頭からの各アイテムのオフセットを追跡するカウンタが保持されます。これは、ファイル全体のすべてを含む非常に大きな「構造体」の一部のようなもので、現時点では実際のメモリは割り当てられておらず、どこにでも配置できます。_c
特定のオフセット(たとえば、セグメントの開始から510バイト)を持つテーブルに記録し、そのカウンターを4ずつインクリメントするため、次のそのような変数は(たとえば)514バイトになります。のアドレスを必要とするコードの場合_c
、出力ファイルに510を配置し、出力_c
には後で追加することを含むセグメントのアドレスが必要であるという注記を追加します。
リンカは、アセンブラのすべての出力ファイルを受け取り、それらを調べます。オーバーラップしないように各セグメントのアドレスを決定し、命令が正しいデータ項目を参照するように必要なオフセットを追加します。が占めるような初期化されていないメモリの場合c
(コンパイラが初期化されていないメモリ用に予約されている名前である「.bss」セグメントにメモリを配置したため、メモリは初期化されないとアセンブラに通知されました)、予約する必要がある量をオペレーティングシステムに通知する出力のヘッダーフィールド。再配置される場合がありますが(通常は再配置されます)、通常は1つの特定のメモリアドレスでより効率的にロードされるように設計されており、OSはこのアドレスでロードしようとします。この時点で、によって使用される仮想アドレスが何であるかについてはかなり良い考えがありますc
。
プログラムが実行されるまで、物理アドレスは実際には決定されません。ただし、プログラマーの観点からは、物理アドレスは実際には無関係です。OSは通常、誰にも気にせず、頻繁に変更される可能性があるため(プログラムの実行中でも)、それが何であるかさえわかりません。 OSの主な目的は、とにかくこれを抽象化することです。
実行可能ファイルは、静的変数に割り当てるスペースを記述します。この割り当ては、実行可能ファイルを実行するときにシステムによって行われます。したがって、1kB静的変数は1kBで実行可能ファイルのサイズを増加させません。
static char[1024];
もちろん、初期化子を指定しない限り、次のようになります。
static char[1024] = { 1, 2, 3, 4, ... };
したがって、「機械語」(つまりCPU命令)に加えて、実行可能ファイルには必要なメモリレイアウトの説明が含まれています。
メモリはさまざまな方法で割り当てることができます。
ここでの質問は、「コンパイル時に割り当てられるメモリ」とは何かということです。間違いなく、それは単に間違った言い回しであり、バイナリセグメント割り当てまたはスタック割り当て、あるいは場合によってはヒープ割り当てのいずれかを指すことになっていますが、その場合、割り当ては目に見えないコンストラクター呼び出しによってプログラマーの目から隠されます。あるいは、メモリがヒープに割り当てられていないことを言いたかったが、スタックまたはセグメントの割り当てについて知らなかった(またはそのような詳細に立ち入りたくない)と言った人かもしれません。
しかし、ほとんどの場合、割り当てられているメモリの量はコンパイル時にわかっていると言いたいだけです。
バイナリサイズは、メモリがアプリのコードまたはデータセグメントで予約されている場合にのみ変更されます。
あなたが正しいです。メモリは、ロード時、つまり実行可能ファイルが(仮想)メモリに取り込まれたときに実際に割り当て(ページング)されます。その瞬間にメモリを初期化することもできます。コンパイラはメモリマップを作成するだけです。[ちなみに、スタックとヒープのスペースもロード時に割り当てられます!]
少し後退する必要があると思います。コンパイル時に割り当てられたメモリ....それはどういう意味ですか?まだ設計されていないコンピュータのために、まだ製造されていないチップ上のメモリが何らかの形で予約されていることを意味するのでしょうか?いいえ、タイムトラベルです。宇宙を操作できるコンパイラはありません。
したがって、コンパイラが実行時に何らかの方法でそのメモリを割り当てるための命令を生成することを意味する必要があります。しかし、それを直角から見ると、コンパイラーがすべての命令を生成するので、違いは何でしょうか。違いは、コンパイラが決定し、実行時にコードがその決定を変更または変更できないことです。コンパイル時、実行時に50バイトが必要であると判断した場合、60を割り当てることを決定することはできません。その決定はすでに行われています。
アセンブリプログラミングを学ぶと、データ、スタック、コードなどのセグメントを作成する必要があることがわかります。データセグメントは、文字列と数値が存在する場所です。コードセグメントは、コードが存在する場所です。これらのセグメントは、実行可能プログラムに組み込まれています。もちろん、スタックサイズも重要です...スタックオーバーフローは望ましくありません!
したがって、データセグメントが500バイトの場合、プログラムには500バイトの領域があります。データセグメントを1500バイトに変更すると、プログラムのサイズは1000バイト大きくなります。データは実際のプログラムに組み込まれます。
これは、高級言語をコンパイルするときに起こっていることです。実際のデータ領域は、実行可能プログラムにコンパイルされるときに割り当てられ、プログラムのサイズが大きくなります。プログラムはその場でメモリを要求することもでき、これは動的メモリです。RAMからメモリを要求すると、CPUがメモリを使用できるようにし、解放すると、ガベージコレクタがメモリをCPUに解放します。必要に応じて、優れたメモリマネージャによってハードディスクに交換することもできます。これらの機能は、高水準言語が提供するものです。
これらの概念をいくつかの図を使って説明したいと思います。
確かに、これはコンパイル時にメモリを割り当てることができないということです。しかし、実際にはコンパイル時に何が起こるのでしょうか。
ここに説明があります。たとえば、プログラムに4つの変数x、y、z、およびkがあるとします。これで、コンパイル時にメモリマップが作成され、これらの変数の相互の位置が確認されます。この図はそれをよりよく説明します。
ここで、メモリ内で実行されているプログラムがないことを想像してみてください。これは大きな空の長方形で示しています。
次に、このプログラムの最初のインスタンスが実行されます。次のように視覚化できます。これは、実際にメモリが割り当てられる時間です。
このプログラムの2番目のインスタンスが実行されている場合、メモリは次のようになります。
そして3番目..
などなど。
この視覚化がこの概念をうまく説明していることを願っています。
受け入れられた答えには非常に良い説明があります。念のため、役立つと思ったリンクを投稿します。 https://www.tenouk.com/ModuleW.html
Reba McEntire が息子の Shelby Blackstock と共有しているクリスマスの伝統について学びましょう。
メーガン・マークルとマライア・キャリーが自然な髪の上でどのように結合したかについて、メーガンの「アーキタイプ」ポッドキャストのエピソードで学びましょう.
ハリー王子が家族、特にチャールズ王とウィリアム王子との関係について望んでいると主張したある情報源を発見してください。
ワイノナ・ジャッドが、母親のナオミ・ジャッドが亡くなってから初めての感謝祭のお祝いを主催しているときに、彼女が今では家長であることをどのように認識したかを学びましょう.
Air travel is far more than getting from point A to point B safely. How much do you know about the million little details that go into flying on airplanes?
The world is a huge place, yet some GeoGuessr players know locations in mere seconds. Are you one of GeoGuessr's gifted elite? Take our quiz to find out!
ランボルギーニの創設者であるフェルッチオランボルギーニが100歳になるのは毎日ではありません(そうです、彼は死んでいて、まだ死んでいると思います。
Appleが自動車分野に参入するという噂はかなり前から渦巻いており、AppleウォッチャーがSixtyEight Researchという会社がAppleの自動車研究開発のシェル会社である可能性が高いと判断したとき、その渦巻きは本当に渦巻いた。また、会社が購入した車は1台だけであることが知られており、その車はAppleが何を考えているかについての手がかりでいっぱいになる可能性があることも伝えています。
太陽系の外側にある架空の大きな物体である惑星Xの探索は、何十年にもわたって人間を魅了してきました。その検索の最新の章は、地球の10倍の大きさで、公転周期が15であるほど遠くにある惑星を指しています。
カムニュートンは昨日、簡単な265ヤードと3回のタッチダウンでファルコンズを引き裂き、別の素晴らしいゲームをしました。その日のハイライトは、上のタッチダウンスローでした。これは、視聴するたびにばかげているだけです。
ロシアのフィギュアスケーター、カミラ・バリエバが関与したドーピング事件が整理されているため、チームは2022年北京冬季オリンピックで獲得したメダルを待っています。
何千人ものAmazonの買い物客がMulberry Silk Pillowcaseを推奨しており、現在販売中. シルクの枕カバーにはいくつかの色があり、髪を柔らかく肌を透明に保ちます。Amazonで最大46%オフになっている間にシルクの枕カバーを購入してください
ラファイエット警察署は、「不審な男性が女性に近づいた」という複数の苦情を受けて、12 月にパデュー大学の教授の捜査を開始しました。
私たちの周りの世界と同じように、言語は常に変化しています。以前の時代では、言語の変化は数年または数十年にわたって発生していましたが、現在では数日または数時間で変化する可能性があります。
認知症を患っている 91 歳のアジア人女性が最近、47 番街のアウター サンセット地区でロメオ ロレンゾ パーハムに襲われました。伝えられるところによると、被害者はサンフランシスコの通りを歩いていたところ、容疑者に近づき、攻撃を受け、暴行を受けました。
“And a river went out of Eden to water the garden, and from thence it was parted and became into four heads” Genesis 2:10. ? The heart is located in the middle of the thoracic cavity, pointing eastward.
人々にチャンスを与えることは、人生で少し遅すぎると私は信じています。寛大に。