アプリケーションで次のコードを使用していますが、正常に機能しています。しかし、mallocで作成するのが良いのか、それともそのままにしておくのが良いのだろうか?
function (int len)
{
char result [len] = some chars;
send result over network
}
アプリケーションで次のコードを使用していますが、正常に機能しています。しかし、mallocで作成するのが良いのか、それともそのままにしておくのが良いのだろうか?
function (int len)
{
char result [len] = some chars;
send result over network
}
回答:
主な違いは、VLA(可変長配列)が割り当てエラーを検出するメカニズムを提供しないことです。
宣言する場合
char result[len];
そして、len
使用可能なスタック領域の量を超えて、あなたのプログラムの動作は未定義です。割り当てが成功するかどうかを事前に判断するため、または成功したかどうかを事実の後に判断するための言語メカニズムはありません。
一方、次のように書く場合:
char *result = malloc(len);
if (result == NULL) {
/* allocation failed, abort or take corrective action */
}
障害を適切に処理するか、少なくとも障害後にプログラムが実行を継続しないことを保証できます。
(まあ、ほとんどのLinuxシステムでは、malloc()
利用可能な対応する記憶はありません場合でも、アドレス空間のチャンクを割り当てることができ、そのスペースを使用するために、後で試みは、呼び出すことができOOMキラーしかしためのチェック。malloc()
失敗はまだ良い習慣です。)
多くのシステムでの別の問題は、VLAのような自動オブジェクトよりも多くのスペース(場合によってはより多くのスペース)が利用できることですmalloc()
。
そして、フィリップの回答がすでに述べたように、VLAはC99に追加されました(特にMicrosoftはそれらをサポートしていません)。
また、VLAはC11でオプションになりました。おそらくほとんどのC11コンパイラーがそれらをサポートしますが、期待することはできません。
C99では、可変長の自動配列がCに導入されました。
古い標準との後方互換性について懸念がない限り、問題ありません。
一般に、それが機能する場合は、触れないでください。事前に最適化しないでください。特別な機能を追加したり、巧妙な方法で物事を実行したりすることを心配しないでください。複雑にしないでおく。
コンパイラが可変長配列をサポートしている場合、いくつかのシステムでlen
ばかげているほどスタックがオーバーフローすることが唯一の危険です。len
特定の数よりも大きくならないことが確実であり、最大長でもスタックがオーバーフローしないことがわかっている場合は、コードをそのままにしておきます。それ以外の場合は、でそれを書き換えるmalloc
とfree
。
char result [sizeof(char)]
はサイズの配列1
(sizeof(char)
1に等しいため)であるため、割り当ては切り捨てられsome chars
ます。
str
ポインターに減衰するため、sizeof
システムのポインターサイズに応じて4または8になります。
char* result = alloca(len);
、スタックに割り当てることができる場合があります。基本的な効果は同じ(および基本的な問題も同じ)
メモリの断片化、ダングリングポインターなどを使用せずに、実行時に割り当てられた配列を使用できるという考えが気に入っています。しかし、この実行時の割り当てが静かに失敗する可能性があることを指摘しています。だから、Cygwin bash環境でgcc 4.5.3を使用してこれを試しました:
#include <stdio.h>
#include <string.h>
void testit (unsigned long len)
{
char result [len*2];
char marker[100];
memset(marker, 0, sizeof(marker));
printf("result's size: %lu\n", sizeof(result));
strcpy(result, "this is a test that should overflow if no allocation");
printf("marker's contents: '%s'\n", marker);
}
int main(int argc, char *argv[])
{
testit(100);
testit((unsigned long)-1); // probably too big
}
出力は次のとおりです。
$ ./a.exe
result's size: 200
marker's contents: ''
result's size: 4294967294
marker's contents: 'should overflow if no allocation'
2回目の呼び出しで渡された長さが長すぎると、明らかに障害が発生しました(marker []にオーバーフローします)。これは、この種のチェックが絶対確実であることを意味するものではありません(愚か者は賢いこともあります!)、またはC99の基準を満たしているという意味ではありませんが、その懸念がある場合には役立つかもしれません。
いつものように、YMMV。
一般的に、スタックはデータを配置するのに最も簡単で最適な場所です。
予想される最大の配列を割り当てるだけで、VLAの問題を回避できます。
ただし、ヒープが最適であり、mallocをいじることが努力に値する場合があります。
まだ誰も言及していないことは、VLAの割り当てはスタックポインタを調整する場合(少なくともGCCでは)であるため、可変長配列オプションはおそらくmalloc / freeよりも大幅に高速になることです。
したがって、この関数が頻繁に呼び出される(もちろん、プロファイリングによって決定される)関数である場合、VLAは最適な最適化オプションです。
これは、私が助けになるかもしれない問題に使用する非常に一般的なCソリューションです。VLAとは異なり、病理学的なケースではスタックオーバーフローの実際的なリスクに直面しません。
/// Used for frequent allocations where the common case generally allocates
/// a small amount of memory, at which point a heap allocation can be
/// avoided, but rare cases also need to be handled which may allocate a
/// substantial amount. Note that this structure is not safe to copy as
/// it could potentially invalidate the 'data' pointer. Its primary use
/// is just to allow the stack to be used in common cases.
struct FastMem
{
/// Stores raw bytes for fast access.
char fast_mem[512];
/// Points to 'fast_mem' if the data fits. Otherwise, it will point to a
/// dynamically allocated memory address.
void* data;
};
/// @return A pointer to a newly allocated memory block of the specified size.
/// If the memory fits in the specified fast memory structure, it will use that
/// instead of the heap.
void* fm_malloc(struct FastMem* mem, int size)
{
// Utilize the stack if the memory fits, otherwise malloc.
mem->data = (size < sizeof mem->fast_mem) ? mem->fast_mem: malloc(size);
return mem->data;
}
/// Frees the specified memory block if it has been allocated on the heap.
void fm_free(struct FastMem* mem)
{
// Free the memory if it was allocated dynamically with 'malloc'.
if (mem->data != mem->fast_mem)
free(mem->data);
mem->data = 0;
}
あなたのケースでそれを使用するには:
struct FastMem fm;
// `result` will be allocated on the stack if 'len <= 512'.
char* result = fm_malloc(&fm, len);
// send result over network.
...
// this function will only do a heap deallocation if 'len > 512'.
fm_free(&fm, result);
上記の場合、これは、文字列が512バイト以下に収まる場合にスタックを使用します。それ以外の場合は、ヒープ割り当てを使用します。これは、たとえば99%の時間、文字列が512バイト以下に収まる場合に便利です。ただし、ユーザーがキーボードなどで眠り込んだ32キロバイトの文字列を処理する必要がある場合があります。これにより、両方の状況を問題なく処理できます。
生産の実際のバージョンIの使用はまた、独自のバージョンを持っているrealloc
し、calloc
などだけでなく、同じ概念上に構築された標準に準拠したC ++データ構造が、私は概念を説明するために最低限必要を抽出しました。
コピーするのは危険であり、割り当てられたポインターを返すべきではないという警告があります(FastMem
インスタンスが破棄されると無効になる可能性があります)。ローカル関数のスコープ内で、常にスタック/ VLAを使用するように誘惑される単純なケースに使用することを意図しています。そうしないと、まれにバッファ/スタックオーバーフローが発生する可能性があります。汎用のアロケーターではないため、そのまま使用しないでください。
実際には、C89を使用したレガシーコードベースで、ユーザーが2047文字を超える名前のアイテムに名前を付けることは不可能だと考えていた(キーボードで眠り込んだかもしれない)と以前のチームは考えていなかった状況に応じて、実際に作成しました)。私の同僚は実際にさまざまな場所に割り当てられた配列のサイズを16,384に増やしようとしましたが、その時点でばかげていると思い、バッファオーバーフローのリスクを減らす代わりにスタックオーバーフローのリスクを交換するだけでした。これにより、数行のコードを追加するだけで、これらのケースを修正するためのプラグインが非常に簡単なソリューションが提供されました。これにより、一般的なケースを非常に効率的に処理し、ヒープを必要とするクレイジーなまれなケースがなくてもスタックを利用できるため、ソフトウェアがクラッシュします。しかし、私は' VLAはスタックオーバーフローから保護できないため、C99以降でも有用であることがわかりました。これは、小さな割り当て要求のためにスタックからプールできます。
コールスタックは、常に限られています。LinuxやWindowsなどのメインストリームOSでは、制限は1メガバイトまたは数メガバイトです(変更する方法を見つけることができます)。一部のマルチスレッドアプリケーションでは、より小さなスタックでスレッドを作成できるため、これは低くなる可能性があります。組み込みシステムでは、数キロバイト程度の小ささになる可能性があります。目安としては、数キロバイトを超える呼び出しフレームを避けることです。
したがって、VLAを使用するのは、自分len
が十分に小さい(最大で数十万)と確信している場合にのみ意味があります。そうしないと、スタックオーバーフローが発生しますが、これは未定義の動作であり、非常に恐ろしい状況です。
ただし、手動のC動的メモリ割り当て(例:calloc
またはmalloc
&free
)を使用することには欠点もあります。
それが失敗することができますし、必要があり、常に失敗(例えばをテストcalloc
またはmalloc
返却しますNULL
)。
遅い:VLAの割り当てに成功するのに数ナノ秒かかり、成功malloc
するには数マイクロ秒(良い場合は、ほんの数分の1秒)またはそれ以上(スラッシングを伴う病理学的な場合、はるかに)が必要です。
コーディングがはるかに困難です。先free
の尖ったゾーンが使用されなくなったことが確実な場合にのみ可能です。あなたの場合、あなたは両方calloc
をfree
同じルーチンで呼び出すかもしれません。
ほとんどの場合result
(非常に貧弱な名前で、自動変数 VLAのアドレスを返すべきではないので、以下の代わりに使用してい ます)、特別な場合があります。buf
result
char tinybuf[256];
char *buf = (len<sizeof(tinybuf))?tinybuf:malloc(len);
if (!buf) { perror("malloc"); exit(EXIT_FAILURE); };
fill_buffer(buf, len);
send_buffer_on_network(buf, len);
if (buf != tinybuf)
free(buf);
ただし、上記のコードは読みにくく、おそらく最適化が時期尚早です。ただし、純粋なVLAソリューションよりも堅牢です。
PS。一部のシステム(たとえば、一部のLinuxディストリビューションではデフォルトで有効になっています)にはメモリのオーバーコミットがあります(メモリmalloc
が十分でない場合でも、ポインタを与えることがあります)。これは私が嫌いな機能で、通常はLinuxマシンでは無効にしています。