배열과 포인터

이인혁·2024년 5월 6일

C

목록 보기
9/23

1. 배열과 포인터의 연관성

C언어에서 배열포인터는 거의 세트로 묶을 정도로 많이 쓰입니다. 그 이유는 배열의 특징 때문입니다. 도대체 배열의 무슨 특징이 포인터와 연관성이 있을까요?

"배열명이 배열의 주소값이라 그런거 아니에요?"

이렇게 생각하셨다면 정답입니다. 배열의 주소값은 배열의 첫 번째 요소 즉, 배열명이기 때문입니다. 그래서 프로그래밍을 할 때 배열과 포인터는 서로를 대체하기도 합니다.
위의 그림처럼 arr은 배열의 주소값이 됩니다.

2. 포인터로 배열 가르키기

포인터 변수의 선언과 초기화

배열명은 배열의 첫 번째 요소의 주소값이라고 알고있습니다. 그럼 포인터는 무엇일까요? 포인터는 주소값을 저장하는 변수입니다. 그래서 포인터에 배열명 즉, 배열의 첫 번째 요소의 주소값을 저장할 수 있습니다.

ex)

typedef struct tagStudent {
	int age;
	int grade;
} Student;

//배열명 : 배열의 첫번째 요소의 주소값
int main() {
	short shortArray[10];
	int intArray[10];
	double doubleArray[10];
	Student studentArray[10];

	short* pshortArray = shortArray;
	int* pintArray = intArray;
	double* pdoubleArray = doubleArray;
	Student* pstudentArray = studentArray;

	return 0;
}

위의 예시처럼 배열의 주소값을 저장하는 포인터 변수를 선언할 수 있습니다. 대신 배열의 DataType의 주소값을 저장하는 것이기 때문에 꼭 같은 DataType의 포인터형을 선언해줘야 합니다. 구조체 DataType 배열에도 구조체 DataType의 포인터형으로 선언해줘야 합니다. 이렇게 배열명을 저장하면, 아래의 그림과 같이 포인터가 배열을 가르키는 형태라고 생각하시면 됩니다.
반복해서 얘기하지만 배열명 자체는 주소값이기 때문에, 따로 &(주소연산자)를 안붙이셔도 됩니다.

배열 포인터의 주소값의 연산

만약 포인터에 +1을 하면 어떻게 될까요? 애초에 주소값에 +1이 가능한지도 의문입니다. 그럼 직접 +1을 한번 해보겠습니다. 위에 선언에 이어서 코딩해보겠습니다.

	//주소값에 +,-를 하는 의미
	//주소값이 가르키는 메모리에 할당 공간의 사이즈만큼 증가하거나 감소한다는 의미입니다.

	pshortArray + 1;//short* + 1 //2byte증가
	pintArray + 1;//4byte증가

	printf("pshortArray = %p, pshortArray + 1 = %p\n", pshortArray, pshortArray + 1);
	printf("pintArray = %p, pintArray + 1 = %p\n", pintArray, pintArray + 1);
	printf("pdoubleArray = %p, pdoubleArray + 1 = %p\n", pdoubleArray, pdoubleArray + 1);
	printf("pstudentArray = %p, pstudentArray + 1 = %p\n", pstudentArray, pstudentArray + 1);
pshortArray = 000000F8A911F5C8, pshortArray + 1 = 000000F8A911F5CA
pintArray = 000000F8A911F5F8, pintArray + 1 = 000000F8A911F5FC
pdoubleArray = 000000F8A911F640, pdoubleArray + 1 = 000000F8A911F648
pstudentArray = 000000F8A911F6B0, pstudentArray + 1 = 000000F8A911F6B8

16진수를 이해하고 있다고 생각하고 설명하겠습니다.
pshortArray는 +1을 했을때, short형 저장공간의 크기인 2byte만큼 증가했습니다.
pintArray는 +1을 했을때, int형 저장공간의 크기인 4byte만큼 증가했습니다.
pdoubleArray는 +1을 했을때, double형 저장공간의 크기인 8byte만큼 증가했습니다.
pstudentArray는 +1을 했을때, int형 2개의 저장공간의 크기인 8byte만큼 증가했습니다.

여기서 알수있는 주소값에 +,-를 하는 의미는 주소값이 가르키는 메모리에 할당 공간의 사이즈만큼 증가하거나 감소한다는 의미입니다.

배열의 각각의 주소값은 그 배열의 DataType의 저장공간의 크기만큼 차이난다고 알고있습니다.

따라서 위의 그림처럼 표현이 가능합니다.

형변환

만약 double형 배열을 int형 포인터 변수에 int형으로 형변환을 한다면 무슨일이 일어나겠습니까? double형은 8byte이고, int형은 4byte입니다. 따라서 8byte였던 저장공간이 모두 4byte로 쪼개집니다.
ex)

doubleArray[10];
int* pintArray;
pintArray = (int*)doubleArray;

이런식으로 바꾼다면 double형 10개로 이루어진 배열이 int형 20개로 쪼개진 주소로 pintArray에 저장이 됩니다. 물론 위에처럼 선언하다고 해서 doubleArray자체가 바뀌는건 아닙니다. 그래서 그닥 도움이되는 예시는 아니지만, 이해하기는 쉬운 예시입니다.

3. 배열에 접근하기

배열식

우리가 흔히 아는 배열식으로 배열에 접근하는 방법입니다.

#include <stdio.h>
int main() {
	int array[10];
	for (int i = 0; i < 10; i++) {
		array[i] = i;  //배열식
	}
	for (int i = 0; i < 10; i++) {
		printf("array[%d] = %d\n", i, array[i]);
	}

	printf("\n\n");

    return 0;
}

결과

array[0] = 0
array[1] = 1
array[2] = 2
array[3] = 3
array[4] = 4
array[5] = 5
array[6] = 6
array[7] = 7
array[8] = 8
array[9] = 9

포인터식

이번에 배운 +,-연산으로 배열에 접근하는 방식입니다.

int main() {
	int array[10];

	for (int i = 0; i < 10; i++) {
		*(array + i) = i;  //포인터식
	}

	for (int i = 0; i < 10; i++) {
		printf("array[%d] = %d\n", i, *(array + i));
	}

	return 0;
}

결과

array[0] = 0
array[1] = 1
array[2] = 2
array[3] = 3
array[4] = 4
array[5] = 5
array[6] = 6
array[7] = 7
array[8] = 8
array[9] = 9

포인터 변수식

지금까지는 배열을 이용해서 접근을 했다면 포인터로 배열에 접근해 보겠습니다.

#include <stdio.h>
int main() {
	int array[10];
    
	int* parray = array;

	for (int i = 0; i < 10; i++) {
	   *(parray + i) = i;	
    }

	for (int i = 0; i < 10; i++) {
		printf("array[%d] = %d\n", i, *parray++);
	}
    return 0;
}

결과

array[0] = 0
array[1] = 1
array[2] = 2
array[3] = 3
array[4] = 4
array[5] = 5
array[6] = 6
array[7] = 7
array[8] = 8
array[9] = 9

번외) void*

void* 는 아직 형이 결정나지 않은 주소값입니다. 따라서 무슨 DataType이라도 다 저장이 가능합니다.

#include <stdio.h>
int main() {
	void* varray[3]; 
    
	int a = 1000;
	float b = 3.4f;
	double c = 100.4;

	varray[0] = &a;
	varray[1] = &b;
	varray[2] = &c;

	printf("array[0] = %d, array[1] = %f, array[2] = %lf\n", *(int*)varray[0], *(float*)varray[1], *(double*)varray[2]);

	return 0;
}

결과

array[0] = 1000, array[1] = 3.400000, array[2] = 100.400000

4. 문제풀이

가벼운 예제 1개만 해결해보도록 하겠습니다.

int형 배열 array[10]을 선언하고 0부터 9까지의 숫자로 초기화를 시킵니다. parray 변수에 배열 array의 맨끝쪽 주소를 저장합니다. parray를 이용해서 배열의 뒤쪽에서부터 앞쪽까지 출력해보세요.

코드

int main() {
	int array[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };

	int length = sizeof(array) / sizeof(array[0]);

	int* parray = array + (length - 1);

	
	for (int i = 0; i < length; i++) {
		printf("array[%d] = %d\n", length - 1 - i, *parray--);
	}

	return 0;
}

결과

array[9] = 9
array[8] = 8
array[7] = 7
array[6] = 6
array[5] = 5
array[4] = 4
array[3] = 3
array[2] = 2
array[1] = 1
array[0] = 0

포인터로 배열에 접근할 때 주의해야 할 점은 포인터로 배열보다 더 큰 범위의 주소값으로 접근하면 안됩니다. 만약 그 주소값에 엄청 중요한 변수가 저장되어있으면 변수가 바뀌어버리는 참사가 발생할 수도 있기 때문에 주의해야 합니다.

profile
게임개발공부블로그

0개의 댓글