17. 다차원 포인터[여기까지 C언어 입문(完)]

이성준·2023년 2월 28일
4

C 자료구조

목록 보기
8/12

이전까지의 포인터의 사용법을 그림으로 표현하면 다음과 같이 표현할 수 있다.

이전의 그림인
위와 같은 그림처럼 설명하지 않아 어색할 수 있겠지만 개념적인 설명은 현재의 그림이 이해하기 편하기 때문에 전체적인 이해를 먼저 한 이후에 세부적인 내용을 위의 그림으로 설명하겠다.

맨위의 표현한 이전의 포인터 그림에서 포인터는 몇차원이라고 할 수 있을까? → 1차원이라고 할 수 있다.
왜 그렇게 생각을 했냐 하면 가리키는 대상이 하나 이기 때문이고 이 글을 보는 여러분도 마찬가지의 생각을 했을것이다.
그렇다면 108번지라고 표현한 상자는 몇차원일까? 바로 0차원이다.

이런 자연스러운 논의를 통해 생각을 해보면 2차원 포인터는 어떻게 표현하면 좋을까?
<그림1>

위의 그림을 보면 왜 2차원이야? 라고 생각이 들것이다. 아마 개념적으로 탄탄하지 않다면 다음과 같은 그림을 2차원이라고 떠올릴 수도 있다.

하지만 금새 하나의 포인터 안에 2개의 주소를 저장할 수 없다는 사실을 깨달아야 한다.
다시 그림1로 돌아와서 생각을 해보면 가리키는 대상이 두가지이다. 왜냐하면 번지지정 연산자를 하나만 써서
*p를 사용하면 108번지를 가리키는 것이고 번지지정연산자를 두개를 사용한
**p는 106번지를 가리키는 것이기 때문이다.

결국에는 *p=&106번지를 사용해서 가운데 상자에 106번지 주소를 저장해두고 **p=Value를 사용해서 106번지의 값을 바꾸는 것이다.

그렇다면 다음과 같이 생각할 수 있다.
처음부터 106번지의 주소를 저장해두고 사용하면 더 편한것 아닌가?
하지만 다음과 같은 구성을 보자. 지금부터 다차원 포인터가 유용한 경우에 대해서 설명하겠다. 먼저 그림으로 이해해보자.

위의 그림을 코드로 작성해보겠다.

void main(void){
    short **pp, data1,data2,data3,data4;
    //모든 포인터형의 크기는 4바이트 이고 4개의 주소를 할당하기 위해 *4를 해줌
    pp = (short **)malloc(sizeof(short*)*4);
    *(pp) = &data1; //short형 메모리 구조니깐 주소는 short*임
    *(pp+1) = &data2;
    *(pp+2) = &data3;
    *(pp+3) = &data4;
    *(*(pp)) = 1; 
    *(*(pp+1)) = 2;
    *(*(pp+2)) = 3;
    *(*(pp+3)) = 4;
    printf("%d%d%d%d",data1,data2,data3,data4);
}


위와 같이 사용을 하는 것이다. 이를 이전에 계속 설명해왔던 형태로 설명을 하면 다음과 같은 그림으로 표현할 수 있다.

자 이제 위와 같은 형태에서 조금 더 발전된 형태로도 사용가능한데 이는 다음과 같다.

void main(void){
    short **pp;
    //모든 포인터형의 크기는 4바이트 이고 4개의 주소를 할당하기 위해 *4를 해줌
    pp = (short **)malloc(sizeof(short*)*4);
    //short형 메모리 2개의 주소
    *(pp) = (short*)malloc(sizeof(short)*2); 
    //short형 메모리 3개의 주소
    *(pp+1) = (short*)malloc(sizeof(short)*3); 
    //short형 메모리 4개의 주소
    *(pp+2) = (short*)malloc(sizeof(short)*4); 
    //short형 메모리 4개의 주소
    *(pp+3) = (short*)malloc(sizeof(short)*5); 
    *(*(pp)+1) = 1; 
    *(*(pp+1)+0) = 2;
    *(*(pp+2)+2) = 3;
    *(*(pp+3)+3) = 4;
    
    free(*pp);
    free(*(pp+1));
    free(*(pp+2));
    free(*(pp+3));
    free(pp);
}

위는 어떻게 생겨 먹은 것일까? 그림을 보자

따라서 꼭 2차원이 아니더라도 다차원 포인터를 쓰면 메모리를 트리구조로 넓혀갈 수 있다는 장점이 있다

또다른 장점으로 다른 함수간의 포인터로 연결이 가능하다. 다음 예제를 보자

void GetMyData(int*q){
    q = (int*)malloc(sizeof(int)*2);
    //p가 가리키는 주소에 4바이트씩 2개의 메모리영역이 생기길 기대
    printf("address of q:%p\n",q);
    return;
}

void main(){
    int*p;
    GetMyData(&p);
    printf("address of p:%p\n",p);
    free(p);
}

오류가 발생한다고 책에는 나와있는데 vscode기준 오류가 발생하지 않아서 코드를 바꿨다
이때 %p 형식지정자는 C언어에서 번지를 출력하고 싶을때 사용하는 형식지정자이다.

위의 결과는 다음과 같다.
설명을 해보자면 우리는 GetMyData로 p의 주소를 포인터 q가 받은후 포인터 q에 (int*)malloc(sizeof(int)*2)주소를 받음으로써 p또한 같은 주소를 갖게 하고 싶은 것이다.
하지만 결과에서 보다시피 같은 주소를 가리키고 있지 않다. 이유는 p의 주소를 q에 대입한 이후 q에는 다시 새로운 주소가 대입됐기 때문이다. A=B를 이용해 B로 초기화 하고 A=C를 이용해 C로 다시 초기화 해놓고 왜 B=C랑 같지 않냐고 말하는 것과 같다.

이때 2차원 포인터를 사용해 p도 가리키고 int형 2개를 넣을 수 있는 메모리(malloc(sizeof(int)*2))도 가리킬 수 있다면 문제가 해결될 것이다.
(문제: q가 malloc(sizeof(int)*2)를 가리킬때 p또한 가리키게 만들고 싶은 상황)
다음과 같이 코드를 바꿀 수 있다.

void GetMyData(int**q){
    *q = (int*)malloc(sizeof(int)*2);
    //p가 가리키는 주소에 4바이트씩 2개의 메모리영역이 생기길 기대
    printf("address q:%p\n",*q);
    return;
}

void main(){
    int*p;
    GetMyData(&p);
    printf("address p:%p\n",p);
    free(p);
}

Quiz> 여태까지 배운 개념을 사용해 2차원 포인터로 연령별 윗몸일으키기 횟수 관리하는 법을 프로그래밍 해보자

  • 20대부터 시작해서 연령층의 개수를 입력받아야함
  • 연령대 별로 사람의 수를 입력받아야함
  • 연령대 별 사람의 수만큼 횟수를 입력받아야함
    p.s 교재 코드랑 내코드는 다르기 때문에 교재 코드도 참고하길 바란다


































정답

void main(void){
    int num_age,num,tmp;
    float avg_score;
    printf("20대부터 시작해서 연령층이 몇개인가요?:");
    scanf("%d",&num_age);
    //sop = score of people
    int**sop = (int**)malloc(sizeof(int*)*num_age);
    float*avg = (float*)malloc(num_age); //평균을 저장해둘 포인터
    for(int i=0;i<num_age;i++){
        avg_score = 0.0; //평균 0으로 초기화
        printf("%d0대 연령의 윗몸 일으키기 횟수\n",i+2);
        printf("이 연령대는 몇명입니까?:");
        scanf("%d",&num);
        *(sop+i) = (int*)malloc(sizeof(int)*num);
        for(int j=0;j<num;j++){
            printf("%dth:",j+1);
            scanf("%d",*(sop+i)+j);// 주소를 입력해야하는데 값을 넣으려고 해서 오류 발생했음
            tmp = *(*(sop+i)+j);
            avg_score = avg_score+(float)tmp;
        }
        avg_score = (float)avg_score / (float)num;//float로 casting
        *(avg+i)=avg_score;
    }
    printf("연령별 평균 윗몸 일으키기 횟수\n");
    for(int i=0;i<num_age;i++){
        printf("%d0대:%.2f\n",i+2,*(avg+i));
    }
}

C언어 기본은 이쯤에서 마치고 구조체와 연결리스트는 이후에 학기중에 내용정리할겸 작성을 해보겠다.
수고하셨습니다!
C언어가 컴퓨터 언어와 가까운 언어이기 때문에 이해하기 어렵습니다.. 이해를 하지 못했다고 하더라도 너무 실망하지 마시고,
프로그래밍에 대해 끊임없는 관심 갖으시길 바랍니다.

-2022.03.01 이성준

profile
Time-Series

0개의 댓글