본문 바로가기
Data Structure

C언어 1차원 배열과 2차원 배열 간단한 정리와 테스트

by SuldenLion 2023. 4. 1.
반응형

C Language 1ㆍ2 차원 배열의 사용과 특성에 대해 알아보겠다.

 

먼저 C의 1차원 배열 사용 관습이다.

#include <stdio.h>
#include <stdlib.h>

int getSum(int p[], int n)
{
	int sum = 0;
	int i;

	for (i = 0; i < n; i++) sum += p[i];

	return sum;
}

void print(int p[], int n)
{
	int sum = 0;
	int i;

	for (i = 0; i < n; i++) printf("%d ", p[i]);
	printf("\n");
}

main()
{
	int x[100];
	int i;
	int sum = 0;
	int n;

	printf("Type size of array : ");
	scanf("%d", &n);

	for (i = 0; i < n; i++) {
		x[i] = rand() % 100;
	}

	sum = getSum(x, n);

	printf("sum = %d\n", sum);

	print(x, n);
}

배열의 size를 입력받아 그 배열의 크기만큼 사용하고 싶다. 그러기 위해선 Automatic allocation으로 배열 x의 크기를 넉넉하게 잡고 사용하는게 일반적이다. 그리고 입력으로 받는 사이즈값이 그 크기보다 작아야한다. 

 

Stack 메모리 영역에 배열을 만들어 쓰는 것이다.

 

하지만 이 방법은 배열이 한정적인 크기를 갖는다는 단점이 있다. 100을 넘는 수를 담기를 원한다면 컴파일 에러가 날 것이다.

 

#include <stdio.h>
#include <stdlib.h>

int getSum(int p[], int n)
{
	int sum = 0;
	int i;

	for (i = 0; i < n; i++) sum += p[i];

	return sum;
}

void print(int p[], int n)
{
	int sum = 0;
	int i;

	for (i = 0; i < n; i++) printf("%d ", p[i]);
	printf("\n");
}

main()
{
	int *x;
	int i;
	int sum = 0;
	int n;

	printf("Type size of array : ");
	scanf("%d", &n);

	x = (int *)malloc(n*sizeof(int));

	for (i = 0; i < n; i++) {
		x[i] = rand() % 100;
	}

	sum = getSum(x, n);

	printf("sum = %d\n", sum);

	print(x, n);
}

그래서 C를 사용하는 프로그래머들은 배열을 사이즈에 구애받지 않고 사용하기 위해 포인터를 사용하여 Dynamic allocation 시킨 배열을 사용한다.

size로 6을 입력받으면 6 * int의 byte 크기만큼 메모리 할당을 받아 배열처럼 자유롭게 사용한다.

사이즈가 100이 들어오든 1000이 들어오든 전혀 문제없이 사용할 수 있다.

 

Java나 C#에서는 객체 생성 시 무조건 Dynamic allocation으로 생성해야 하지만, C나 C++에서는 Automatic allocation이나 Dynamic allocation을 선택적으로 할 수 있다는 특징이 있다.

 

그렇다면 이제 2차원 배열을 보자

위의 1차원 배열 프로그램과 마찬가지로 행과 열을 입력받아 랜덤값을 배열에 채워넣은 후 getSum()과 print()를 해볼 것이다. 여러가지 방법으로 접근해 볼 것이다.

 

우선 행과 열을 미리 정해놓고 랜덤값을 배열에 넣고 출력해볼 것이다.

int getSum(int p[], int row, int col) 
{
	int sum, i, j;
	sum = 0;

	for (i = 0; i < row; i++) 
		for (j = 0; j < col; j++)
			sum += p[i * col +j]; //p[i][j]

	return sum;
}

void print(int p[], int row, int col) 
{
	int i, j;

	for (i = 0; i < row; i++) {
		for (j = 0; j < col; j++) {
			printf("%5d ", p[i * col +j]);
		}
		printf("\n");
	}		
	printf("\n");
}

main()
{
	int x[3][4];
	int i, j, sum;

	for (i = 0; i < 3; i++) 
		for (j = 0; j < 4; j++)
			x[i][j] = rand() % 100;

	sum = getSum((int *)x,3,4);
	printf("sum = %d\n", sum);
	print((int *)x,3,4);
}

이 경우는 행과 열을 미리 3, 4로 정해놓고 배열을 자동할당 시킨다.

그리고 이 이차원 배열의 각 요소를 가져와 구하고 싶다.

하지만 C에서 함수에 2차원 배열을 인자로서 넘겨줄 수 없다.

> int getSum(int p[][], int row, int col) 이런식으로 사용 불가하다.

 

그래서 이런경우에 각 row의 첫 번째 요소의 주소에 있는 값부터 row단위로 값을 가져와야 한다.

 

C의 2차원 배열은 저렇게 테이블 형태로 이루어진 것처럼 보여도 사실 기억장소가 연달아 붙어서 존재하는데 아래 그림과 같을 것이다.

 

 

그렇다면 해당 배열의 모든 값을 확인하기 위해서는 p[i * col + j]를 해주면 순서대로 값을 참조하게 된다.

 

위 프로그램을 컴파일하고 실행시켜 보겠다.

 

별 문제 없이 모든값들의 sum을 구하고 2차원 테이블 형태로 출력시킨 듯 보인다.

 

하지만, 위의 방식은 치명적인 오류가 있다.

 

만약, 고정된 사이즈의 2차원 배열이 아닌, 우리가 직접 입력한 사이즈만큼 2차원 배열을 만들어 사용하고 싶다면 어떻게 될까? Automatic allocation이니 배열을 정의할 때 row와 col을 Max 사이즈로 넉넉하게 10을 준다고 하고 4행 5열짜리 배열을 만들어 쓴다고 가정해 보겠다.

 

int getSum(int p[], int row, int col) 
{
	int sum, i, j;
	sum = 0;

	for (i = 0; i < row; i++) 
		for (j = 0; j < col; j++)
			sum += p[i * col +j]; //p[i][j]

	return sum;
}

void print(int p[], int row, int col) 
{
	int i, j;

	for (i = 0; i < row; i++) {
		for (j = 0; j < col; j++) {
			printf("%5d ", p[i * col +j]);
		}
		printf("\n");
	}		
	printf("\n");
}

main()
{
	int x[10][10];
	int i, j, sum;
	
	int n, m;

	printf("Type row and col in one line : ");
	scanf("%d %d", &n, &m);

	for (i = 0; i < n; i++) 
		for (j = 0; j < m; j++)
			x[i][j] = rand() % 100;

	sum = getSum((int *)x,n,m);
	printf("sum = %d\n", sum);
	print((int *)x,n,m);
}

 

행과 열을 입력받아 그만큼 루프를 돌며 이차원 배열에 값을 추가해 주는 코드로 해보겠다.

컴파일하여 4와 5를 타입시켜주면 결과가 어떻게 될까?

 

바로 위와 같은 식으로 이상한 값들이 뒤섞이게 된다.

이것도 앞서 말한 2차원 배열의 기억장소에 대한 특성에 기인하는데, row 한줄이 끝나고 바로 연달아 다음 row의 값을 가리킴에 있다.

 

즉, x라는 배열은 10, 10의 Max size로 이미 틀이 잡혀있고

 

필요한 4행 5열이 위와 같은 식으로 값을 할당하지만

 

주소값 참조시 이런 식으로 해석한다.

그래서 엉터리 값들이 중간에 들어가게 된다.

 

 

그럼 다른 방법을 찾아보자. 

배열의 시작점을 포인터로 잡아서 row와 col을 입력받고 그에 맞게 malloc 시켜주는 것이다.

int getSum(int p[], int row, int col) 
{
	int sum, i, j;
	sum = 0;

	for (i = 0; i < row; i++) 
		for (j = 0; j < col; j++)
			sum += p[i * col +j]; //p[i][j]

	return sum;
}

void print(int p[], int row, int col) 
{
	int i, j;

	for (i = 0; i < row; i++) {
		for (j = 0; j < col; j++) {
			printf("%5d ", p[i * col +j]);
		}
		printf("\n");
	}		
	printf("\n");
}

main()
{
	int *x;
	int i, j, sum;
	
	int n, m;

	printf("Type row and col in one line : ");
	scanf("%d %d", &n, &m);

	x = (int *)malloc(n*m*sizeof(int));

	for (i = 0; i < n; i++) 
		for (j = 0; j < m; j++)
			x[i*m+j] = rand() % 100;

	sum = getSum(x,n,m);
	printf("sum = %d\n", sum);
	print(x,n,m);
}

 

3행 5열짜리 배열을 만들어 쓰고자 한다면

이런식으로 2차원 배열처럼 쓸 1차원 배열짜리를 가리키는 포인터를 만들어 사용하는 것이다.

이제 배열에 미리 Max size를 정함으로써 잘못된 기억장소를 참조하는 문제는 일어나지 않을 것이다.

 

컴파일하여 실행해보겠다.

 

 

그럴싸하게 결과가 출력되어 나왔다.

 

하지만 이것도 진정한 2차원 형태의 배열이 아닌 1차원 배열을 가지고 col 수 만큼의 덩어리를 row 취급하는 것과 다름없기 때문에 2차원 배열이라 하기엔 만족스럽지 못하다.

 

 

그럼 이제 최종적으로 Java나 C#에서 2차원 배열을 할당하는 방식과 같은 **(더블 포인터)로 2차원 배열을 만들어 보도록 하겠다.

 

#include <stdio.h>
#include <malloc.h>
#include <stdlib.h>

int getSum(int **p, int row, int col) 
{
	int sum, i, j;
	sum = 0;

	for (i = 0; i < row; i++) 
		for (j = 0; j < col; j++)
			sum += p[i][j];

	return sum;
}

void print(int **p, int row, int col) 
{
	int i, j;

	for (i = 0; i < row; i++) {
		for (j = 0; j < col; j++) {
			printf("%5d ", p[i][j]);
		}
		printf("\n");
	}		
	printf("\n");
}

int **allocInt(int row, int col) 
{
	int **p;
	int i;

	p = (int **)malloc(row*sizeof(int*));

	for (i = 0; i < row; i++) {
		p[i] = (int *)malloc(col*sizeof(int));
	}

	return p;
}

main()
{
	int **x;
	int i, j, sum;
	
	int n, m;

	printf("Type row and col in one line : ");
	scanf("%d %d", &n, &m);

	x = allocInt(n, m);

	for (i = 0; i < n; i++) 
		for (j = 0; j < m; j++)
			x[i][j] = rand() % 100;

	sum = getSum(x,n,m);
	printf("sum = %d\n", sum);
	print(x,n,m);
}

 

int 포인터를 가리키는 포인터 x를 만들어주고 입력받은 row와 col만큼 메모리를 할당시켜줄 것이다.

 

int **를 리턴하는 allocInt를 통해 다음과 같은 메모리 할당을 해준다.

row는 3, col은 4라 가정하겠다.

이제 각각의 row가 독립된 상태로 있는(row의 마지막 col 다음 주소값이 다음번 row의 첫번째 col 주소값과 연관이 없는) 2차원 배열을 만들어낸다.

 

이러한 결과가 나오며 사용에 있어서도 메모리 낭비가 없는 방법이다.

반응형

댓글