C언어

13-2) 2차원 포인터와 2차원 배열

SleeveStar 2021. 6. 29. 23:03
반응형

여러개의 1차원 포인터를 정적으로 할당하기

 

short* 형식의 1차원 포인터가 100개 필요하다면 배열 문법을 사용하여 다음과 같이 선언할 수 있다.

 

short *p[100]; /*short*p 형식의 1차원 포인터를 100개 선언함*/

 

배열의 요소가 100개이고 각 요소의 크기가 4바이트(포인터)이기 때문에 이 배열에 할당된 전체 메모리 크기는 400바이트이다. 이렇게 선언하면 p[0]부터 p[99]까지 총 100개의 포인터를 사용할 수 있다.

 

하지만 위와 같은 표현에는 두 가지 비효율성이 있다. 첫 번째는 배열을 사용했기 때문에 컴파일할 때 변수 p의 메모리 크기가 400바이트로 고정되어버린다. 만약 포인터 200개를 사용하도록 수정해야한다면 변수 p를 p[100] 대신 p[200]으로 바꿔서 선언해야한다. 그리고 소스코드를 변경했기 때문에 소스 파일을 다시 컴파일 해야 하는 불편함이 있다. 두 번째는 메모리가 낭비될 수 있다. 변수 p는 100개의 포인터가 메모리에 할당되기 때문에 실제로 포인터를 5개만 사용하는 경우에 95개의 포인터를 저장할 수 있는 메모리가 낭비된다. 결국 이 문제는 배열을 선언할 때 배열 크기를 반드시 상수로 적어야 하기 때문에 발생하는 것이다.

 

여러개의 1차원 포인터를 동적 할당하기

 

2차원 포인터가 가리키는 첫 번째 대상에는 1차원 포인터의 주소 값을 저장해서 사용하는 것이 가장 좋지만, 4바이트 크기의 메모리에 주소 값을 저장해서 사용해도 된다고 설명했다. 그래서 malloc 함수로 4바이트 메모리를 할당하여 2차원 포인터에 저장하면 그 메모리를 1차원 포인터처럼 활용할 수 있기 때문에 다음과 같이 적을 수 있다. 결국 다음 코드는 1차원 포인터 1개를 할당하는 내용이다.

 

short *pp;

pp = (short **)malloc(sizeof(short *))  /* pp = (short **)malloc(4)와 같은 표현*/

 

malloc 함수의 매개변수에 동적으로 할당할 메모리의 크기를 적을 때는 상수 뿐만 아니라 변수를 사용할 수 있다. 따라서 다음과 같이 short * 형식의 1차원 포인터를 n개 할당할 수 있고 이렇게 할당된 메모리는 *(pp + 0)부터 *(pp + n-1)까지 1차원 포인터 n개를 사용할 수 있다.

 

int n;

short **pp;

scanf("%d", &n)  /*사용할 1차원 포인터의 개수를 사용자에게 받음*/

pp = (short **)malloc(sizeof(short*)*n)   /*malloc 함수의 매개변수로 short * 형식의 1차원 포인터 n개를 할당한다.*/

 

이렇게 2차원 포인터와 malloc 함수를 사용하면 배열을 사용하는 것보다 코드는 좀 더 복잡해지지만 프로그램 실행중에 1차원 포인터의 개수가 바뀌어도 소스 파일을 다시 컴파일 할 필요가없다. 그리고 사용자가 메모리를 사용하고 싶은 크기만큼 선택할 수 있기 때문에 배열을 사용하는 것보다 메모리 효율이 좋다.

 

 

2차원 배열과 동적 메모리 할당

 

이제부터 2차원 배열과 같은 용도로 사용할 수 있는 메모리를 동적으로 할당하는 방법을 살펴보겠다. 내용을 한번에 이해하기는 어렵기 때문에, 하나의 프로그램을 차근차근 완성해 가면서 개념을 설명할 것이다. 우리가 만들 프로그램은 어떤 회사에서 직원들의 체력을 테스트한 결과를 저장하는 프로그램이다. 이 회사의 연령별 인원수는 20대가 4명, 30대가 2명, 40대가 3명이다. 1분간 윗몸 일으키기를 하고, 이 횟수를 연령별로 저장하는 데 필요한 메모리는 어떻게 할당하는 것이 가장 효과적일까?

 

2차원 배열로 메모리를 할당하는 방법

 

먼저 1명이 1분 동안 수행한 윗몸 일으키기 횟수를 저장하기 위한 자료형을 결정해야한다. 이 횟수는 정수 값이고 음수가 나올 수 없으며 200개를 넘을 가능성도 없다. 따라서 0~200 사이의 값을 저장하면 되기 때문에 unsigned char 자료형(0~255)으로도 충분히 데이터를 저장할 수 있다. 그 다음은 어떤 배열 구조를 사용할지 정해야 한다. 인원 분포를 살펴보면, 연령별 그룹이 3그룹이고 각 그룹별로 2명에서 4명까지 포함될 수 있다. 따라서 2차원 배열 구조가 효율적일것이다. 그런데 2차원 배열을 사용하려면 그룹별 크기가 같아야 하므로 최대 인원수인 4명으로 배열의 크기를 정해야 한다.

 

  첫 번째 사람 두 번째 사람 세 번째 사람 네 번째 사람
20대 1 2 3 4
30대 1 2    
40대 1 2 3  

unsigned char count[3][4];       /*3개의 그룹에 최대 4명의 사람을 관리*/

 

count[0][1] = 49; /*20대 연령의 두번째 사람*/

count[1][0] = 57; /*30대 연령의 첫번째 사람*/

count[2][2] = 77; /*40대 연령의 세번째 사람*/

 

 

2차원 배열로 연령별 윗몸 일으키기 횟수 관리하기

각 연령층에 포함된 인원수가 변한다면?

 

회사에 새로운 직원들이 입사하고 기존 직원이 퇴사해서 각 연령층에 포함된 인원수에 변화가 생길 수 있다는 조건을 추가해 보겠다. 이 조건을 만족하려면 limit_table에 고정되어 있는 4, 2, 3값을 정해놓지 않고 프로그램이 시작할 때 사용자에게 입력 받으면 된다. 하지만 count 배열에서 고려한 최대 인원수가 4명이기 때문에 사용자가 입력한 인원수가 4명보다 많아지면 count 배열에 문제가 생긴다. 이 문제를 어떻게 해결해야 할까? 배열은 요소의 개수를 상수로만 입력 받을 수 있기 때문에 최대 인원수를 4보다 큰 숫자로 변경하려면 소스 코드에서 count 배열의 크기를 직접 변경해야 한다. 그런데 이렇게 되면 소스 코드 또한 다시 컴파일해야 한다. 따라서 2차원 배열로는 이 문제를 해결할 수 없다.

 

포인터 배열을 사용하여 메모리 할당하기

 

 

조건 추가2 : 직원의 연령층이 다양해진다면?

마지막으로 위 예제에 조건을 하나 더 추가해 보겠다. 위 예제에서는 연령별 인원수 변동데 대한 조건만 처리하면 되었는데 20대, 30대, 40대 외에도 50대나 60대를 추가될 수 있다고 가정해 보겠다. 이렇게 되면 연령별 윗몸 일으키기 횟수를 저장할 포인터가 3개에서 4개 또는 5개로 변경될 수 있다는 뜻이 된다. 그리고 이 정보도 사용자에게 입력 받아서 처리할 수 있어야 하므로 연령층의 개수도 변수로 선언해서 사용해야 한다.

따라서 포인터 개수를 고정해서 만드는 포인터 배열은 이제 더 이상 사용할 수 없다. 그러면 이 문제를 어떻게 해결해야 할까?

 

 

2차원 포인터를 사용하여 2차원 데이터 형식 만들기

이 문제는 어렵게 생각할 필요가 없다. 앞에서 여러개의 1차원 포인터를 동적 할당하기에 대해 배웠다. 시작 부분에서 2차원 포인터와 malloc함수를 사용하면 1차원 포인터를 원하는 개수만큼 동적으로 할당할 수 있다고 설명했다. 이 상황에 맞춰서 윗몸 일으키기 횟수를 저장할 메모리를 만들어 보면 다음과 같다.

 

연령층의 개수가 사용자의 입력에 따라 달라질 수 있도록 만들어야 하기 때문에 연령층의 개수를 저장할 age_step 변수를 추가했다. 그리고 연령층별 인원수를 관리하던 p_limit_table 배열 크기는 더 이상 3으로 고정할 수 없기 때문에 age_step 변수를 malloc 함수에 사용하여 동적으로 메모리를 할당하도록 변경했다. 그리고 2차원 포인터 p도 연령층의 개수에 따라 만들어지는 1차원 포인터의 개수가 달라지기 때문에 age_step 변수를 malloc 함수에 사용하여 동적으로 메모리를 할당하도록 변경했다. 이와 같이 메모리를 구성하면 첫 예제에서 unsigned char count[3][4];라고 선언한 2차원 배열구조와 같은 용도로 사용할 수 있도록 2차원 포인터로 메모리를 할당한 것이다. 이렇게 2차원 포인터를 사용하면 소스 코드는 좀 복잡해지더라도 프로그램이 훨씬 유연해진다. 그러면 마지막으로 연령별 윗몸 일으키기 횟수를 관리하는 프로그램에 연령층을 추가할 수 있도록 2차원 포인터로 코드를 재구성해보자.

 

 

많은 프로그래머들이 2차원 포인터의 동적 할당 구조를 이해하는 것이 어려워서 사용하지 못하고 포기한다. 하지만 2차원 배열이나 포인터 배열을 사용하는 것보다 2차원 포인터를 이해하고 사용하면 프로그램을 훨씬 유연하게 만들 수 있으니 꼭 이해하고 넘어가자.

반응형