C 동적으로 성장하는 배열


126

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

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

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

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

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


1
이것은 C에서 (매우 작은) 문제이지만 어떻게 모든 C ++ 및 C # 솔루션을 놓쳤습니까?
Ignacio Vazquez-Abrams

11
"포인터가 유일한 해결책이라면, 누출을 피하기 위해 어떻게 추적 할 수 있습니까?" 관리,주의 및 valgrind. 이것이 바로 C가 처음에 사람들이 그렇게 두려워하는 이유입니다.
Chris Lutz

27
포인터를 사용하지 않으면 C를 효과적으로 사용할 수 없습니다. 두려워하지 마십시오.
qrdl

큰 라이브러리가 없으면 구조체에 대한 하나의 함수 만 있습니다. 예 : stackoverflow.com/questions/3456446/…
user411313

6
포인터없이 C를 사용하는 것은 연료가없는 자동차를 사용하는 것과 같습니다.
martinkunev

답변:


210

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

동적 배열이 필요한 경우 포인터를 벗어날 수 없습니다. 그래도 왜 두려워? 그들은 물지 않습니다 (주의하는 한). 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
샘플 코드에 감사드립니다. 나는 큰 배열을 사용하여 특정 기능을 구현했지만 이것을 사용하여 다른 유사한 것을 구현할 것이고, 그것을 제어 한 후에 다른 것을 다시 변경하십시오 :)
Balkania

2
코드에 감사드립니다. removeArray마지막 요소를 제거 하는 방법도 깔끔합니다. 허용하면 코드 샘플에 추가하겠습니다.
brimborium

5
% d와 size_t ... 비트가 없습니다. C99 이상을 사용하는 경우 % z를 추가 할 수 있습니다.
Randy Howard

13
메모리 할당 및 재 할당으로 안전 점검을 생략하지 마십시오.
Alex Reynolds

3
성능 트레이드 오프입니다. 매번 두 배로 늘리면 때로는 100 % 오버 헤드와 평균 50 %가 발생합니다. 3/2는 최악의 50 %와 일반적인 25 %를 제공합니다. 또한 한계 (phi)에서 피비 오 나치 수열의 유효 기저에 가깝습니다.이 기수는 종종 기하 급수적이지만 기수 2보다 훨씬 덜 강력하지만 계산하기는 쉽습니다. +8은 합리적으로 작은 어레이가 너무 많은 사본을 수행하지 않음을 의미합니다. 크기와 관련이없는 경우 배열이 빠르게 성장할 수 있도록 곱셈 용어를 추가합니다. 전문가의 사용에서는 조정 가능해야합니다.
댄 셰퍼드

10

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

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

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


현대적인 2D 게임에서 3264 개의 정수로 구성된 7 개의 배열은 어떻게 들립니까? 내가 편집증이라면, 해결책은 큰 배열 일 것입니다.
Balkania

3
여기서 # 1과 # 4는 모두 포인터와 동적 메모리 할당을 사용해야합니다. realloc# 3과 함께 사용 하는 것이 좋습니다 . 배열에 정상적인 크기를 할당 한 다음 부족할 때마다 크기를 늘립니다. realloc필요한 경우 데이터 복사를 처리합니다. 메모리 관리에 대한 OP의 질문에 관해서 malloc는 시작에서 free한 번, 끝에서 한 번, 그리고 realloc공간이 부족할 때마다해야합니다. 그렇게 나쁘지 않습니다.
Borealid

1
@ 발카 니아 : 3264 정수의 7 배열은 100KB 미만의 머리카락입니다. 전혀 메모리가 많지 않습니다.
Borealid

1
@ 발카 니아 : 7 * 3264 * 32 bit같은 소리 91.39 kilobytes. 요즘 어떤 표준에 의해서도 많지 않습니다.)
Wolph

1
이 때 발생해야하는 완전히 명확하지 않기 때문에이 특정 누락, 부끄러운 realloc반환 NULL: a->array = (int *)realloc(a->array, a->size * sizeof(int));...이 아마 가장 잘으로 기록 된 것 같습니다 int *temp = realloc(a->array, a->size * sizeof *a->array); a->array = temp;... 일어날 필요가 일어나지 않는 어떤 것이 명백 할 것이다 그런 식으로 이전을NULL 값이 할당된다 a->array(이것은 전혀 경우).
자폐증

10

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

불행히도 한계가 있습니다. 여전히 기능 사용을 배우는 동안 예를 들어 교사의 역할을 맡아서는 안됩니다. 나는 종종 겉으로 사용하는 방법을 알고하지 않는 답변을 읽고 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); !


" array[x]에 대한 바로 가기입니다 *(array + x). [...]"확실합니까 ???? eli.thegreenplace.net/2009/10/21/… 의 다양한 동작에 대한 설명을 참조하십시오 .
C-Star-W-Star

1
아아, @ C-Star-Puppy, 당신의 자원이 전혀 언급하지 않은 것으로 보이는 참조는 C 표준입니다. 그것은 컴파일러가 합법적으로 C 컴파일러를 호출 해야하는 사양입니다. 귀하의 리소스가 내 정보와 전혀 모순되지 않는 것 같습니다. 그럼에도 불구하고, 표준은 실제로 다음과 같은 몇 가지 예제가 이 보석 이 밝혀있어 array[index]실제로 ptr[index]변장 ... "첨자 연산자의 정의 []E1[E2]동일 (*((E1)+(E2)))" 당신은 표준 반박 할 수없는
자폐증

@ C-Star-Puppy : int main(void) { unsigned char lower[] = "abcdefghijklmnopqrstuvwxyz"; for (size_t x = 0; x < sizeof lower - 1; x++) { putchar(x[lower]); } }... 이 데모를 시도해보십시오. 아마도 당신은 아마도 ... #include <stdio.h>그리고 <stddef.h>내가 어떻게 x[lower]( x정수 타입으로) 쓴 것이 lower[x]아닌가? C 컴파일러는 상관하지 않습니다. 왜냐하면와 *(lower + x)같은 값 *(x + lower)이고 lower[x]전자 x[lower]는 후자입니다. 이 모든 표현은 동일합니다. 시도해보십시오 ... 내 말을 받아 들일 수 없다면 직접 참조하십시오 ...
자폐증

... 그리고 물론이 부분이 있습니다. 제가 강조한 부분은 있습니다. 그러나 "sizeof 연산자, _Alignof 연산자, 또는 단항 및 연산자이거나 배열을 초기화하는 데 사용되는 문자열 리터럴 인 경우 ''array of type ''유형의 표현식은 ''pointer to type ''유형의 표현식으로 변환되어 배열의 초기 요소를 가리 킵니다. 객체 및 좌변 아니다 . 어레이 오브젝트 레지스터 스토리지 클래스가있는 경우, 동작이 정의되지 않는다. " 함수 btw도 마찬가지입니다.
자폐증

마지막으로 @ C-Star-Puppy라는 마이크로 소프트 C ++는 C 컴파일러가 아니며 거의 20 년 동안 사용되지 않았습니다. C89 모드 인 suuuure를 활성화 할 수 있지만 컴퓨팅 분야에서 1980 년대 후반을 넘어서 발전했습니다. 그 주제에 대한 자세한 내용은, 내가 읽어 보시기 바랍니다 이 기사를 ... 다음과 같은 실제 C 컴파일러로 전환 gcc또는 clang당신은 C99이 기능을 채택 많은 패키지가 찾을 수 있기 때문에, 당신의 C 컴파일의 모든 ...
자폐증

3

바탕 마테오 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);
}

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


1
당신이 추구하는 건설적인 비판이라면 Code Review 에 게시하는 것이 좋습니다 . 즉, 몇 가지 제안 : 코드는 malloc()할당을 사용하기 전에 호출 성공 여부를 확인 해야합니다. 같은 맥락 realloc()에서 포인터 의 결과를 재 할당 된 원래 메모리에 직접 할당하는 것은 실수입니다 . 경우 realloc()실패 NULL반환되고, 코드는 메모리 누수로 남아 있습니다. 크기를 조정할 때 한 번에 1 개의 공간을 추가하는 것보다 메모리를 두 배로 늘리는 것이 훨씬 효율적 realloc()입니다.
전 nihilo

1
내가 찢어 질 줄 알았는데, "건축적인 비판"이라고 말했을 때 농담

2
누군가를 찢어 내려고하지 않고, 약간의 건설적인 비판을 제공하기 만하면, 당신의 마음이 가까워지지 않아도 앞으로 나올 수도 있습니다.)
ex nihilo

1
David, 나는 당신의 의견에 대해 생각하고 있습니다. "한 번에 하나의 공간을 추가하는 것보다 크기를 조정할 때 메모리를 두 배로하는 것이 훨씬 효율적입니다. realloc ()에 대한 호출 횟수가 적습니다." 작업에 필요한 양만 할당하는 것보다 두 배의 메모리를 할당하고 메모리를 낭비하지 않는 것이 더 나은 이유는 무엇입니까? realloc () 호출에 대한 당신의 의견을 얻었지만 문제가있을 때마다 realloc ()을 호출하는 이유는 무엇입니까? 메모리를 재 할당하기 위해 존재하는 것이 아닙니까?

1
엄격한 배가가 최적이지는 않지만 한 번에 한 바이트 (또는 하나 int등)로 메모리를 늘리는 것보다 낫습니다 . 더블링은 일반적인 솔루션이지만 모든 상황에 맞는 최적의 솔루션이 있다고 생각하지 않습니다. 이중화가 좋은 아이디어 인 이유는 다음과 같습니다 (1.5와 같은 다른 요소도 좋습니다). 합리적인 할당으로 시작하면 전혀 재 할당 할 필요가 없습니다. 더 많은 메모리가 필요할 때 합리적인 할당이 두 배가됩니다. 이 방법으로에 한 번 또는 두 번만 전화하면 realloc()됩니다.
전 nihilo

2

당신이 말할 때

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

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

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

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


2

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

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

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

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


이것이 실제로 제기 된 질문에 대한 답변인지 또는 의견인지는 확실하지 않습니다.

그것은 "방법"에 대한 의견이며 누군가가 더 나은 아이디어를 가지고 있다면 확인을 요구하고 있습니다. ;)
JOSMAR BARBOSA-M4NOV3Y

마지막 문장을 이해하지 못하는 것 같습니다. SO는 스레드 포럼이 아니므로 답변에서 이와 같은 공개 질문은 이상하게 보입니다.

1
마지막 문장을 당신이하고 싶은 생각으로 고쳤습니다.
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.