C: Pointer
Memory & Address
- 메모리에는 1byte 단위로 위치를 식별 가능한 물리적인 주소값이 존재한다.
- 주소 범위: 0 - 232(32bits) 또는 0 - 264(64bits)
- 주소 표기: 16진수(e.g. 0x100, 0x104, …)
- 주소 특성: 주소는 이미 정해진 값으로, 변경할 수 없기 때문에 상수이다.
- 메모리에 접근하는 방법 2가지
- 식별자 사용
- 식별자는 메모리 공간에 붙여진 이름(변수명, 배열명, 구조체명, 함수명 등)이다.
- 이름을 사용하여 메모리에 값을 저장하거나 저장된 값을 꺼내어 사용할 수 있다.
- 실제 주소값(포인터) 사용
- 프로그램을 실행할 때마다 로딩 위치가 달라지기 때문에 직접 주소를 고정시킬 수는 없다.
&
연산자를 사용해 식별자가 가진시작 주소값(첫 번째 byte)
을 얻을 수 있다.
- 식별자 사용
Variable
// int형 변수 선언(4byte 차지)
int a = 10;
int b = 11;
// 1️⃣ 변수의 포인터 구하기 → 변수명 앞에 주소연산자 &
printf("a의 포인터 : %p\n", &a); // 100 (a의 시작 주소값)
printf("b의 포인터 : %p\n", &b); // 104 (b의 시작 주소값)
// 2️⃣ 포인터가 가리키는 메모리를 사용(참조)하기 → 포인터 앞에 참조연산자 *
*&b = 20;
printf("b에 저장된 값 : %d\n", b); // 20 (포인터로 저장된 값)
int c = *&b;
printf("c에 저장된 값 : &d\n", c); // 20 (포인터로 꺼내온 값)
// 3️⃣ 포인터 저장하기 → 주소를 저장하는 포인터 변수 만들기
int *pa; // int형을 가리키는 포인터 변수
pa = &a; // a의 주소를 pa에 배정
*pa = 1; // pa가 가리키는 메모리(a)에 값 저장
printf("a의 주소 : %d\n", pa); // pa 자체는 a의 주소(100번지) 출력
printf("a의 값 : %d\n", *pa); // *pa로 a의 값(1) 출력
// ⭐️ 별(*pa)을 보고 찾아가면 보석(a)의 값을 알 수 있다
*️⃣
포인터 변수 선언 시 *
- 해당 변수의 타입을 포인터 타입으로 승격
- * 개수에 따라 주소 단계(레벨) 결정
// 일반 int 변수
int a = 10; // (사원a, 100번지)
int b = 50; // (사원b, 150번지)
// 포인터
int *pa; // (대리, 200번지) 1단계 포인터 → int형 변수 주소 저장
int **ppa; // (과장, 300번지) 2단계 포인터 → int* 주소 저장
int ***pppa; // (부장, 400번지) 3단계 포인터 → int** 주소 저장
int *pa = &a; // pa → 대리는 사원a의 주소(100번지)를 기억
int **ppa = &pa; // ppa → 과장은 대리의 주소(200번지)를 기억
int ***pppa = &ppa; // pppa → 부장은 과장의 주소(300번지)를 기억
포인터 사용(접근) 시 *
- 간접 참조(dereference) 연산자
- 포인터가 가리키는 값에 접근
- * 개수만큼 주소 단계(레벨)를 풀어 내려가 실제 값에 도달
*pa = 20; // *pa → a(값 변경) / 대리에서 사원a로 1단계 다운
*ppa = &b; // *ppa → pa → &b / 과장에서 대리로 1단계 다운 후 사원b 주소 저장
**ppa = 30; // **ppa → b(값 변경) / 과장에서 사원b로 2단계 다운
***pppa = 40; // ***ppa → b(값 변경) / 과장에서 사원b로 3단계 다운
1d Array
// 길이 3 1차원 배열 arr 선언
int arr[3] = {1,2,3};
// 배열 첫 번째 원소를 가리키는 포인터 변수 선언
int* p;
// 포인터 변수에 주소 배정하는 2가지 방법
p = &arr[0]; // 1. 주소 연산자로 'arr의 첫 번째 원소'의 주소 넘기기
p = arr; // 2. 배열명 'arr'는 배열의 시작 주소 '&arr[0]'로 자동 변환됨
100번지 104번지 108번지 arr [ 1 | 2 | 3 ] 4byte 4byte 4byte ------------------------------ <arr+i → i번째 원소의 주소> arr == p == &arr[0] == 100번지 arr+1 == p+1 == &arr[1] == 104번지 arr+2 == p+2 == &arr[2] == 108번지 <주소 arr+i가 가리키는 값 간접 참조 → i번째 원소 값> *(arr+0) == *(p+0) == arr[0] == 1 == *arr *(arr+1) == *(p+1) == arr[1] == 2 *(arr+2) == *(p+2) == arr[2] == 3
Array Pointer with 2d Array
// 2행 3열 2차원 배열 arr2 선언
int arr2[2][3] = { {1, 2, 3}, {4, 5, 6} };
int (*pp)[3]; // 열 3개짜리 배열 포인터 (길이가 3인 행 하나를 가리켜야 하기 때문)
pp = arr2; // arr2 == &arr2[0] (2차원 배열에서 인덱스 1개만 사용 시 행 시작 주소를 뜻함)
0열 1열 2열 0행 [[ 1(100번지) | 2(104번지) | 3(108번지) ] 1행 [ 4(112번지) | 5(116번지) | 6(120번지) ]] -------------------------------------------- pp == &arr2[0] == 0행 시작 주소 pp+1 == &arr2[1] == 1행 시작 주소 (int열 3개짜리, 12byte만큼 증가) *(pp) == 0행 자체 == 0행 시작 주소 *(pp+1) == 1행 자체 == 1행 시작 주소 *(pp+1)+0 == &arr2[1][0] == 1행 0열 주소 *(pp+1)+1 == &arr2[1][1] == 1행 1열 주소 *(*(pp+1)+0) == arr2[1][0] == 1행 0열 값 = 4 *(*(pp+1)+1) == arr2[1][1] == 1행 1열 값 = 5 ↑ ↑ 행 열
Pointer Array & Double Pointer
// 길이 3인 포인터 배열 선언(배열 포인터 선언 형식에서 괄호만 뺀 형태)
char *ary[3] = {"fig", "pear", "apple"};
// 이중 포인터 → 배열의 원소(포인터) → 문자열
char **ptr_ary = ary; // ary의 첫 번째 원소를 가리키는 char**
100번지 ary [ 200번지 | 300번지 | 400번지 ] ↓ ↓ ↓ fig pear apple --------------------------------- ptr_ary == &ary[0] == 100번지 ptr_ary+1 == &ary[1] *(ptr_ary) == ary[0] == 200번지("fig" 의 시작 주소) *(ptr_ary+1) == ary[1] == 300번지("pear"의 시작 주소) *(ptr_ary)+1 == &ary[0][1] == "fig" 의 2번째 문자 주소 == 201번지 *(ptr_ary+1)+2 == &ary[1][2] == "pear"의 3번째 문자 주소 == 302번지 *(*(ptr_ary)+1) == ary[0][1] == 201번지의 값 == 'i' *(*(ptr_ary+1)+2) == ary[1][2] == 302번지의 값 == 'a'
Leave a comment