5. 포인터 - 배열
1. 필수 기반 지식
더보기
필수 기반 지식
- 2진수, bit, Byte, 16진수
- 프로그래밍) 빌드, 컴파일, 링킹(함수)
- 컴퓨터) 실행 개념과 메모리, CPU 동작구조
- 프로그래밍 언어, 어셈블리, 기계어
- C 언어의 자료형과 함수
- 스텍 메모리 동작 구조
배열과 포인터는 연산자 & * [ ] 만 이해하면 된다.
그리고, 배열과 포인터는 다르다.
하지만, 학습 초기에는 "같다" 고 가정하고 접근하여 익숙해지는게 먼저다.
연산자 우선순위
우선순위 | 연산자 유형 | 연산자 | 결합방향 | ||
높다 | 1 | 괄호, 배열, 구조체 | ( ) [ ] . → | 좌 → 우 | |
2 | 단항연산 | - ! ~ ++ -- (type) & * sizeof | 좌 ← 우 | ||
3 | 산술 연산 | 승제 | * / % | 좌 → 우 | |
4 | 가감 | + - | |||
5 | 비트 이동 연산 | >> << | |||
6 | 관계 연산 | 비교 | < <= > => | ||
7 | 등가 | == != | |||
8 | 비트 논리 연산 | & | ^ | |||
9 | 논리 연산 | && || | |||
10 | 삼항(조건) 연산 | ? : | 좌 ← 우 | ||
11 | 대입 연산 | 대입 | = | ||
복합 대입 | += -= /= %= | ||||
축약 비트 대입 | >>== <<== | ||||
낮다 | 12 | 나열 연산 | 좌 → 우 |
2. 포인터 연산자 사용법
더보기
2.1 포인터 변수 선언, 메모리 주소값 할당
#include <stdio.h>
void main()
{
int iNum = 90;
printf("iNum 변수의 정수값: %d\n", iNum); // num 변수에 담긴 "정수 값 90" 출력
printf("iNum 변수의 주소값: %p\n", &iNum); // num 변수가 참조하는 첫번째 Byte "메모리 주소" 출력
int* p_iNum = &iNum; // 실제로 p_iNum 같은 형태로 선언하지 않는다. 하나의 예시다.
printf("p_iNum 포인터 변수 값: %p\n", p_iNum); // num 변수에 담긴 "정수 값 90" 출력
printf("p_iNum 포인터 변수 역참조 값: %d\n", *p_iNum); // num 변수가 참조하는 첫번째 Byte "메모리 주소 값" 출력
}
2.2 포인터는 포인터다.
메모리 주소값은 주소값일 뿐이다. 변수는 변수다.
#include <stdio.h>
int main(void)
{
int iNum2 = 89; // int형 변수를 선언
int* p_iNum2 = &iNum2; // &iNum2 int형 포인터 주소값을, char형 포인터로 변환
printf("p_iNum: %p\n", p_iNum2); // 포인터 변수명 p_iNum 만 사용한다. "변수명"만 사용한다.
printf("p_iNum: %d\n", *p_iNum2); // 변수명 p_iNum 에 *역참조 연산자를 사용한다. -1, +10
return 0;
}
2.3 포인터 관련 연산자 상관관계 정리
포인터는 역참조를 위해 존재한다. 포인터의 역참조는 변수의 사용과 동일한 의미다.
개념 | 예시 | 포인터는 "가르킨다"는 의미다. |
변수 → 데이터 | iNum → 90 | 일반 변수 사용 방법 |
변수 + & → 포인터 + * → 데이터 | iNum + & → p_iNum + * → 90 | |
&변수 → 포인터 | (&iNum) → p_iNum | 메모리 주소 |
*포인터 → 데이터 | *p_iNum → 90 | 메모리 주소의 데이터를 포인터한다. |
*(&변수) → 데이터 | *(&iNum) → 90 | 포인터 역참조는 변수의 사용과 동일한 방법이다. |
변수 → 데이터 | iNum → 90 |
3. 포인터 연산
더보기



*포인터 선언할 때 사용한 자료형 크기만큼, (자료형*숫자) Byte 만큼 연산한다.
*예시의 변수들은 순차적으로 저장된다 가정하고, 배열은 연속되고 순차적인 특성을 가진다.

3.1 예시 1
#include <stdio.h>
int main(void)
{
char cNum0 = 65, cNum1 = 66, cNum2 = 67;
char *p_cNum0 = &cNum0, *p_cNum1 = &cNum1, *p_cNum2 = &cNum2;
// 변수명 : 주소값, 정수값
printf("cNum0 : %p, %d\n", p_cNum0, *p_cNum0); // cNum0 : 0x6F20, 65
printf("cNum1 : %p, %d\n", p_cNum1, *p_cNum1); // cNum1 : 0x6F21, 66
printf("cNum2 : %p, %d\n", p_cNum2, *p_cNum2); // cNum2 : 0x6F22, 67
printf("\n");
// 메모리에 순차적으로 저장된다고 가정 // 포인터연산 : 주소값, 역참조
printf("p_cNum0+0 : %p, %d\n", p_cNum0+0, *(p_cNum0+0)); // p_cNum0+0 : 0x6F20, 65
printf("p_cNum0+1 : %p, %d\n", p_cNum0+1, *(p_cNum0+1)); // p_cNum0+1 : 0x6F21, 66
printf("p_cNum0+2 : %p, %d\n", p_cNum0+2, *(p_cNum0+2)); // p_cNum0+2 : 0x6F22, 67
return 0;
}

3.2 예시 2
#include <stdio.h>
int main(void)
{
int cNum0 = 97, cNum1 = 98, cNum2 = 99;
int *p_cNum0 = &cNum0, *p_cNum1 = &cNum1, *p_cNum2 = &cNum2;
// 변수명 : 주소값, 정수값
printf("p_cNum0: %p, %d\n", p_cNum0, *p_cNum0); // p_cNum0: 0x6F20, 97
printf("p_cNum1: %p, %d\n", p_cNum1, *p_cNum1); // p_cNum1: 0x6F24, 98
printf("p_cNum2: %p, %d\n", p_cNum2, *p_cNum2); // p_cNum2: 0x6F28, 99
printf("\n");
// 메모리에 순차적으로 저장된다고 가정 // 포인터연산 : 주소값, 역참조
printf("p_cNum0+0: %p, %d\n", p_cNum0+0 *(p_cNum0+0)); // p_cNum0+0: 0x6F20, 97
printf("p_cNum0+1: %p, %d\n", p_cNum0+1, *(p_cNum0+1)); // p_cNum0+1: 0x6F24, 98
printf("p_cNum0+2: %p, %d\n", p_cNum0+2, *(p_cNum0+2)); // p_cNum0+2: 0x6F28, 99
return 0;
}

int 포인터의 연산은 int 자료형의 크기인 4byte 기준으로 연산된다.
4. 배열의 포인터 연산
더보기




4.1 char 변수, char 배열, char 포인터 역참조
#include <stdio.h>
int main(void)
{
char cNum1 = 65, cNum2 = 66, cNum3 = 67;
char *p_cNum1 = &cNum1, *p_cNum2 = &cNum2, *p_cNum3 = &cNum3;
// 변수명: 주소값, 정수값
printf("cNum1: %p, %d\n", p_cNum1, *p_cNum1); // cNum1: 0x7ff0, 70
printf("cNum2: %p, %d\n", p_cNum2, *p_cNum2); // cNum2: 0x7ff1, 71
printf("cNum3: %p, %d\n", p_cNum3, *p_cNum3); // cNum3: 0x7ff2, 72
printf("\n");
char cArr[3] = {65, 66, 67};
printf("cArr[0]: %p, %d, %d\n", cArr+0, cArr[0], *(cArr + 0)); // cArr[0] : 0xaaf0, 70, 70
printf("cArr[1]: %p, %d, %d\n", cArr+1, cArr[1], *(cArr + 1)); // cArr[1] : 0xaaf1, 71, 71
printf("cArr[2]: %p, %d, %d\n", cArr+2, cArr[2], *(cArr + 2)); // cArr[2] : 0xaaf2, 72, 72
printf("\n");
return 0;
}


*(cArr + 0) == cArr[0]
*(cArr + 1) == cArr[0+1]
*(cArr + 2) == cArr[0+2] // 배열로 사용하면 * 역참조 연산자가 생략되고, [ ] 연산자가 사용된 형태다.
arr <<<< 포인터, 주소값이 들어있는 배열명인 주소 값은 변경이 불가능한, 상수다.
arr 포인터 상수, 즉 포인터 변수처럼 메모리 주소가 들어있다.
arr[0] 의 주소값(첫번째 바이트 메모리 주소)이다.
4.2 int 변수, int 배열, int 포인터 역참조
#include <stdio.h>
int main(void)
{
int iNum0 = 97, iNum1 = 98, iNum2 = 99;
int *p_iNum0 = &iNum0, *p_iNum1 = &iNum1, *p_iNum2 = &iNum2;
// 변수명 : 주소값, 정수값
printf("p_iNum0: %p, %d\n", p_iNum0, *p_iNum0); // p_iNum0: 0x6F20, 97
printf("p_iNum1: %p, %d\n", p_iNum1, *p_iNum1); // p_iNum1: 0x6F24, 98
printf("p_iNum2: %p, %d\n", p_iNum2, *p_iNum2); // p_iNum2: 0x6F28, 99
printf("\n");
int iArr[3] = {97, 98, 99};
printf("iArr[0]: %p, %d, %d\n", iArr+0, iArr[0], *(iArr + 0)); // iArr[0]: 0x6F20, 97, 97
printf("iArr[1]: %p, %d, %d\n", iArr+1, iArr[1], *(iArr + 1)); // iArr[1]: 0x6F24, 98, 98
printf("iArr[2]: %p, %d, %d\n", iArr+2, iArr[2], *(iArr + 2)); // iArr[2]: 0x6F28, 99, 99
printf("\n");
return 0;
}


5. 함수와 배열
더보기
#include <stdio.h>
int main(void)
{
int arr_1d[4] = {
0,
};
//1
printf(" arr_1d : %p\n", arr_1d);
printf(" *arr_1d : %d\n", *arr_1d);
//2
printf("sizeof(arr_1d) : %ld\n", sizeof(arr_1d));
//3
int *ptr = &(arr_1d[0]);
printf(" &(arr_1d[0]): %p\n", ptr);
return 0;
}
// arr_1d : 0x7fff8f21f4d0
// *arr_1d : 0
// sizeof(arr_1d) : 16
// &(arr_1d[0]): 0x7fff8f21f4d0
#include <stdio.h>
int change_Arr_1d(int *arr)
{
printf("%p\n", arr);
printf("입력하세요 >> ");
scanf("%d", arr +0); // arr[0]
printf("입력하세요 >> ");
scanf("%d", arr + 1); // arr[1]
printf("입력하세요 >> ");
scanf("%d", arr + 2); // arr[2]
printf("입력하세요 >> ");
scanf("%d", arr + 3); // arr[3]
return 0;
}
int print_Arr_1d(int *arr_1d)
{
for (size_t j = 0; j < 4; j++)
{
printf("%d ", arr_1d[j]);
}
printf("\n");
return 0;
}
int main(void)
{
int arr_1d[4] = {
0,
};
printf("%p\n", arr_1d);
change_Arr_1d(arr_1d);
print_Arr_1d(arr_1d);
return 0;
}