sourcecode

고정된 크기의 배열에 대한 포인터 배열

copyscript 2023. 10. 1. 21:52
반응형

고정된 크기의 배열에 대한 포인터 배열

고정된 크기의 배열 두 개를 포인터 배열에 할당하려고 했지만 컴파일러가 경고했고 그 이유를 이해할 수 없습니다.

int A[5][5];
int B[5][5];
int*** C = {&A, &B};

이 코드는 다음 경고와 함께 컴파일됩니다.

경고: 호환되지 않는 포인터 유형에서 초기화 [기본적으로 활성화]

코드를 실행하면 세그멘테이션 오류가 발생합니다.하지만 동적으로 할당하면A그리고.B, 그것은 잘 작동합니다.이게 왜죠?

,C다 것입니다.A그리고.B이렇게 해야 합니다.

int A[5][5];
int B[5][5];
int (*C[])[5][5] = {&A, &B};

의 입니다.C"는 배열에 대한 포인터의 배열입니다."C로 읽힙니다.전체 배열을 할당할 수 없으므로 배열에 포인터를 할당해야 합니다.

이 으로,(*C[0])[1][2]다와 .A[1][2].

만약 당신이 더 깨끗한 구문을 원한다면 다음과 같이.C[0][1][2] 즉, .

int **A;
int **B;
// allocate memory for A and each A[i]
// allocate memory for B and each B[i]
int **C[] = {A, B};

모스크바에서 Vlad가 제안한 구문을 사용하여 이 작업을 수행할 수도 있습니다.

int A[5][5];
int B[5][5];
int (*C[])[5] = {A, B};

은.C"는 배열에 대한 포인터의 배열입니다."C로 읽힙니다.이 경우, 각 배열 요소는C입니다.int (*)[5], int [5][5]이런 유형으로 붕괴될 수 있습니다.

이제, 당신은 사용할 수 있습니다.C[0][1][2]다와 수 .A[1][2].

이 논리를 보다 높은 차원으로 확장할 수도 있습니다.

int A[5][5][3];
int B[5][5][3];
int (*C[])[5][3] = {A, B};

불행하게도 잘못된 것들을 가르쳐주는 형편없는 책들/튜터링들/선생님들이 많이 있습니다.

포인터 대 포인터는 잊어버려요. 배열과는 아무 상관이 없어요.마침표.

또한 경험칙으로서: 2단계 이상의 간접 사용을 발견할 때마다 프로그램 설계에 근본적인 결함이 있으므로 처음부터 다시 만들어야 합니다.


이 작업을 올바르게 수행하려면 다음과 같이 수행해야 합니다.

에 대한 int [5][5]배열 포인터라고 하며 다음과 같이 선언됩니다.int(*)[5][5] 예:

int A[5][5];
int (*ptr)[5][5] = &A;

배열 포인터의 배열을 원한다면, 그것은 타입일 것입니다.int(*[])[5][5] 예:

int A[5][5];
int B[5][5];
int (*arr[2])[5][5] = {&A, &B};

당신이 알 수 있듯이 이 코드는 불필요하게 복잡해 보입니다. 그리고 그렇습니다.입니다를 하는 것은 입니다. 입력해야 하기 때문입니다.(*arr[x])[y][z] x를 그을 선택합니다 의미: "배열 포인터 배열에서 배열 포인터 번호 x를 선택하고, 2D 배열인 2D 배열을 가리키는 내용을 선택한 후 해당 배열에서 인덱스 [y][z] 항목을 선택합니다."

그런 구조물을 발명하는 것은 미친 짓일 뿐이지 제가 추천하고 싶은 것은 없습니다.일반 배열 포인터로 작업하면 코드를 단순화할 수 있다고 생각합니다.

int A[5][5];
int B[5][5];
int (*arr[2])[5][5] = {&A, &B};
int (*ptr)[5][5] = arr[0];
...
ptr[x][y][z] = 0;

하지만 이것은 여전히 다소 복잡한 코드입니다.완전히 다른 디자인을 고려해보세요!예:

  • 3D 배열을 만듭니다.
  • 2D 배열이 포함된 구조물을 만든 다음 이러한 구조물의 배열을 만듭니다.

선이 많이 잘못되어 있습니다.

int*** C = {&A, &B};

당신은 하나의 포인터를C의 물체를 될 입니다. 그건 통하지 않을 겁니다. 할 입니다를 입니다.C그 배열들에 대한 포인터들의 배열로.

모두&A그리고.&B.int (*)[5][5]의 5 의 5 에 ", "5 의 5 요소"를 합니다.int 5-element array of 5-element array "; 를 들어, C 는 "5-element 5-element " 을 해야 .int", 아니면

int (*C[2])[5][5] = { &A, &B };

라고 쓰여져 있습니다.

      C           -- C is a
      C[2]        -- 2-element array of
     *C[2]        -- pointers to
    (*C[2])[5]    -- 5-element arrays of
    (*C[2])[5][5] -- 5-element arrays of
int (*C[2])[5][5] -- int

, 다 중 하나의 더 .A아니면B해를 C:

int x = (*C[0])[i][j]; // x = A[i][j]
int y = (*C[1])[i][j]; // y = B[i][j]

는 를 수 없습니다.C[i] 연산자 []다보다 .*오퍼레이터, 우리는 그룹을 짜야 합니다.*C[0]품으로

우리는 이것을 조금만 치울 수 있습니다.sizeof또는 단항의&연산자(또는 선언에서 다른 배열을 초기화하는 데 사용되는 문자열 리터럴), 유형의 표현 "N- T은 "에서 "("")로 유형 "에서" "됩니다" 됩니다의 됩니다.T", 식의 값은 배열의 첫 번째 요소의 주소가 될 것입니다.

이.A그리고.B활자를 가지다int [5][5]의 5- , "5-의 5-,int두 모두 " 의 표현식에 됩니다. , "5-" 에 "" 를 합니다.int", 아니면int (*)[5]한다면으로 을 A그리고.B&A그리고.&B 5 의 요소 합니다의 합니다.int, 아니면

int (*C[2])[5] = { A, B };

좋아요, 그래도 꽤나 눈에 거슬리는군요. 하지만 유형의 결함 없이도 이 정도면 깨끗할 겁니다.

그럼 우리는 어떻게 그들의 요소들에 접근할 것인가요.A그리고.B해를 C?

.a[i]다음과 같이 정의됩니다.*(a + i) 때; ,a, i 해당 주소에서 요소(1바이트가 아닌)를 입력하고 결과를 역참조합니다.이 말은

*a == *(a + 0) == a[0]

따라서,

*C[i] == *(C[i] + 0) == C[i][0]

이 모든 것을 종합하면:

C[0] == A                      // int [5][5], decays to int (*)[5]
C[1] == B                      // int [5][5], decays to int (*)[5]

*C[0] == C[0][0] == A[0]       // int [5], decays to int *
*C[1] == C[1][0] == B[0]       // int [5], decays to int *

C[0][i] == A[i]                // int [5], decays to int *
C[1][i] == B[i]                // int [5], decays to int *

C[0][i][j] == A[i][j]          // int
C[1][i][j] == B[i][j]          // int

우리는 색인할 수 있습니다.C 마치 그것이 3D 배열인 처럼.int보다 .(*C[i)[j][k].

이 표는 유용할 수도 있습니다.

Expression        Type                "Decays" to       Value
----------        ----                -----------       -----
         A        int [5][5]           int (*)[5]       Address of A[0]
        &A        int (*)[5][5]                         Address of A
        *A        int [5]              int *            Value of A[0] (address of A[0][0])
      A[i]        int [5]              int *            Value of A[i] (address of A[i][0])
     &A[i]        int (*)[5]                            Address of A[i]
     *A[i]        int                                   Value of A[i][0]   
   A[i][j]        int                                   Value of A[i][j]   

:A,&A,A[0],&A[0],그리고.&A[0][0]모두 같은 을 산출하지만(배열의 주소와 배열의 첫번째 요소의 주소는 항상 동일함), 위의 표와 같이 유형이 다릅니다.


  1. 합니다인 p에는 있습니다.int어,tp+1는 2~4바이트 떨어진 다음 개체의 주소를 나타냅니다.

C 초보자들 사이에서 흔한 오해는 포인터와 배열이 동일하다고 가정한다는 것입니다.그건 완전히 잘못된 것입니다.

초보자들은 코드가 다음과 같은 것을 볼 때 혼란스러움을 느낍니다.

int a1[] = {1,2,3,4,5};
int *p1 = a1;            // Beginners intuition: If 'p1' is a pointer and 'a1' can be assigned
                         // to it then arrays are pointers and pointers are arrays.

p1[1] = 0;               // Oh! I was right
a1[3] = 0;               // Bruce Wayne is the Batman! Yeah.

이제 초보자들은 배열이 포인터이고 포인터가 배열이라는 것을 확인하여 다음과 같은 실험을 수행합니다.

int a2[][5] = {{0}};
int **p2 = a2;

그러면 호환되지 않는 포인터 할당에 대한 경고가 나타납니다. 그러면 사람들은 "오 마이 갓!왜 이 배열이 하비 덴트(Harvey Dent)가 되었습니까?"

일부는 한발 앞서 가기도 합니다.

int a3[][5][10] = {{{0}}};
int ***p3 = a3;             // "?"

그리고 리들러는 배열-포인터 동등성의 악몽에 빠집니다.

enter image description here

배열은 포인터가 아니며 그 반대도 마찬가지입니다.배열은 데이터 유형이고 포인터는 배열 유형이 아닌 다른 데이터 유형입니다.이는 몇 년 전 C-FAQ에서 다루어진 내용입니다.

배열과 포인터가 "동등하다"고 말하는 것은 그들이 동일하거나 심지어 상호 교환 가능하다는 것을 의미하지 않습니다.즉, 배열에 액세스하거나 배열을 시뮬레이션하기 위해 포인터를 편리하게 사용할 수 있도록 배열 및 포인터 산술이 정의된다는 것입니다.다시 말해, Wayne Throop이 말했듯이, "[C]에서 동일한 포인터 산술배열 색인은 포인터와 배열이 다릅니다.")

이제 이러한 혼란을 피하기 위한 배열에 대한 몇 가지 중요한 규칙을 항상 기억해 두십시오.

  • 배열은 포인터가 아닙니다.포인터는 배열이 아닙니다.
  • 됩니다, 됩니다, 의 피연산자를 될 때 은 첫 됩니다.sizeof그리고.&교환입니다.
  • 포인터 산술배열 색인이 동일합니다.
  • 포인터와 배열이 다릅니다.
  • 제가 "포인트는 배열이 아니며 그 반대입니다."라고 말했었나요?

이제 당신은 규칙을 가지고 있고, 당신은 다음과 같이 결론지을 수 있습니다.

int a1[] = {1,2,3,4,5};
int *p1 = a1;

a1다에 .int *p1 = a1;첫번째 요소를 가리키는 포인터로 변환했습니다. 요소들은입니다 입니다.int입니다 .int *됩니다.p1.

int a2[][5] = {{0}};
int **p2 = a2;

a2인에 .int **p2 = a2;첫번째 요소를 가리키기 위해 붕괴됩니다. 요소들은입니다 입니다.int[5](2D은 1D이므로)다가 .int(*)[5]에 대한에 포인터)과되지 않는int ** 겁니다.그럴 것 같네요.

int (*p2)[5] = a2;

마찬가지로

int a3[][5][10] = {{{0}}};
int ***p3 = a3;

a3입니다.int [5][10]입니다 입니다.int (*)[5][10],그렇지만p3의.int ***,는,

int (*p3)[5][10] = a3;

이제 여러분의 토막글로 이동합니다.

int A[5][5];
int B[5][5];
int*** C = {&A, &B};

&A그리고.&Bint(*)[5][5].C입니다.int***까를 .C두 합니다.A그리고.B, 를 해야 .C인 1의 int(*)[5][5]활자 요소이 작업은 다음과 같이 수행해야 합니다.

int (*C[2])[5][5] = {&A, &B};

하지만 A와 B를 동적으로 할당하면 잘 됩니다.왜 이러한가?

그런 이 틀림없습니다.A그리고.B~하듯이int **이 둘 다 입니다.C입니다.int ***, 그래서 그것은 주소를 가질 수 있습니다.int**데이터를 입력합니다.int*** C = {&A, &B};

  int*** C = &A;

int*** C = {&A, &B};됩니다.됩니다.

C11: 5.1.1.3(P1):

동작 또한 명시적으로 정의되지 않았거나 구현이 정의된 것으로 명시적으로 지정된 경우에도, 사전 처리 번역 유닛 또는 번역 유닛이 어떤 구문 규칙 또는 제약 조건의 위반을 포함하는 경우, 적합한 구현은 (구현이 정의된 방식으로 식별된) 적어도 하나의 진단 메시지를 생성해야 합니다.

자세한 설명을 위해게시물을 읽어보세요.

배열은 C의 다차원 포인터와 같은 것이 아닙니다.배열의 이름은 색인을 작성하는 방법에 관계없이 대부분의 경우 배열이 포함된 버퍼의 주소로 해석됩니다.A다로 됩니다.int A[5][5],그리고나서A합니다. 즉,으로 해석됩니다.됩니다,즉.int *()int *[5] a)가 .int **조금. 하려면 주소 계산에는 두 가지 요소가 필요합니다라는 두 요소가 합니다.A[x][y] = A + x + 5 * y합니다를 할 때 .A[x + 5 * y], 그것은 홍보하지 않습니다.A다차원 버퍼에 연결합니다.

C에서 다차원 포인터를 원한다면 그렇게 해도 됩니다.구문은 매우 비슷하겠지만 설정이 조금 더 필요합니다.몇 가지 일반적인 방법이 있습니다.

단일 버퍼 사용 시:

int **A = malloc(5 * sizeof(int *));
A[0] = malloc(5 * 5 * sizeof(int));
int i;
for(i = 1; i < 5; i++) {
    A[i] = A[0] + 5 * i;
}

각 행에 대해 별도의 버퍼가 있는 경우:

int **A = malloc(5 * sizeof(int *));
int i;
for(i = 0; i < 5; i++) {
    A[i] = malloc(5 * sizeof(int));
}

배열과 포인터의 동등성 때문에 혼란스러워 하고 있습니다.

과 같은 할 때A[5][5]에 대해 C 는 으로 25 합니다를 했으므로합니다.즉,됩니다와 됩니다.

A00, A01, ... A04, A10, A11, ..., A14, A20, ..., A24, ...

는,A는 이 입니다.그것은 타입입니다.int *,것은 아니다.int **.

배열에 대한 포인터 벡터를 원하는 경우 변수를 다음과 같이 선언합니다.

int   *A[5], *B[5];

그러면 다음과 같은 결과가 나옵니다.

A0, A1, A2, A3, A4

int*, 를 할.malloc()아니면 뭐든.

아니면, 당신은 다음과 같이 선언할 수 있습니다.C~하듯이int **C.

배열과 포인터는 밀접하게 연관되어 있지만 전혀 같은 것이 아닙니다.대부분의 상황에서 배열 값은 포인터로 붕괴되고 배열 표기법은 함수 프로토타입에서 실제로 포인터인 매개 변수를 선언하는 데 사용될 수 있기 때문에 사람들은 때때로 이에 대해 혼란스러워 합니다.또한 배열 인덱싱 표기법이라고 생각하는 많은 사람들이 실제로 포인터 산술과 참조 해제의 조합을 수행하므로 포인터 값과 배열 값에 대해 동일하게 잘 작동합니다(배열 값이 포인터로 붕괴되기 때문입니다).

선언문을 보면

int A[5][5];

A의 5개합니다의 합니다.int됩니다 유형의 int (*)[5] 5터, 5즉의 포인터입니다.int전체 . 을 갖습니다.int (*)[5][5]의 배열로(5개의를로 포인터)int ).int ***r합니다(포인터다를)int와 같은 :). 이와 같은 다차원 배열에 포인터를 선언하려면 다음과 같이 수행할 수 있습니다.

int A[5][5];
int B[5][5];
int (*C)[5][5] = &A;

이러한 포인터의 배열을 선언하려면 다음을 수행할 수 있습니다.

int (*D[2])[5][5] = { &A, &B };

추가됨:

이러한 구분은 다양한 방식으로 작용하며, 배열 값이 포인터로 붕괴되지 않는 맥락과 이와 관련된 맥락이 더 중요합니다.이들 중 가장 중요한 것 중 하나는 값이 피연산자일 때입니다.sizeof교환입니다. 할 때, 1됩니다.위의 선언을 고려할 때, 다음의 관계식들은 모두 1(true)로 평가됩니다.

sizeof(A)       == 5 * 5 * sizeof(int)
sizeof(A[0])    == 5 * sizeof(int)
sizeof(A[0][4]) == sizeof(int)
sizeof(D[1])    == sizeof(C)
sizeof(*C)      == sizeof(A)

또한, 이러한 관계식은 1로 평가될 가능성이 있지만 보장되지는 않습니다.

sizeof(C)       == sizeof(void *)
sizeof(D)       == 2 * sizeof(void *)

이는 배열 인덱싱이 작동하는 방식의 기본이며, 메모리를 할당할 때 이해하는 데 필수적입니다.

세 번째 배열을 다음과 같이 선언해야 합니다.

int A[5][5];
int B[5][5];
int ( *C[] )[N][N] = { &A, &B };

그것은 2차원 배열에 대한 포인터들의 배열입니다.

예를들면

#include <stdio.h>

#define N   5

void output( int ( *a )[N][N] )
{
    for ( size_t i = 0; i < N; i++ )
    {
        for ( size_t j = 0; j < N; j++ ) printf( "%2d ", ( *a )[i][j] );
        printf( "\n" );
    }
}

int main( void )
{
    int A[N][N] =
    {
        {  1,  2,  3,  4,  5 },
        {  6,  7,  8,  9, 10 },
        { 11, 12, 13, 14, 15 },
        { 16, 17, 18, 19, 20 },
        { 21, 22, 23, 24, 25 }
    };
    int B[N][N] =
    {
        { 25, 24, 23, 22, 21 },
        { 20, 19, 18, 17, 16 },
        { 15, 14, 13, 12, 11 },
        { 10,  9,  8,  7,  6 },
        {  5,  4,  3,  2,  1 }
    };

/*
    typedef int ( *T )[N][N];
    T C[] = { &A, &B };
*/

    int ( *C[] )[N][N] = { &A, &B };

    output( C[0] );
    printf( "\n" );

    output( C[1] );
    printf( "\n" );
}        

프로그램 출력은

 1  2  3  4  5 
 6  7  8  9 10 
11 12 13 14 15 
16 17 18 19 20 
21 22 23 24 25 

25 24 23 22 21 
20 19 18 17 16 
15 14 13 12 11 
10  9  8  7  6 
 5  4  3  2  1 

라든가

int A[5][5];
int B[5][5];
int ( *C[] )[N] = { A, B };

그것은 2차원 배열의 첫 번째 요소에 대한 포인터의 배열로서.

예를들면

#include <stdio.h>

#define N   5

void output( int ( *a )[N] )
{
    for ( size_t i = 0; i < N; i++ )
    {
        for ( size_t j = 0; j < N; j++ ) printf( "%2d ", a[i][j] );
        printf( "\n" );
    }
}

int main( void )
{
    int A[N][N] =
    {
        {  1,  2,  3,  4,  5 },
        {  6,  7,  8,  9, 10 },
        { 11, 12, 13, 14, 15 },
        { 16, 17, 18, 19, 20 },
        { 21, 22, 23, 24, 25 }
    };
    int B[N][N] =
    {
        { 25, 24, 23, 22, 21 },
        { 20, 19, 18, 17, 16 },
        { 15, 14, 13, 12, 11 },
        { 10,  9,  8,  7,  6 },
        {  5,  4,  3,  2,  1 }
    };

/*
    typedef int ( *T )[N];
    T C[] = { A, B };
*/

    int ( *C[] )[N] = { A, B };

    output( C[0] );
    printf( "\n" );

    output( C[1] );
    printf( "\n" );
}        

프로그램 출력은 위와 같습니다.

 1  2  3  4  5 
 6  7  8  9 10 
11 12 13 14 15 
16 17 18 19 20 
21 22 23 24 25 

25 24 23 22 21 
20 19 18 17 16 
15 14 13 12 11 
10  9  8  7  6 
 5  4  3  2  1 

세 번째 배열을 어떻게 사용할지에 따라 달라질 수 있습니다.

(설명에 나와 있는 시연 프로그램에 나와 있는) type def를 사용하면 배열의 정의가 단순해집니다.

이 선언에 관해서는

int*** C = {&A, &B};

에는 됩니다 가 선언됩니다.int ***에는 다른 입니다를 .int ( * )[N][N].

그래서 컴파일러는 메시지를 발행합니다.

는 좋아합니다.typedef:

#define SIZE 5

typedef int  OneD[SIZE]; // OneD is a one-dimensional array of ints
typedef OneD TwoD[SIZE]; // TwoD is a one-dimensional array of OneD's
                         // So it's a two-dimensional array of ints!

TwoD a;
TwoD b;

TwoD *c[] = { &a, &b, 0 }; // c is a one-dimensional array of pointers to TwoD's
                           // That does NOT make it a three-dimensional array!

int main() {
    for (int i = 0; c[i] != 0; ++i) { // Test contents of c to not go too far!
        for (int j = 0; j < SIZE; ++j) {
            for (int k = 0; k < SIZE; ++k) {
//              c[i][j][k] = 0;    // Error! This proves it's not a 3D array!
                (*c[i])[j][k] = 0; // You need to dereference the entry in c first
            } // for
        } // for
    } // for
    return 0;
} // main()

언급URL : https://stackoverflow.com/questions/37348741/array-of-pointers-to-an-array-of-fixed-size

반응형