프로그래밍 언어/C

[자료형(data type)] C 파생형 포인터와 배열

순정법사 2023.08.17

B. 파생형 포인터와 배열

1. 포인터와 배열

a. 포인터와 배열의 관계

배열의 이름은 그 값을 변경할 수 없는 상수라는 점을 제외하면 포인터와 같음

따라서 배열의 이름 = 포인터 상수

 

💙 포인터 상수?

⦁ 포인터 상수(constant pointer) : 포인터 변수가 가리키고 있는 주소 값을 변경할 수 없는 포인터
⦁ 상수 포인터(pointer to constant) : 상수를 가르키는 포인터를 의미

 

🤓 예제 : 포인터에 배열의 이름을 대입

#include <stdio.h>

int main(void)
{
	int arr[3] = {10, 20, 30};	// 배열 선언 
	int* ptr_arr = arr;			// 포인터에 배열의 이름을 대입함 
	
	printf("배열의 이름을 이용하여 배열 요소에 접근 : %d %d %d\n", arr[0], arr[1], arr[2]);
	printf("    포인터를 이용하여 배열 요소에 접근 : %d %d %d\n", ptr_arr[0], ptr_arr[1], ptr_arr[2]);
		
	printf("배열의 이름을 이용한 배열의 크기 계산 : %d\n", sizeof(arr));
	printf("    포인터를 이용한 배열의 크기 계산 : %d\n", sizeof(ptr_arr));
	return 0;
}

✨ 실행결과

배열의 이름을 이용하여 배열 요소에 접근 : 10 20 30
     포인터를 이용하여 배열 요소에 접근 : 10 20 30
배열의 이름을 이용한 배열의 크기 계산 : 12
     포인터를 이용한 배열의 크기 계산 : 8

 

 하지만 배열의 이름과 포인터에 크기 차이가 일어남 (배열은 배열의 크기만큼, 포인터는 포인터의 크기만큼)

 

b. 배열의 포인터 연산

배열의 이름과 포인터 사이에는 다음과 같은 공식이 성립

 

📘 공식

arr이 배열의 이름이거나 포인터이고 n이 정수일 때,

arr[n] == *(arr + n)

 

👉 1차원 배열뿐만 아니라 다차원 배열에서도 언제나 성립

 

🤓 예제 : 배열의 이름으로 포인터 연산을 수행하여 각각의 배열 요소에 접근

int arr[3] = {10, 20, 30}; // 배열 선언  

printf("          배열의 이름을 이용하여 배열 요소에 접근 : %d %d %d\n", arr[0], arr[1], arr[2]);
printf("배열의 이름으로 포인터 연산을 해 배열 요소에 접근 : %d %d %d\n", *(arr+0), *(arr+1), *(arr+2));

✨ 실행결과

          배열의 이름을 이용하여 배열 요소에 접근 : 10 20 30
배열의 이름으로 포인터 연산을 해 배열 요소에 접근 : 10 20 30

 

📝 예제 설명

 

💥 WARNING

배열 연산을 할때는 배열의 크기를 넘어서는 접근을 하지 않도록 주의
포인터 연산을 이용하여 계산하다가 배열의 크기를 넘어서는 접근을 하는 경우
C 컴파일러는 오류 발생 ❌, 하지만 잘못된 결과만을 반환

따라서 C언어로 프로그래밍할 때에는 언제나 배열의 크기에 주의!

 

 

2. 포인터 배열과 배열 포인터

a. 포인터 배열

🌟 포인터 변수를 저장할 수 있는 배열

 

🤓 예제 : 세 개의 int형 포인터 변수를 저장할 수 있는 포인터 배열을 선언

int i, arr_len;
int num01 = 10, num02 = 20, num03 = 30;
int* arr[3] = {&num01, &num02, &num03};	// int형 포인터 배열 선언 

arr_len = sizeof(arr)/sizeof(arr[0]);
for (i = 0; i < arr_len; i++)
{
    printf("%d\n", *arr[i]);
}

✨ 실행결과

10
20
30

 

b. 배열 포인터

🌟 배열을 가리킬 수 있는 포인터

 

배열 이름 = 포인터 이지만 따로 포인터를 정의해서 사용하는 이유는, 2차원 이상 배열을 가리킬 때 포인터를 통해 배열과 같은 인덱싱을 하기 위함 (배열 포인터는 1차원 배열에서는 의미 , 2차원 이상의 배열에서만 의미 )

 

즉, 포인터를 배열처럼 사용하기 위해 배열포인터를 정의해서 사용

 

 

🤓 예제 : 각 부분 배열의 시작 주소가 가리키는 메모리에 저장된 데이터를 출력

int arr[2][3] = 
{
    {10, 20, 30},
    {40, 50, 60}
};

printf("%d\n", *arr[0]);
printf("%d\n", *arr[1]);

✨ 실행결과

10

40

 

c. 2차원 배열의 포인터 연산

2차원 배열에서 포인터 연산 시 증가하는 값 = 행의 크기

 

📘 문법: 부분 배열의 크기 구하는 수식

sizeof(arr[0]) / sizeof(타입)

🤓 예제 : 포인터 증감시 증가하는 값의 크기

int arr[2][3];	//4바이트에 배열 행의 길이인 3를 곱한 12바이트

int (*pArr)[3]; //위 배열 포인터 선언하기

 

📝 예제 설명

 

위의 예제에서 배열 이름 arr의 타입은 정확하게 다음과 같이 정의

 

1) 배열의 이름 arr는 int형 데이터를 가리키는 배열 포인터

2) 이 배열 포인터는 포인터 연산 시 증감하는 값의 크기가 12바이트

 

또한, 위 예제의 배열 포인터는 다음과 같은 배열들을 가리킬 수 있음

int arr01[2][3];
int arr02[3][3];
int arr03[4][3];

...

 

🤓 예제 : 배열 포인터를 사용하여 배열과 같은 인덱싱 방법으로 배열 요소를 참조하기

#include <stdio.h>

int main(void)
{
	int arr[2][3] = 			// 배열의 선언
	{
		{10, 20, 30},
		{40, 50, 60}
	};
	int (*pArr)[3] = arr;		// 배열 포인터의 선언
	
	printf("%d\n", arr[1][1]);	// 배열 이름으로 참조
	printf("%d\n", pArr[1][1]);	// 배열 포인터로 참조
	return 0;
}

✨ 실행결과

50
50

 

💙 포인터 배열과 배열 포인터의 구분

int (*pArr)[3]; //int형 데이터를 저장할 수 있는 2차원 배열을 가리키는 배열 포인터
int* pArr[3];  // int형 데이터를 가리킬 수 있는 포인터 변수를 모아 놓은 배열을 가리키는 포인터 배열


❕ 전혀 다른 의미가 되기 때문에 괄호 유무가 중요함

 

c. main() 함수의 인수 전달

main() :  프로그램이 실행되면 제일 먼저 자동으로 호출되는 함수

 

📘 main() 함수의 원형

void(또는 int) main(int argc, char *argv[]);

//첫 번째 인수 : int형 변수인 argc, main() 함수에 인수로 전달되는 문자열의 개수를 명시
//두 번째 인수 : char형 포인터 배열인 argv, main() 함수에 인수로 전달된 각각의 문자열이 저장된 배열