web-dev-qa-db-ja.com

C動的に成長する配列

ゲーム内エンティティの「生の」リストを読み取るプログラムがあり、さまざまなものを処理するために、不定の数のエンティティのインデックス番号(int)を保持する配列を作成する予定です。このようなインデックスを保持するためにメモリやCPUを使いすぎないようにしたい...

私がこれまで使用した迅速で汚い解決策は、メイン処理関数(ローカルフォーカス)で、最大ゲームエンティティのサイズの配列と、リストに追加された数を追跡する別の整数を宣言することです。すべてのリストが3000+配列を保持しているため、これは満足のいくものではありませんが、それほど多くはありませんが、さまざまな機能に6〜7個のリストのソリューションを使用できるため、無駄に感じます。

これを実現するためのC(C++またはC#ではない)固有のソリューションは見つかりませんでした。ポインターを使用することはできますが、ポインターを使用するのが少し怖いです(唯一の方法でない限り)。

配列は、物事が変化した場合に備えて、ローカル関数スコープを離れません(関数に渡されてから破棄されます)。

ポインターが唯一のソリューションである場合、リークを避けるためにポインターを追跡するにはどうすればよいですか?

115
Balkania

ポインターを使用できますが、使用するのが少し怖いです。

動的配列が必要な場合、ポインターをエスケープすることはできません。どうして怖いの?彼らは噛み付きません(あなたが注意している限り、そうです)。 Cには組み込みの動的配列はありません。自分で作成する必要があります。 C++では、組み込みの std::vector クラスを使用できます。 C#および他のすべての高級言語にも、動的配列を管理する類似のクラスがいくつかあります。

独自のコードを作成する予定がある場合は、ここから始めましょう。ほとんどの動的配列の実装は、いくつかの(小さな)デフォルトサイズの配列から開始し、新しい要素を追加するときにスペースを使い果たすと、配列のサイズ。次の例でわかるように、それほど難しくありません:(簡潔にするために安全性チェックを省略しました)

typedef struct {
  int *array;
  size_t used;
  size_t size;
} Array;

void initArray(Array *a, size_t initialSize) {
  a->array = (int *)malloc(initialSize * sizeof(int));
  a->used = 0;
  a->size = initialSize;
}

void insertArray(Array *a, int element) {
  // a->used is the number of used entries, because a->array[a->used++] updates a->used only *after* the array has been accessed.
  // Therefore a->used can go up to a->size 
  if (a->used == a->size) {
    a->size *= 2;
    a->array = (int *)realloc(a->array, a->size * sizeof(int));
  }
  a->array[a->used++] = element;
}

void freeArray(Array *a) {
  free(a->array);
  a->array = NULL;
  a->used = a->size = 0;
}

それを使用するのは簡単です:

Array a;
int i;

initArray(&a, 5);  // initially 5 elements
for (i = 0; i < 100; i++)
  insertArray(&a, i);  // automatically resizes as necessary
printf("%d\n", a.array[9]);  // print 10th element
printf("%d\n", a.used);  // print number of elements
freeArray(&a);
187
casablanca

考えられる選択肢がいくつかあります。

  1. リンクリスト。リンクリストを使用して、動的に成長する配列を作成できます。ただし、最初にarray[100]を実行することなく1-99を実行することはできません。そして、どちらを使用するのもそれほど便利ではないかもしれません。
  2. 大きな配列。すべてに十分なスペースがあるアレイを作成するだけです
  3. 配列のサイズ変更。サイズがわかったらアレイを再作成し、マージンを確保してスペースがなくなるたびに新しいアレイを作成し、すべてのデータを新しいアレイにコピーします。
  4. リンクリスト配列の組み合わせ。単純に固定サイズの配列を使用し、スペースがなくなったら、新しい配列を作成してリンクします(構造体の配列と次の配列へのリンクを追跡するのが賢明です)。

どのオプションがあなたの状況に最適であると言うのは難しいです。もちろん、単純に大きな配列を作成することは、最も簡単な解決策の1つであり、本当に大きくない限り、多くの問題を引き起こすことはありません。

10
Wolph

最初は恐ろしいと思われるすべてのものと同様に、最初の恐怖を乗り越える最善の方法は、未知の不快感に没頭することです !結局、それは私たちが最も多く学ぶもののようなものです。

残念ながら、制限があります。あなたはまだ関数を使うことを学んでいますが、例えば教師の役割を引き受けるべきではありません。私はよくreallocの使用方法がわからない人(つまり、現在受け入れられている回答!)の答えを読んでいます。 エラー処理の省略、これは言及する必要がある一般的な落とし穴ですが。 これはreallocを正しく使用する方法を説明する回答ですエラーチェックを実行するために、返り値がdifferent変数に戻り値を格納していることに注意してください。

関数を呼び出すたびに、また配列を使用するたびに、ポインターを使用しています。変換は暗黙的に行われます。これは、最も恐ろしい原因となることが多いので見られないため、さらに恐ろしいことになります。たとえば、メモリリーク...

配列演算子はポインター演算子です。 array[x]は実際には*(array + x)のショートカットであり、*(array + x)に分解できます。 *が混乱を招く可能性が高いです。 x0と仮定することで、問題から追加をさらに排除できます。したがって、array[0]を追加しても値が変更されないため、*array0になります。

...したがって、*arrayarray[0]と同等であることがわかります。使用したい場所で一方を使用できます。逆の場合も同様です。配列演算子はポインター演算子です。

mallocreallocおよび友人はinventあなたがずっと使用してきたポインターの概念をしません。それらは単にseこれを他の機能を実装するために使用します。これは異なる期間のストレージ期間であり、サイズの劇的で動的な変更を望むときに最適です。

現在受け入れられている答えまたStackOverflowに関する他の非常に根拠のあるアドバイス のグレインに反し、同時に導入する機会を逃すのは残念ですこのユースケースにぴったりのあまり知られていない機能:柔軟な配列メンバー!それは実際にはかなり壊れている答えです... :(

structを定義するときは、構造の配列末尾を、上限なしで宣言します。例えば:

struct int_list {
    size_t size;
    int value[];
};

これにより、intの配列をcountと同じ割り当てに結合できます。このようにバインドすると、非常に便利になります。

sizeof (struct int_list)valueのサイズが0であるかのように動作するため、構造のサイズ空のリストがわかります。リストのサイズを指定するには、reallocに渡されるサイズに追加する必要があります。

もう1つの便利なヒントは、realloc(NULL, x)malloc(x)と同等であり、これを使用してコードを簡素化できることを思い出してください。例えば:

int Push_back(struct int_list **fubar, int value) {
    size_t x = *fubar ? fubar[0]->size : 0
         , y = x + 1;

    if ((x & y) == 0) {
        void *temp = realloc(*fubar, sizeof **fubar
                                   + (x + y) * sizeof fubar[0]->value[0]);
        if (!temp) { return 1; }
        *fubar = temp; // or, if you like, `fubar[0] = temp;`
    }

    fubar[0]->value[x] = value;
    fubar[0]->size = y;
    return 0;
}

struct int_list *array = NULL;

struct int_list **を最初の引数として使用することを選択した理由はすぐには明らかではないかもしれませんが、2番目の引数について考えると、Push_back内からvalueに加えられた変更は、呼び出し元の関数には表示されません、 右?同じことが最初の引数にも当てはまり、ここだけでなくここでだけでなく、おそらくそれを渡す他の関数/ sでも)arrayを変更できる必要があります。 _...

arrayは何も指さずに開始します。空のリストです。 _ {(初期化これは追加するのと同じです。例えば:

struct int_list *array = NULL;
if (!Push_back(&array, 42)) {
    // success!
}

追伸完了したら、free(array);を忘れないでください!

8
autistic

あなたが言っているとき

不定の数のエンティティのインデックス番号(int)を保持する配列を作成します

基本的に「ポインター」を使用していると言っていますが、これはメモリ全体のポインターではなく、配列全体のローカルポインターです。概念的には既に「ポインター」(つまり、配列内の要素を参照するID番号)を使用しているので、通常のポインター(つまり、最大の配列内の要素を参照するID番号:メモリ全体)を使用しないのはなぜですか)。

オブジェクトにリソースID番号を保存する代わりに、代わりにポインターを保存させることができます。基本的には同じことですが、「配列+インデックス」を「ポインター」に変えることを避けるため、はるかに効率的です。

ポインタをメモリ全体の配列インデックスと考える場合、それは怖くありません(実際はポインタです)

2
Lie Ryan

Matteo Furlans designで構築し、「ほとんどの動的配列実装は、いくつかの(小さな)デフォルトサイズの配列から始めて、次に実行するたびに動作します」新しい要素を追加するときのスペースの2倍、配列のサイズ)。以下の "work in progress"の違いは、サイズが2倍にならず、必要なもののみを使用することを目的としていることです。また、簡単にするために安全性チェックも省略しています... brimboriumsという考えに基づいて、コードに削除機能を追加しようとしました...

Storage.hファイルは次のようになります...

#ifndef STORAGE_H
#define STORAGE_H

#ifdef __cplusplus
extern "C" {
#endif

    typedef struct 
    {
        int *array;
        size_t size;
    } Array;

    void Array_Init(Array *array);
    void Array_Add(Array *array, int item);
    void Array_Delete(Array *array, int index);
    void Array_Free(Array *array);

#ifdef __cplusplus
}
#endif

#endif /* STORAGE_H */

Storage.cファイルは次のようになります...

#include <stdio.h>
#include <stdlib.h>
#include "storage.h"

/* Initialise an empty array */
void Array_Init(Array *array) 
{
    int *int_pointer;

    int_pointer = (int *)malloc(sizeof(int));

    if (int_pointer == NULL)
    {       
        printf("Unable to allocate memory, exiting.\n");
        free(int_pointer);
        exit(0);
    }
    else
    {
        array->array = int_pointer; 
        array->size = 0;
    }
}

/* Dynamically add to end of an array */
void Array_Add(Array *array, int item) 
{
    int *int_pointer;

    array->size += 1;

    int_pointer = (int *)realloc(array->array, array->size * sizeof(int));

    if (int_pointer == NULL)
    {       
        printf("Unable to reallocate memory, exiting.\n");
        free(int_pointer);
        exit(0);
    }
    else
    {
        array->array = int_pointer;
        array->array[array->size-1] = item;
    }
}

/* Delete from a dynamic array */
void Array_Delete(Array *array, int index) 
{
    int i;
    Array temp;
    int *int_pointer;

    Array_Init(&temp);

    for(i=index; i<array->size; i++)
    {
        array->array[i] = array->array[i + 1];
    }

    array->size -= 1;

    for (i = 0; i < array->size; i++)
    {
        Array_Add(&temp, array->array[i]);
    }

    int_pointer = (int *)realloc(temp.array, temp.size * sizeof(int));

    if (int_pointer == NULL)
    {       
        printf("Unable to reallocate memory, exiting.\n");
        free(int_pointer);
        exit(0);
    }
    else
    {
        array->array = int_pointer; 
    } 
}

/* Free an array */
void Array_Free(Array *array) 
{
  free(array->array);
  array->array = NULL;
  array->size = 0;  
}

Main.cは次のようになります...

#include <stdio.h>
#include <stdlib.h>
#include "storage.h"

int main(int argc, char** argv) 
{
    Array pointers;
    int i;

    Array_Init(&pointers);

    for (i = 0; i < 60; i++)
    {
        Array_Add(&pointers, i);        
    }

    Array_Delete(&pointers, 3);

    Array_Delete(&pointers, 6);

    Array_Delete(&pointers, 30);

    for (i = 0; i < pointers.size; i++)
    {        
        printf("Value: %d Size:%d \n", pointers.array[i], pointers.size);
    }

    Array_Free(&pointers);

    return (EXIT_SUCCESS);
}

建設的な批判を楽しみにしています...

1
user1687619

さて、要素を削除する必要がある場合、除外する要素を軽deする配列のコピーを作成すると思います。

// inserting some items
void* element_2_remove = getElement2BRemove();

for (int i = 0; i < vector->size; i++){
       if(vector[i]!=element_2_remove) copy2TempVector(vector[i]);
       }

free(vector->items);
free(vector);
fillFromTempVector(vector);
//

getElement2BRemove()copy2TempVector( void* ...)、およびfillFromTempVector(...)は、一時ベクトルを処理するための補助メソッドであると想定します。

任意の種類の無制限のアイテムの配列を作成するには:

typedef struct STRUCT_SS_VECTOR {
    size_t size;
    void** items;
} ss_vector;


ss_vector* ss_init_vector(size_t item_size) {
    ss_vector* vector;
    vector = malloc(sizeof(ss_vector));
    vector->size = 0;
    vector->items = calloc(0, item_size);

    return vector;
}

void ss_vector_append(ss_vector* vec, void* item) {
    vec->size++;
    vec->items = realloc(vec->items, vec->size * sizeof(item));
    vec->items[vec->size - 1] = item;
};

void ss_vector_free(ss_vector* vec) {
    for (int i = 0; i < vec->size; i++)
        free(vec->items[i]);

    free(vec->items);
    free(vec);
}

そして、それを使用する方法:

// defining some sort of struct, can be anything really
typedef struct Apple_STRUCT {
    int id;
} Apple;

Apple* init_Apple(int id) {
    Apple* a;
    a = malloc(sizeof(Apple));
    a-> id = id;
    return a;
};


int main(int argc, char* argv[]) {
    ss_vector* vector = ss_init_vector(sizeof(Apple));

    // inserting some items
    for (int i = 0; i < 10; i++)
        ss_vector_append(vector, init_Apple(i));


    // dont forget to free it
    ss_vector_free(vector);

    return 0;
}

このベクター/配列はあらゆる種類のアイテムを保持でき、サイズは完全に動的です。

0