配列またはMalloc?


12

アプリケーションで次のコードを使用していますが、正常に機能しています。しかし、mallocで作成するのが良いのか、それともそのままにしておくのが良いのだろうか?

function (int len)
{
char result [len] = some chars;
send result over network
}

2
コードが非埋め込み環境を対象としているという仮定はありますか?
tehnyit

回答:


27

主な違いは、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コンパイラーがそれらをサポートしますが、期待することはできません。


14

C99では、可変長の自動配列がCに導入されました。

古い標準との後方互換性について懸念がない限り、問題ありません。

一般に、それが機能する場合は、触れないでください。事前に最適化しないでください。特別な機能を追加したり、巧妙な方法で物事を実行したりすることを心配しないでください。複雑にしないでおく。


7
私は、「それが機能するのであれば、触らないでください」という言葉に反対しなければなりません。一部のコードが「機能する」と誤って信じることにより、「機能する」コードの問題を回避できる場合があります。信念は、一部のコードが現在機能しているという暫定的な受け入れに置き換えなければなりません。
ブルースエディガー

2
バージョンを「うまく」動作させるまでは触れないでください。
H_7

8

コンパイラが可変長配列をサポートしている場合、いくつかのシステムでlenばかげているほどスタックがオーバーフローすることが唯一の危険です。len特定の数よりも大きくならないことが確実であり、最大長でもスタックがオーバーフローしないことがわかっている場合は、コードをそのままにしておきます。それ以外の場合は、でそれを書き換えるmallocfree


これはc99以外の関数(char []){char result [sizeof(char)] = some chars; ネットワーク経由で結果を送信}
開発バッグ

@DevBag char result [sizeof(char)]はサイズの配列1sizeof(char)1に等しいため)であるため、割り当ては切り捨てられsome charsます。
-dasblinkenlight

申し訳ありませんが、私はこのように意味していますfunction(char str []){char result [sizeof(str)] = some chars; ネットワーク経由で結果を送信}
開発バッグ

4
@DevBagこれも機能しません- str ポインター減衰するため、sizeofシステムのポインターサイズに応じて4または8になります。
-dasblinkenlight

2
可変長配列のないバージョンのCを使用している場合はchar* result = alloca(len);、スタックに割り当てることができる場合があります。基本的な効果は同じ(および基本的な問題も同じ)
ロボット

6

メモリの断片化、ダングリングポインターなどを使用せずに、実行時に割り当てられた配列を使用できるという考えが気に入っています。しかし、この実行時の割り当てが静かに失敗する可能性があることを指摘しています。だから、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。


1
+1これは非常に便利です:3
Kokizzu 14年

人々の主張に沿ったコードを用意しておくのはいつでもいいことです!ありがとう^ _ ^
ムーサ

3

一般的に、スタックはデータを配置するのに最も簡単で最適な場所です。

予想される最大の配列を割り当てるだけで、VLAの問題を回避できます。

ただし、ヒープが最適であり、mallocをいじることが努力に値する場合があります。

  1. データ量が多いが変動する場合。大規模は、環境によって異なります>組み込みシステムでは1K、エンタープライズサーバーでは10MB。
  2. ルーチンを終了した後、データへのポインターを返す場合など、データを保持したい場合。を使用して
  3. 通常、静的ポインターとmalloc()の組み合わせは、大きな静的配列を定義するよりも優れています。

3

組み込みプログラミングでは、malloc操作とfree操作が頻繁に行われる場合、mallocではなく常に静的配列を使用します。組み込みシステムではメモリ管理が不足しているため、頻繁なallocおよびfree操作によりメモリフラグメントが発生します。ただし、配列の最大サイズを定義したり、静的ローカル配列を使用したりするなど、いくつかのトリッキーな方法を利用する必要があります。

アプリケーションがLinuxまたはWindowsで実行されている場合、arrayまたはmallocを使用しても問題ありません。重要な点は、日付構造とコードロジックを使用する場所にあります。


1

まだ誰も言及していないことは、VLAの割り当てはスタックポインタを調整する場合(少なくともGCCでは)であるため、可変長配列オプションはおそらくmalloc / freeよりも大幅に高速になることです。

したがって、この関数が頻繁に呼び出される(もちろん、プロファイリングによって決定される)関数である場合、VLAは最適な最適化オプションです。


1
スタックスペース不足の状況に陥るまで、それは良いように思えます。さらに、実際にスタック制限達するのはコードではない場合があります。ライブラリまたはシステムコール(または割り込み)に噛み付く可能性があります。
ドナルドフェローズ

@Donalパフォーマンスは、常にメモリと速度のトレードオフです。数メガバイトの配列の割り当てを一巡する場合、関数が再帰的でない限り、数キロバイトでもポイントがあります。これは最適化です。
JeremyP

1

これは、私が助けになるかもしれない問題に使用する非常に一般的な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以降でも有用であることがわかりました。これは、小さな割り当て要求のためにスタックからプールできます。


1

コールスタックは、常に限られています。LinuxやWindowsなどのメインストリームOSでは、制限は1メガバイトまたは数メガバイトです(変更する方法を見つけることができます)。一部のマルチスレッドアプリケーションでは、より小さなスタックでスレッドを作成できるため、これは低くなる可能性があります。組み込みシステムでは、数キロバイト程度の小ささになる可能性があります。目安としては、数キロバイトを超える呼び出しフレームを避けることです。

したがって、VLAを使用するのは、自分lenが十分に小さい(最大で数十万)と確信している場合にのみ意味があります。そうしないと、スタックオーバーフローが発生ますが、これは未定義の動作であり、非常に恐ろしい状況です。

ただし、手動のC動的メモリ割り当て(例:callocまたはmallocfree)を使用することには欠点もあります。

  • それが失敗することができますし、必要があり、常に失敗(例えばをテストcallocまたはmalloc返却しますNULL)。

  • 遅い:VLAの割り当てに成功するのに数ナノ秒かかり、成功mallocするには数マイクロ秒(良い場合は、ほんの数分の1秒)またはそれ以上(スラッシングを伴う病理学的な場合、はるかに)が必要です。

  • コーディングがはるかに困難です。先freeの尖ったゾーンが使用されなくなったことが確実な場合にのみ可能です。あなたの場合、あなたは両方callocfree同じルーチンで呼び出すかもしれません。

ほとんどの場合result (非常に貧弱な名前で、自動変数 VLAのアドレスを返すべきではないので、以下の代わりに使用してい ます)、特別な場合があります。bufresult

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マシンでは無効にしています。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.