C: Pointer
Memory & Address
- 메모리에는 1byte 단위로 위치를 식별 가능한 물리적인 주소값이 존재한다.
- 주소 범위: 0 - 232(32bits) 또는 0 - 264(64bits)
- 주소 표기: 16진수(e.g. 0x100, 0x104, …)
- 주소 특성: 주소는 이미 정해진 값으로, 변경할 수 없기 때문에 상수이다.
- 메모리에 접근하는 방법 2가지
- 식별자 사용
- 식별자는 메모리 공간에 붙여진 이름(변수명, 배열명, 구조체명, 함수명 등)이다.
- 이름을 사용하여 메모리에 값을 저장하거나 저장된 값을 꺼내어 사용할 수 있다.
- 실제 주소값(포인터) 사용
- 프로그램을 실행할 때마다 로딩 위치가 달라지기 때문에 직접 주소를 고정시킬 수는 없다.
&연산자를 사용해 식별자가 가진시작 주소값(첫 번째 byte)을 얻을 수 있다.
- 식별자 사용
Variable - int
// 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 = 5; // pa가 가리키는 메모리(a)에 값을 새로 저장
// 출력
printf("a의 주소 : %p\n", pa); // pa 자체는 a의 주소(100번지) 출력
printf("a의 값 : %d\n", *pa); // *pa로 a의 값(5) 출력
// ⭐️ 별(*pa)을 보고 찾아가면 보석(a)의 값을 알 수 있다
Variable - string, character
// 문자열 선언(맨 끝의 null 문자 '\0'까지 총 14byte 차지)
char str[] = "Hello, World!";
// 'W'를 가리키는 포인터 변수
char *p = str + 7;
// 출력
printf("전체 문자열 : %s\n", str); // Hello, World! (str[0] 부터)
printf("부분 문자열 : %s\n", p); // World! (str[7] 부터)
printf("문자 : %c\n", *p); // W
*️⃣
포인터 변수 선언 시 *
- 해당 변수의 타입을 포인터 타입으로 승격
- * 개수에 따라 주소 단계(레벨) 결정
// 일반 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단계 다운
Pointer to 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]'로 자동 변환됨
// 주소 출력 → arr == p == &arr[0] == 100번지
printf("arr + 0: %p\n", (void*)(arr + 0)); // arr[0]의 주소
printf("arr + 1: %p\n", (void*)(arr + 1)); // arr[1]의 주소
printf("arr + 2: %p\n", (void*)(arr + 2)); // arr[2]의 주소
printf("p + 0: %p\n", (void*)(p + 0)); // p가 가리키는 첫 번째 원소 주소 (arr[0]과 동일)
printf("p + 1: %p\n", (void*)(p + 1)); // p+1 → arr[1] 주소
printf("p + 2: %p\n", (void*)(p + 2)); // p+2 → arr[2] 주소
// 값 출력 → *arr == *p == arr[0] == 1
printf("*(arr + 0) = %d\n", *(arr + 0)); // arr[0] 값 (1)
printf("*(arr + 1) = %d\n", *(arr + 1)); // arr[1] 값 (2)
printf("*(arr + 2) = %d\n", *(arr + 2)); // arr[2] 값 (3)
printf("*(p + 0) = %d\n", *(p + 0)); // p[0] 값 → arr[0]과 동일
printf("*(p + 1) = %d\n", *(p + 1)); // p[1] 값 → arr[1]과 동일
printf("*(p + 2) = %d\n", *(p + 2)); // p[2] 값 → arr[2]과 동일
printf("arr[0] = %d\n", arr[0]); // 배열 인덱스로 접근
printf("arr[1] = %d\n", arr[1]);
printf("arr[2] = %d\n", arr[2]);
100번지 104번지 108번지
arr [ 1 | 2 | 3 ]
4byte 4byte 4byte
Pointer to 2d Array
// 2행 3열 2차원 배열 arr2 선언
int arr2[2][3] = { {1, 2, 3}, {4, 5, 6} };
// 열 3개짜리 배열 포인터 (길이가 3인 행 하나를 가리켜야 하기 때문)
int (*pp)[3];
// 포인터에 2차원 배열의 첫 행 주소 배정
pp = arr2; // arr2 == &arr2[0] (2차원 배열에서 인덱스 1개만 사용 시 행 시작 주소)
// 주소 출력
printf("pp + 0 : %p\n", (void*)pp); // &arr2[0] = 0행 시작 주소
printf("pp + 1 : %p\n", (void*)(pp + 1)); // &arr2[1] = 1행 시작 주소
printf("*(pp + 0): %p\n", (void*)(*(pp))); // 0행 자체 = 0행 시작 주소
printf("*(pp + 1): %p\n", (void*)(*(pp + 1))); // 1행 자체 = 1행 시작 주소
printf("*(pp + 1) + 0: %p\n", (void*)(*(pp + 1) + 0)); // &arr2[1][0] = 1행 0열 주소
printf("*(pp + 1) + 1: %p\n", (void*)(*(pp + 1) + 1)); // &arr2[1][1] = 1행 1열 주소
printf("*(pp + 1) + 2: %p\n", (void*)(*(pp + 1) + 2)); // &arr2[1][2] = 1행 2열 주소
// 값 출력
printf("*(*(pp + 1) + 0) = %d\n", *(*(pp + 1) + 0)); // 1행 0열 값 (4)
printf("*(*(pp + 1) + 1) = %d\n", *(*(pp + 1) + 1)); // 1행 1열 값 (5)
printf("*(*(pp + 1) + 2) = %d\n", *(*(pp + 1) + 2)); // 1행 2열 값 (6)
printf("arr2[1][0] = %d\n", arr2[1][0]); // 배열 인덱스로 접근
printf("arr2[1][1] = %d\n", arr2[1][1]);
printf("arr2[1][2] = %d\n", arr2[1][2]);
0열 1열 2열
0행 [[ 1(100번지) | 2(104번지) | 3(108번지) ]
1행 [ 4(112번지) | 5(116번지) | 6(120번지) ]]
Array of Pointers & Double Pointer
// 길이 3인 포인터 배열 선언 (배열 포인터 선언 형식에서 괄호만 뺀 형태)
char *ary[3] = {"fig", "pear", "apple"};
// 이중 포인터 → 배열의 원소(포인터) → 문자열
char **ptr_ary = ary; // ary의 첫 번째 원소를 가리킴(&ary[0]와 동일)
// 주소 출력
printf("ptr_ary + 0 : %p\n", (void*)(ptr_ary + 0)); // &ary[0] (100번지)
printf("ptr_ary + 1 : %p\n", (void*)(ptr_ary + 1)); // &ary[1] (108번지)
printf("*(ptr_ary + 0): %p\n", (void*)(*(ptr_ary + 0))); // ary[0] (200번지, "fig" 시작)
printf("*(ptr_ary + 1): %p\n", (void*)(*(ptr_ary + 1))); // ary[1] (300번지, "pear" 시작)
printf("*(ptr_ary + 0) + 1: %p\n", (void*)(*(ptr_ary + 0) + 1)); // &ary[0][1] (201번지, "fig"의 'i')
printf("*(ptr_ary + 1) + 2: %p\n", (void*)(*(ptr_ary + 1) + 2)); // &ary[1][2] (302번지, "pear"의 'a')
// 값 출력
printf("*(*(ptr_ary + 0) + 1) = %c\n", *(*(ptr_ary + 0) + 1)); // ary[0][1] = 'i'
printf("*(*(ptr_ary + 1) + 2) = %c\n", *(*(ptr_ary + 1) + 2)); // ary[1][2] = 'a'
printf("ary[0][1] = %c\n", ary[0][1]); // 배열 인덱스로 접근
printf("ary[1][2] = %c\n", ary[1][2]);
100번지 108번지 116번지
ary [ 200번지 | 300번지 | 400번지 ]
↓ ↓ ↓
fig pear apple
Struct Pointer ->
// Fruit 구조체 정의
typedef struct {
char name[20];
int quantity;
} Fruit;
// 구조체 변수 선언 및 정의
Fruit apple = {"Apple", 10};
// 1️⃣ 참조 연산자(.)로 직접 접근
printf("%s : %d개\n", apple.name, apple.quantity); // Apple : 10개
apple.quantity += 5; // 값 수정
// 2️⃣ 구조체 포인터(->)로 접근
Fruit *ptr = &apple; // apple 구조체의 주소 저장
printf("%s : %d개\n", ptr->name, ptr->quantity); // Apple : 15개
ptr->quantity += 10; // == (*ptr).quantity += 10
Leave a comment