Open-Closed Principle

[C언어] 2차원 배열과 포인터 본문

Programming/C, C++

[C언어] 2차원 배열과 포인터

대박플머 2014. 7. 6. 19:31

1차원 배열과 포인터에 대해서는 포스팅을 했다. 

그런데 1차원 배열과 포인터 보다 더 중요한 2차원 배열과 포인터에 대한 내용을 포스팅 해보도록 하겠다. 

기본적으로 1차원 배열도 많이 사용하지만 2차원 배열을 사용해야 할때 또한 다분히 존제 한다. 

우선 기본 소스를 우선 보고 하나하나 찾아가 보자. 

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
26
27
28
29
30
31
32
33
int main(){
    int i, j;
    int imsi[3][2] = {{6,3},{9,1},{7,2}};
    int (*imsip)[2]; // 선언
 
    imsip = imsi; // A
 
    for(i = 0; i < 3; i++){
        for(j=0;j<2;j++){
            printf("[%d][%d] %d\n", i, j, *(*(imsip + i)+j)); // B
        }
    }
 
 
    for(i = 0; i < 3; i++){
        for(j=0;j<2;j++){
            printf("[%d][%d] %d\n", i, j, *(imsip[i]+j)); // C
        }
    }
 
    for(i = 0; i < 3; i++){
        for(j=0;j<2;j++){
            printf("[%d][%d] %d\n", i, j, imsip[i][j]); // D
        }
    }
 
    for(i = 0; i < 3; i++){
        for(j=0;j<2;j++){
            printf("[%d][%d] %d\n", i, j, imsi[i][j]); // E
        }
    }
}
 

이 소스를 보면 B, C, D, E가 모두 같은 결과를 가져 온다. 하나 하나 알아 보도록 하겠다. 

2차원 배열에서 알아둘것은 우선 '선언'부분이다. 

이 부분이 포인터를 사용하게 하는데 많은 시행착오를 만들어 낸다. 

선언을 잘 기억 해 두어야 할 것이다. 

선언을 중요한 이유에 대해서 예제를 통해 한번 확인 해보자. 

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
26
27
28
29
30
31
#include<stdio.h>
 
int main(){
    
    int imsi[3][2] = { { 1, 2 }, { 3, 4 }, { 5, 6 } };
    int i, j;
 
    int *intp;
    intp = imsi;
 
    for ( i = 0; i < 3; i++)
    {
        for (j = 0; j < 2; j++)
        {
            printf("[%d][%d] = %d\n", i, j, imsi[i][j]);
        }
    }
 
 
    for ( i = 0; i < 3; i++)
    {
        printf("%d \n", intp + i);   //   A
    }
 
    printf("\n");
 
    for ( i = 0; i < 3; i++)
    {
        printf("%d \n", imsi[i]);      // B
    }
}


예기서 우리가 알아야 할 사실은 단 한가지 뿐이다. 포인터 변수는 1차원 배열만을 받아 들인다는 것이다. 

1차원 배열만 갖을 수 있는 아이들이기 때문에 그 포인터들의 배열을 갖는 다면 2차원 배열이 되는 것이다. 

말을 쉽게 하자면 포인터 패열을 저장할 수 있는 (*pint)의 배열 문법 각괄호([])를 사용하면 되는 것이다. 

여기서 괄호는 아주 중요한 열활을 한다. 연산자 우선순위가 머리에 명확하게 있는 사람이 아니라면 괄호가 들어갈 부분을 논리적으로 생각하여 괄호를 하는 것이 좋다. 

가장 많이 실수 할 수 있는 코드다. 

1
2
3
4
5
6
7
8
9
int main(){
 
    int imsi[3][2] = { { 1, 2 }, { 3, 4 }, { 5, 6 } };
    int i, j;
 
    int (*pint1)[2];  //   A
    int* pint2[3];    //   B
 
}

가장 많이 실수 하는 부분이 A를 B처럼 사용하는 것이다. 
이런 실수를 막기 위해서는 B처럼 표기 하는 것이 좋다. 이전 포스팅

에서도 언급한 내용이지만 포인터는 일반적인 변수 타입중 하나라고 받아들임으로써 해깔리지 않을 수 있는 것이다. 

2차원 배열과 포인터에서는 가장 핵심이 되는 내용이다. 이걸 이해하면 다른 것또한 쉽게 이해가 갈것이다. 

이제 한가지 재미 있는 실험을 해보려고 한다. 

실험에 들어가기 앞서 실험과 관련되어 있는 문제 하나를 풀고 넘어 갔으면 좋겠다. 

1
2
3
4
5
6
7
8
9
10
11
12
13
int main(){
    int imsi[2];
 
    int* pimsi;
 
    pimsi = imsi;
 
    printf("imsi[2] = %d \n"sizeof(imsi));
    printf("pimsi = %d \n"sizeof(pimsi));
    printf("*pimsi = %d "sizeof(*pimsi));
 
    printf("\n");
}

결과를 예상해보면 많은 공부가 될 것 같다. 그리고 정확하게 포인터의 개념과 배열을 분리해서도 생각할 수 있는 문제이다. 

답은 예제 소스를 돌려보면 알 수 있으니깐 안다는 가정하에 이야기를 하도록 하겠다. 

이 예제를 통해 보여주고 싶은 것은 바로 배열의 사이즈와 포인터의 사이즈이다. 
그리고 결과를 통해 나온 사이즈를 보며 우리는 확실히 알 수 있다.
포인터에 배열을 넣고 사용은 하고 있지만 물리적은 구조는 배열과 포인터는 다르구나. 

그래서 주의해야 할 부분이 있다. 

1
2
3
4
5
6
7
8
9
10
int main(){
    int imsi[3][2] = { { 1, 2 }, { 3, 4 }, {5,6} };
 
    int (*pimsi)[2];
 
    pimsi = imsi;
    pimsi = imsi[0];
    pimsi = &imsi[0][0];
 
}

위의 예제를 보면 2차원 배열이 시작하는 부분의 주소를 다양하게 표현하여 포인터 배열에 넣었다. 

정말 다 똑같은 것일까???

1
2
3
4
5
6
7
8
9
10
int main(){
    int imsi[3][2] = { { 1, 2 }, { 3, 4 }, {5,6} };
 
    printf("imsi        = %d \taddr = %x \n",sizeof(imsi), imsi);
    printf("imsi[0]     = %d \taddr = %x \n"sizeof(imsi[0]), imsi[0]);
    printf("&imsi[0][0] = %d \taddr = %x \n"sizeof(&imsi[0][0]), &imsi[0][0]);
 
    printf("");
}
 


결과는 보면 아주 명확해 질것이다. 

주소는 똑같지만 배열의 크기가 다르게 표현이 된다. 

배열과 포인터를 엮어서 생각하면 아주 재미 있는 것들을 할 수 있다. 

하지만 배열과 포인터의 특성을 정확하게 이해 하고 사용하는 것이 선행 되어야 할 것이다. 

2차원 배열과 포인터 끝.....