2 차원 배열을 할당하는 이상한 방법?


110

프로젝트에서 누군가 다음 줄을 썼습니다.

double (*e)[n+1] = malloc((n+1) * sizeof(*e));

이것은 아마도 (n + 1) * (n + 1) double의 2 차원 배열을 생성합니다.

아마 지금까지 내가 물었다 아무도 정확히, 이것은 무엇을 말해 줄 수 없었다 때문에, 나는 말,도 아니다 유래 어디서 왜 작동합니다 (주장, 그것은 않는,하지만 난 아직 구입하고 있지 않다).

아마도 나는 분명한 것을 놓치고 있지만 누군가가 위의 줄을 설명해 줄 수 있다면 감사 할 것입니다. 왜냐하면 개인적으로 우리가 실제로 이해하는 것을 사용한다면 훨씬 나아질 것이기 때문입니다.


15
작동하더라도 푸셔를 발사하십시오.
Martin James

22
@MartinJames 왜? 그렇지 않으면 인접한 메모리에 2D 배열을 어떻게 할당합니까? 망가진 1D 배열? 그것은 1990 년대의 프로그래밍이고, 우리는 지금 VLA를 가지고 있습니다.
Lundin

43
공식적으로, 즉, 한 동적 실제 2D 배열을 할당하는 유일한 방법.
Quentin

15
@Kninnug 아니요, 2D 배열이 아닌 포인터 배열을 선언합니다. 2D 배열을 원하는 경우 포인터 배열을 할당 할 이유가 없습니다. 힙 조각화와 낮은 캐시 메모리 사용률로 인해 느리고 배열로 사용할 수 없기 때문에 안전하지 않습니다 (memcpy 등). 게다가 코드가 부풀어 오른다. 여러 개의 free () 호출이 필요하며 메모리 누수가 발생하기 쉽습니다. 이러한 코드가 널리 퍼져있을 수 있지만 이는 매우 나쁩니다.
Lundin

15
이 문제는 샘플이 동일한 값의 차원을 사용 n+1하지 않고 대신 사용 되었다면 설명 / 답하기가 더 명확했을 것입니다.double (*e)[rows] = malloc(columns * sizeof *e);
chux-Monica 복원

답변:


87

변수 en + 1유형 요소의 배열에 대한 포인터 double입니다.

역 참조 연산자를 e사용하면 e" n + 1유형 의 요소 배열"인 기본 유형이 제공 double됩니다.

malloc호출은 단순히의 염기 형을 얻어 e(상술)로하고, 승산을 사이즈를 취득 n + 1하고, 해당 크기를 전달하는 malloc기능. 기본적으로 n + 1n + 1요소 배열 배열을 할당합니다 double.


3
@MartinJames sizeof(*e)sizeof(double [n + 1]). 그것을 곱하면 n + 1충분합니다.
일부 프로그래머 친구

24
@MartinJames : 무엇이 문제입니까? 눈에 띄지 않고 할당 된 행이 연속적임을 보장하며 다른 2D 배열과 마찬가지로 색인을 생성 할 수 있습니다. 이 관용구를 내 코드에서 많이 사용합니다.
John Bode

3
당연해 보일 수 있지만 이것은 정사각형 배열 (동일한 차원) 에서만 작동합니다 .
Jens

18
@Jens : n+1두 차원을 모두 입력 하면 결과가 정사각형이 된다는 의미에서만 가능합니다 . 를 수행 double (*e)[cols] = malloc(rows * sizeof(*e));하면 결과에 지정한 행과 열의 수가 포함됩니다.
user2357112 모니카 지원

9
@ user2357112 이제 나는 훨씬 차라리 볼 것입니다. 추가해야 함을 의미하더라도 int rows = n+1int cols = n+1. 신은 영리한 코드에서 우리를 구해 주 십니다.
candied_orange 2016-04-22

56

이것은 2D 배열을 동적으로 할당해야하는 일반적인 방법입니다.

  • e유형 배열에 대한 배열 포인터 double [n+1]입니다.
  • sizeof(*e)따라서 한 double [n+1]배열 의 크기 인 pointed-at 유형의 유형을 제공합니다 .
  • n+1이러한 어레이를 위한 공간을 할당 합니다.
  • e이 배열 배열의 첫 번째 배열을 가리 키 도록 배열 포인터 를 설정합니다 .
  • 이를 통해 eas 를 사용 e[i][j]하여 2D 배열의 개별 항목에 액세스 할 수 있습니다.

개인적으로이 스타일이 훨씬 읽기 쉽다고 생각합니다.

double (*e)[n+1] = malloc( sizeof(double[n+1][n+1]) );

12
내가 제안한 스타일에 동의하지 않고 스타일을 선호하는 것을 제외하고는 좋은 대답 ptr = malloc(sizeof *ptr * count)입니다.
chux - 분석 재개 모니카

좋은 답변입니다. 선호하는 스타일이 마음에 듭니다. 고려해야 할 행 사이에 패딩이있을 수 있으므로이 방법으로 수행해야한다는 점을 지적하는 것이 약간 개선 될 수 있습니다. (적어도 그게 당신이 이런 식으로해야하는 이유라고 생각합니다.) (내가 틀렸다면 알려주세요.)
davidbak

2
@davidbak 그것도 똑같습니다. 배열 구문은 자체 문서화 코드 일뿐입니다. 소스 코드 자체에 "2D 배열을위한 공간 할당"이라고 말합니다.
Lundin

1
@davidbak 참고 : 주석 의 사소한 단점은 오버플로 malloc(row*col*sizeof(double))가 발생 row*col*sizeof()하지만 그렇지 않은 경우 발생합니다 sizeof()*row*col. (예 : row, col are int)
chux-Monica 복원 apr

7
@davidbak : sizeof *e * (n+1)유지 관리가 더 쉽습니다. 만약 당신이 (에서 기본 유형을 변경하기로 결정한 경우 doublelong double, 예를 들면), 다음 만의 선언을 변경해야합니다 e; 호출 에서 sizeof표현식 을 수정할 필요가 없습니다 malloc(시간을 절약하고 한 곳에서 변경하지 못하도록 보호). sizeof *e항상 올바른 크기를 제공합니다.
John Bode

39

이 관용구는 자연스럽게 1D 배열 할당에서 벗어납니다. 임의의 유형의 1D 배열을 할당하는 것으로 시작하겠습니다 T.

T *p = malloc( sizeof *p * N );

간단 하죠? 표현은 *p 유형이 T있으므로, sizeof *p같은 결과를 제공 sizeof (T)그래서 우리는 충분한 공간을 할당하고, N의 - 요소 배열 T. 이것은 모든 유형T해당됩니다 .

이제 T와 같은 배열 유형으로 대체합시다 R [10]. 그러면 우리의 할당은

R (*p)[10] = malloc( sizeof *p * N);

여기서 의미 는 1D 할당 방법 과 정확히 동일 합니다. 변경된 것은 p. 대신 T *지금 R (*)[10]입니다. 식은 *p형 갖는 T타입 R [10]이므로 sizeof *p동등 sizeof (T)동등하다 sizeof (R [10]). 그래서 우리는 충분한 공간을 할당하고 N10의 요소 배열 R.

우리가 원한다면 이것을 더 나아갈 수 있습니다. R자체가 배열 유형 이라고 가정 합니다 int [5]. 그것을 대체 R하고 우리는

int (*p)[10][5] = malloc( sizeof *p * N);

같은 거래는 - sizeof *p과 동일 sizeof (int [10][5])하고, 우리는을 개최 메모리 충분한 크기의 연속 된 덩어리를 할당하는 바람 N에 의해 10에 의해 5배열 int.

이것이 할당 측면입니다. 액세스 측은 어떻습니까?

기억 []첨자 동작되는 정의 포인터 연산의 관점에서 : a[i]으로 정의 *(a + i)1 . 따라서 첨자 연산자는 [] 암시 적 으로 포인터를 역 참조합니다. 경우 p에 대한 포인터 T를 명시 적으로 단항으로 역 참조하여 지적-가치 중 하나, 액세스 할 수있는 *운영자 :

T x = *p;

또는[] 아래 첨자 연산자 를 사용하여 :

T x = p[0]; // identical to *p

따라서 배열p 의 첫 번째 요소를 가리키는 경우 포인터의 아래 첨자를 사용하여 해당 배열의 모든 요소에 액세스 할 수 있습니다 .p

T arr[N];
T *p = arr; // expression arr "decays" from type T [N] to T *
...
T x = p[i]; // access the i'th element of arr through pointer p

이제 대체 작업을 다시 수행하고 T배열 유형으로 대체하겠습니다 R [10].

R arr[N][10];
R (*p)[10] = arr; // expression arr "decays" from type R [N][10] to R (*)[10]
...
R x = (*p)[i];

하나의 즉각적인 차이점; p아래 첨자 연산자를 적용하기 전에 명시 적으로 역 참조 합니다. 우리는에 첨자를 붙이고 싶지 않고 p, 어떤 p (이 경우 배열 arr[0] )을 가리키는 지에 첨자를 씁니다 . 단항 *은 아래 첨자 []연산자 보다 우선 순위가 낮기 때문에 괄호를 사용하여 명시 적 p으로 *. 그러나 위에서 *p는과 동일 p[0]하므로 다음으로 대체 할 수 있습니다.

R x = (p[0])[i];

아니면 그냥

R x = p[0][i];

따라서 p2D 배열을 가리키는 경우 p다음과 같이 해당 배열을 인덱싱 할 수 있습니다 .

R x = p[i][j]; // access the i'th element of arr through pointer p;
               // each arr[i] is a 10-element array of R

상기 및 치환과 같은 결론이 촬영 R으로 int [5]:

int arr[N][10][5];
int (*p)[10][5]; // expression arr "decays" from type int [N][5][10] to int (*)[10][5]
...
int x = p[i][j][k];

이 작품 그냥 같은 경우 p규칙적인 배열을 포인트하거나 메모리를 가리키는 경우를 통해 할당 malloc.

이 관용구에는 다음과 같은 이점이 있습니다.

  1. 간단합니다. 단편적인 할당 방법과 달리 코드 한 줄만
    T **arr = malloc( sizeof *arr * N );
    if ( arr )
    {
      for ( size_t i = 0; i < N; i++ )
      {
        arr[i] = malloc( sizeof *arr[i] * M );
      }
    }
    
  2. 할당 된 배열의 모든 행은 * 연속적 *이며, 위의 단편적인 할당 방법의 경우에는 해당되지 않습니다.
  3. 어레이 할당 해제는 free. 다시 말하지만, 할당을 해제 arr[i]하기 전에 각 할당을 해제해야하는 증분 할당 방법에서는 사실이 아닙니다 arr.

힙이 심하게 조각화되어 메모리를 연속 청크로 할당 할 수 없거나 각 행이 다른 길이를 가질 수있는 "들쭉날쭉 한"배열을 할당하려는 경우와 같이 단편적인 할당 방법이 선호되는 경우가 있습니다. 그러나 일반적으로 이것이 더 나은 방법입니다.


1. 배열 포인터 가 아니라는 점을 기억하십시오. 대신 배열 표현식 은 필요에 따라 포인터 표현식으로 변환됩니다.


4
+1 개념을 제시하는 방식이 마음에 듭니다. 요소 자체가 배열 인 경우에도 모든 유형에 대해 일련의 요소를 할당 할 수 있습니다.
logo_writer 2016-04-24

1
당신의 설명은 정말 좋지만, 연속적인 메모리 할당은 당신이 정말로 필요로 할 때까지 이점이 아닙니다. 연속 메모리는 연속되지 않은 메모리보다 더 비쌉니다. 간단한 2D 배열의 경우 메모리 레이아웃에 차이가 없으므로 (할당 및 할당 해제를위한 라인 수 제외) 비 연속 메모리 사용을 선호합니다.
Oleg Lokshyn
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.