[c] C 동적으로 성장하는 배열

게임 내 엔터티의 “원시”목록을 읽는 프로그램이 있으며 다양한 것을 처리하기 위해 알 수없는 엔터티의 인덱스 번호 (int)를 보유하는 배열을 만들려고합니다. 그러한 색인을 유지하기 위해 너무 많은 메모리 또는 CPU를 사용하지 않으려합니다 …

내가 지금까지 사용하는 빠르고 더러운 솔루션은 주 처리 기능 (로컬 포커스)에서 최대 게임 엔터티 크기의 배열과 목록에 추가 된 수를 추적하는 또 다른 정수를 선언하는 것입니다. 모든 목록에 3000 개 이상의 배열이 포함되어 있기 때문에 만족스럽지 않습니다. 다양한 기능은 아니지만 6-7 목록에 대한 솔루션을 사용할 수 있기 때문에 낭비처럼 느껴집니다.

이것을 달성하기 위해 C (C ++ 또는 C #이 아닌) 특정 솔루션을 찾지 못했습니다. 포인터를 사용할 수 있지만 가능한 유일한 방법이 아니라면 포인터를 사용하는 것이 약간 두렵습니다.

배열은 변경되는 경우 로컬 함수 범위를 벗어나지 않습니다 (함수에 전달 된 후 폐기 됨).

포인터가 유일한 해결책이라면 누출을 피하기 위해 어떻게 추적 할 수 있습니까?



답변

포인터를 사용할 수는 있지만 포인터를 사용하는 것이 약간 두렵습니다.

동적 배열이 필요한 경우 포인터를 벗어날 수 없습니다. 그래도 왜 두려워? 그들은 물지 않습니다 (주의하는 한). 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 = 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 = 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);


답변

내가 생각할 수있는 몇 가지 옵션이 있습니다.

  1. 연결된 목록. 연결된 목록을 사용하여 동적으로 성장하는 배열을 만들 수 있습니다. 그러나 먼저 array[100]걸을 필요 없이는 할 수 없습니다 1-99. 그리고 당신이 사용하는 것이 그렇게 편리하지 않을 수도 있습니다.
  2. 큰 배열. 모든 것을위한 충분한 공간을 가진 어레이를 생성
  3. 배열 크기 조정 크기를 알면 어레이를 다시 생성하거나 여유 공간이 부족할 때마다 새 어레이를 만들고 모든 데이터를 새 어레이에 복사하십시오.
  4. 연결된 목록 배열 조합. 고정 크기의 배열을 사용하기 만하면 공간이 부족 해지면 새 배열을 생성하고 그 배열에 연결합니다 (구조에서 배열과 다음 배열에 대한 링크를 추적하는 것이 좋습니다).

상황에 가장 적합한 옵션을 말하기는 어렵습니다. 단순히 큰 배열을 만드는 것은 물론 가장 쉬운 솔루션 중 하나이며 실제로 크지 않으면 큰 문제를 일으키지 않아야합니다.


답변

처음보다 더 무서워 보이는 모든 것과 마찬가지로, 초기 두려움을 극복하는 가장 좋은 방법은 자신을 미지의 불쾌에 빠뜨리는 것입니다 ! 결국 우리가 가장 많이 배우는 것과 같은 시간입니다.

불행히도 한계가 있습니다. 여전히 기능 사용을 배우는 동안 예를 들어 교사의 역할을 맡아서는 안됩니다. 나는 종종 겉으로 사용하는 방법을 알고하지 않는 답변을 읽고 realloc(즉, 현재 허용 대답! ) 때때로 그들이 한 것으로 가장하여, 잘못을 사용하는 방법을 다른 사람을 말하는 오류 처리 생략 이 일반적인 함정에도 불구하고, 언급이 필요합니다. 올바르게 사용하는 방법을 설명하는 답변이 있습니다realloc . 답은 오류 검사를 수행하기 위해 반환 값을 다른 변수에 저장한다는 것입니다 .

함수를 호출 할 때마다 그리고 배열을 사용할 때마다 포인터를 사용합니다. 변환이 암묵적으로 발생하고 있는데, 어떤 것이 더 무서워 야한다면, 가장 큰 문제를 일으키는 것은 우리가 보지 못하는 것들이기 때문입니다. 예를 들어, 메모리 누수 …

배열 연산자는 포인터 연산자입니다. array[x]에 대한 바로 가기이며 *(array + x)다음 *과 같이 나눌 수 있습니다 (array + x). 이것이 *당신을 혼란스럽게 할 가능성이 높습니다 . 우리는 더 가정에 의한 문제에서 또한 제거 할 수 x수를 0, 따라서 array[0]이된다 *array추가하기 때문에 0값을 변경하지 않습니다 …

… 따라서 우리 *array는 이것과 같다는 것을 알 수 있습니다 array[0]. 다른 것을 사용하려는 곳을 사용할 수 있으며 그 반대도 가능합니다. 배열 연산자는 포인터 연산자입니다.

malloc, realloc친구가없는 발명 은 모두 함께 사용하고 포인터의 개념을; 그들은 단지 다른 기능을 구현 하기 위해 이것을 사용하는데 , 이것은 다른 형태의 저장 시간이며, 크기가 급격하고 역동적으로 변화하기 를 원할 때 가장 적합 합니다 .

현재 받아 들여진 대답 이 StackOverflow에 대한 다른 잘 알려진 조언에 위배되는 동시에 부끄러운 일이지만 동시에이 유스 케이스에 빛나는 거의 알려지지 않은 기능을 소개 할 기회가 없습니다 : 유연한 배열 회원! 그것은 실제로 꽤 깨진 대답입니다 … 🙁

를 정의 할 때 구조체 의 끝 에서 상한없이 struct배열 선언하십시오 . 예를 들면 다음과 같습니다.

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

이렇게하면 배열을와 int같은 할당으로 통합 할 count수 있으며 이와 같이 묶는 것이 매우 편리합니다 !

sizeof (struct int_list)value크기가 0 인 것처럼 작동 하므로 빈 목록을 가진 구조의 크기를 알려줍니다 . realloc목록의 크기를 지정하려면 전달 된 크기에 여전히 추가 해야합니다.

또 다른 유용한 팁은이 내용이에 해당함을 기억하는 것 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 **첫 번째 인수 로 사용하기 로 선택한 이유 는 즉시 명백하지 않을 수도 있지만 두 번째 인수에 대해 생각하면 value내부에서 변경 한 내용 push_back이 호출 한 함수에 표시되지 않습니다. 동일은 첫 번째 인수에 간다, 우리는 우리 수정할 수 있어야 array뿐만 아니라, 여기에 있지만 다른 기능도 가능하게 / 우리는 그것을 통과 s의

array아무것도 가리 키지 않고 시작합니다. 빈 목록입니다. 초기화 하는 것은 추가하는 것과 같습니다. 예를 들면 다음과 같습니다.

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

추신 당신이 그것으로 끝났을 때를 기억하십시오free(array); !


답변

바탕 마테오 Furlans 그가 말했을 때, 디자인 ” 새로운 요소를 추가 할 때 가장 역동적 인 배열 구현 일부 (작은) 기본 크기의 배열 오프 시작하여 작동, 당신은 배열의 크기를 두 배로, 공간이 부족할 때마다 “. 아래 ” 진행중인 작업 “의 차이점은 크기가 두 배가 아니라 필요한 것만 사용한다는 것입니다. 나는 또한 또한 구축 … 단순에 대한 안전 점검을 생략 한 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);
}

건설적인 비판 을 기대하고 …


답변

당신이 말할 때

불확실한 수의 엔티티의 색인 번호 (int)를 보유하는 배열을 만듭니다.

기본적으로 “포인터”를 사용하고 있지만 메모리 전체 포인터 대신 배열 전체 로컬 포인터입니다. 개념적으로 이미 “포인터”(예 : 배열의 요소를 나타내는 id 번호)를 사용하고 있기 때문에 정규 포인터 (즉, 가장 큰 배열의 요소를 나타내는 id 번호 : 전체 메모리) 만 사용하지 않는 이유는 무엇입니까? ).

자원 ID 번호를 저장하는 객체 대신 포인터를 저장하도록 만들 수 있습니다. 기본적으로는 똑같지 만 “배열 + 색인”을 “포인터”로 바꾸지 않기 때문에 훨씬 효율적입니다.

포인터를 전체 메모리의 배열 인덱스로 생각하면 무서운 것은 아닙니다 (실제로있는 것입니다)


답변

모든 유형의 무제한 항목 배열을 만들려면

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;
}

이 벡터 / 배열은 모든 유형의 항목을 보유 할 수 있으며 크기가 완전히 동적입니다.


답변

글쎄, 요소를 제거 해야하는 경우 제외 할 요소를 멸시하는 배열의 사본을 만들 것입니다.

// 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(...)임시 벡터를 처리하는 방법을 보조한다.