質問
C ではメモリをどのように管理するかに十分注意する必要があるといつも聞いてきました。私はまだ C の学習を始めていますが、これまでのところ、メモリ管理関連のアクティビティをまったく行う必要がありません。私は変数を解放して、あらゆる種類の醜いことをしなければならないことをいつも想像していました。しかし、そうではないようです。
誰かが「メモリ管理」を行う必要がある場合の例を(コード例とともに)見せてくれませんか?
解決
変数をメモリに格納できる場所は2つあります。このような変数を作成する場合:
int a;
char c;
char d[16];
変数は" stack "に作成されます。スタック変数は、スコープから外れると(つまり、コードが変数に到達できなくなると)自動的に解放されます。 「自動」と呼ばれる音が聞こえるかもしれません。変数ですが、それは時代遅れになりました。
多くの初心者の例では、スタック変数のみを使用します。
スタックは自動であるため優れていますが、次の2つの欠点もあります。(1)コンパイラーは、変数の大きさを事前に知る必要があります。(b)スタックスペースが多少制限されます。たとえば、Windowsの場合、Microsoftリンカーのデフォルト設定では、スタックは1 MBに設定されていますが、すべての変数が使用できるわけではありません。
コンパイル時に配列の大きさがわからない場合、または大きな配列または構造体が必要な場合は、「プランB」が必要です。
プランBは「ヒープ」と呼ばれます。通常、オペレーティングシステムで許容される限りの大きさの変数を作成できますが、自分で行う必要があります。他の方法もありますが、以前の投稿ではそれを行うことができる1つの方法を示しました。
int size;
// ...
// Set size to some value, based on information available at run-time. Then:
// ...
char *p = (char *)malloc(size);
(ヒープ内の変数は直接操作されるのではなく、ポインタを介して操作されることに注意してください)
一度ヒープ変数を作成すると、問題はコンパイラーがいつ終了したかを判断できないため、自動解放が失われることです。そこで、「マニュアルリリース」が行われます。参照していたコードが入ります。コードは、変数が不要になったときを決定し、メモリを他の目的に使用できるように解放する責任を負います。上記の場合:
free(p);
この2番目のオプションを「厄介なビジネス」にする理由変数が不要になったときを知るのは必ずしも簡単ではないということです。必要のないときに変数を解放するのを忘れると、プログラムが必要とするより多くのメモリを消費します。この状況は「リーク」と呼ばれます。 「リーク」プログラムが終了し、OSがすべてのリソースを回復するまで、メモリは何にも使用できません。ヒープ変数を実際に使用する前に誤って 解放すると、さらに厄介な問題が発生する可能性があります。
CおよびC ++では、上記のようなヒープ変数をクリーンアップする必要があります。ただし、JavaやC#などの.NET言語など、異なるアプローチを使用する言語や環境があり、ヒープ自体がクリーンアップされます。 「ガベージコレクション」と呼ばれるこの2番目の方法は、開発者にとってはるかに簡単ですが、オーバーヘッドとパフォーマンスにペナルティがかかります。バランスです。
(私は多くの詳細をよりシンプルに説明するためにつやを上げましたが、できればより平等な答えを提供します)
他のヒント
例を次に示します。文字列を複製するstrdup()関数があるとします:
char *strdup(char *src)
{
char * dest;
dest = malloc(strlen(src) + 1);
if (dest == NULL)
abort();
strcpy(dest, src);
return dest;
}
そして、次のように呼び出します:
main()
{
char *s;
s = strdup("hello");
printf("%s\n", s);
s = strdup("world");
printf("%s\n", s);
}
プログラムが機能していることがわかりますが、メモリを解放せずに(mallocを介して)割り当てています。 2回目にstrdupを呼び出したときに、最初のメモリブロックへのポインタが失われました。
これはこの少量のメモリでは大したことではありませんが、ケースを考慮してください:
for (i = 0; i < 1000000000; ++i) /* billion times */
s = strdup("hello world"); /* 11 bytes */
これで11ギガのメモリを使用しました(メモリマネージャに応じて、おそらくそれ以上)。クラッシュしていない場合、プロセスの実行速度はおそらくかなり遅いです。
修正するには、malloc()を使用して取得したすべてのものについて、使用終了後にfree()を呼び出す必要があります。
s = strdup("hello");
free(s); /* now not leaking memory! */
s = strdup("world");
...
この例がお役に立てば幸いです!
「メモリ管理」を行う必要があります;スタックではなくヒープ上のメモリを使用する場合。実行時まで配列を作成する大きさがわからない場合は、ヒープを使用する必要があります。たとえば、何かを文字列に格納したいが、プログラムが実行されるまでその内容がどれだけ大きくなるかわからないことがあります。その場合、次のように記述します。
char *string = malloc(stringlength); // stringlength is the number of bytes to allocate
// Do something with the string...
free(string); // Free the allocated memory
Cのポインターの役割を検討するための質問に答える最も簡潔な方法だと思います。ポインターは軽量でありながら強力なメカニズムであり、足を踏み入れるための莫大な能力を犠牲にします。
Cでは、ポインタが自分が所有するメモリを指すようにする責任は、あなたとあなただけのものです。これには、ポインターを捨てない限り、組織化された規律のとれたアプローチが必要です。これにより、効果的なCの記述が難しくなります。
日付に対する投稿された回答は、自動(スタック)およびヒープ変数の割り当てに集中しています。スタック割り当てを使用すると、自動的に管理された便利なメモリが作成されますが、状況によっては(大きなバッファ、再帰アルゴリズム)、スタックオーバーフローという恐ろしい問題につながる可能性があります。スタックに割り当てることができるメモリの量を正確に知ることは、システムに大きく依存しています。一部の組み込みシナリオでは数十バイトが制限になる場合があり、一部のデスクトップシナリオではメガバイトを安全に使用できます。
ヒープ割り当ては言語固有のものではありません。基本的に、返される(「空き」)準備ができるまで、指定されたサイズのメモリブロックの所有権を付与するライブラリ呼び出しのセットです。単純に聞こえますが、未だにプログラマーの悲嘆に関連しています。問題は単純です(同じメモリを2回解放するか、まったくメモリリークが発生せず、十分なメモリを割り当てない[バッファオーバーフロー]など)が、回避およびデバッグは困難です。高度に統制されたアプローチは、実際には絶対に必須ですが、もちろん言語は実際にそれを強制しません。
他の投稿で無視されている別の種類のメモリ割り当てについて言及したいと思います。関数の外部で変数を宣言することで、変数を静的に割り当てることができます。一般に、このタイプの割り当ては、グローバル変数によって使用されるため、悪いラップになると思います。ただし、この方法で割り当てられたメモリを使用する唯一の方法は、大量のスパゲッティコードでの規律のないグローバル変数としてであると言うものはありません。静的割り当て方法は、ヒープおよび自動割り当て方法の落とし穴の一部を回避するためだけに使用できます。一部のCプログラマーは、大きくて洗練されたC組み込みおよびゲームプログラムが、ヒープ割り当てをまったく使用せずに構築されたことを知って驚いています。
メモリの割り当てと解放の方法については、ここに素晴らしい答えがいくつかあります。私の意見では、C を使用する際のより難しい側面は、使用するメモリが割り当てられたメモリだけであることを確認することです。これが正しく行われなかった場合、どうなるでしょうか。 up with はこのサイトの一種であるバッファ オーバーフローであり、別のアプリケーションで使用されているメモリを上書きして、非常に予測不可能な結果を引き起こす可能性があります。
例:
int main() {
char* myString = (char*)malloc(5*sizeof(char));
myString = "abcd";
}
この時点で、myString に 5 バイトを割り当て、「abcd\0」を入力しました (文字列は null - \0 で終わります)。文字列割り当てが
myString = "abcde";
プログラムに割り当てた 5 バイトに「abcde」を割り当てることになり、末尾のヌル文字がこの末尾に置かれることになります。これは、使用するために割り当てられておらず、メモリの一部である可能性があります。無料ですが、別のアプリケーションによって同様に使用される可能性があります。これはメモリ管理の重要な部分であり、間違いが予測できない (場合によっては再現不可能な) 結果をもたらす可能性があります。
覚えておくべきことは、ポインタを常にNULLに初期化することです。初期化されていないポインタには、疑似エラーの有効なメモリアドレスが含まれている場合があり、ポインタエラーを静かに進めることができます。ポインターを強制的にNULLで初期化することにより、初期化せずにこのポインターを使用している場合は常にキャッチできます。その理由は、オペレーティングシステムが「ワイヤー」であるためです。仮想アドレス0x00000000から一般保護例外にNULLポインターの使用をトラップします。
また、int [10000]などの巨大な配列を定義する必要がある場合は、動的メモリ割り当てを使用することもできます。スタックに入れることはできません。そのため、スタックオーバーフローが発生します。
もう1つの良い例は、リンクリストやバイナリツリーなどのデータ構造の実装です。ここに貼り付けるサンプルコードはありませんが、簡単にグーグルで検索できます。
(これまでのところ答えがあまりよくないように感じているので書いています。)
言及する価値のあるメモリ管理が必要な理由は、複雑な構造を作成する必要がある問題/解決策がある場合です。 (一度にスタックの多くのスペースに割り当てるとプログラムがクラッシュする場合、それはバグです。)通常、最初に学習する必要があるデータ構造は、ある種のリスト。これは私の頭上にある、リンクされた単一のものです:
typedef struct listelem { struct listelem *next; void *data;} listelem;
listelem * create(void * data)
{
listelem *p = calloc(1, sizeof(listelem));
if(p) p->data = data;
return p;
}
listelem * delete(listelem * p)
{
listelem next = p->next;
free(p);
return next;
}
void deleteall(listelem * p)
{
while(p) p = delete(p);
}
void foreach(listelem * p, void (*fun)(void *data) )
{
for( ; p != NULL; p = p->next) fun(p->data);
}
listelem * merge(listelem *p, listelem *q)
{
while(p != NULL && p->next != NULL) p = p->next;
if(p) {
p->next = q;
return p;
} else
return q;
}
当然、他にもいくつかの機能が必要ですが、基本的にこれはメモリ管理に必要なものです。 「手動」で可能なトリックがいくつかあることを指摘する必要があります。メモリ管理、例:
優れたデバッガを入手してください... がんばってください!
@ ユーロミチェリ
追加すべきマイナス点の1つは、関数への戻り時にスタックへのポインターが無効になるため、関数からスタック変数へのポインターを返すことができないことです。これは一般的なエラーであり、スタック変数だけではうまくいかない主な理由です。関数がポインターを返す必要がある場合は、mallocしてメモリ管理を処理する必要があります。
@ テッドパーシバル:
... malloc()の戻り値をキャストする必要はありません。
もちろん、あなたは正しいです。 K&amp; R が確認します。
Cの暗黙的な変換の多くは好きではないので、キャストを使用して「魔法」を作成する傾向があります。より目に見える。読みやすくする場合もあれば、そうでない場合もあります。また、サイレントバグがコンパイラーにキャッチされることもあります。それでも、私はこれについて強い意見はありません。何らかの形で。
これは、コンパイラがC ++スタイルのコメントを理解している場合に特に起こります。
ええ...あなたは私を捕まえました。私はCよりもC ++で多くの時間を過ごしています。それに気づいてくれてありがとう。
Cでは、実際には2つの異なる選択肢があります。 1つは、システムにメモリを管理させることができます。または、それを自分で行うこともできます。一般的に、できるだけ前者に固執したいと思うでしょう。ただし、Cの自動管理メモリは非常に限られているため、次のような多くの場合、メモリを手動で管理する必要があります。
a。変数は関数よりも長持ちし、グローバル変数は必要ありません。例:
struct pair{ int val; struct pair *next; } struct pair* new_pair(int val){ struct pair* np = malloc(sizeof(struct pair)); np->val = val; np->next = NULL; return np; }
b。動的にメモリを割り当てたい場合。最も一般的な例は、固定長のない配列です:
int *my_special_array; my_special_array = malloc(sizeof(int) * number_of_element); for(i=0; ic. You want to do something REALLY dirty. For example, I would want a struct to represent many kind of data and I don't like union (union looks soooo messy):
struct data{ int data_type; long data_in_mem; }; struct animal{/*something*/}; struct person{/*some other thing*/}; struct animal* read_animal(); struct person* read_person(); /*In main*/ struct data sample; sampe.data_type = input_type; switch(input_type){ case DATA_PERSON: sample.data_in_mem = read_person(); break; case DATA_ANIMAL: sample.data_in_mem = read_animal(); default: printf("Oh hoh! I warn you, that again and I will seg fault your OS"); }
参照、ANYTHINGを保持するには長い値で十分です。それを解放することを忘れないでください、さもなければ後悔します。これは、C:Dで楽しむための私のお気に入りのトリックの1つです。
ただし、一般的に、お気に入りのトリック(T___T)には近づかないようにします。 OSを頻繁に使用すると、遅かれ早かれ、OSが壊れます。 * allocとfreeを使用しない限り、あなたはまだ処女であり、コードはまだ見栄えが良いと言っても安全です。
もちろん。スコープの外側に存在するオブジェクトを作成する場合、それを使用します。ここに、不自然な例があります(構文がオフになることに注意してください。私のCはさびていますが、この例でも概念を説明します)。
class MyClass
{
SomeOtherClass *myObject;
public MyClass()
{
//The object is created when the class is constructed
myObject = (SomeOtherClass*)malloc(sizeof(myObject));
}
public ~MyClass()
{
//The class is destructed
//If you don't free the object here, you leak memory
free(myObject);
}
public void SomeMemberFunction()
{
//Some use of the object
myObject->SomeOperation();
}
};
この例では、MyClassの存続期間中にSomeOtherClass型のオブジェクトを使用しています。 SomeOtherClassオブジェクトはいくつかの関数で使用されるため、メモリを動的に割り当てました:MyClassの作成時にSomeOtherClassオブジェクトが作成され、オブジェクトの存続期間中に数回使用され、MyClassが解放されると解放されます。
明らかにこれが実際のコードである場合、この方法でmyObjectを作成する理由はありません(スタックメモリの消費は別として)が、このタイプのオブジェクトの作成/破棄は、多くのオブジェクトがあり、それらが作成および破棄されるタイミングを細かく制御するため(たとえば、アプリケーションがそのライフタイム全体にわたって1GBのRAMを消費しないようにするため)、ウィンドウ環境では、これは作成するオブジェクト(ボタン、たとえば)、特定の関数(またはクラス)のスコープ外に存在する必要があります。