Memory & Address

  • 메모리에는 1byte 단위로 위치를 식별 가능한 물리적인 주소값이 존재한다.
  • 주소 범위: 0 - 232(32bits) 또는 0 - 264(64bits)
  • 주소 표기: 16진수(e.g. 0x100, 0x104, …)
  • 주소 특성: 주소는 이미 정해진 값으로, 변경할 수 없기 때문에 상수이다.
  • 메모리에 접근하는 방법 2가지
    1. 식별자 사용
      • 식별자는 메모리 공간에 붙여진 이름(변수명, 배열명, 구조체명, 함수명 등)이다.
      • 이름을 사용하여 메모리에 값을 저장하거나 저장된 값을 꺼내어 사용할 수 있다.
    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

Double Pointer

int num = 10;           
int *ptr = #        // 단일 포인터 : num의 주소 저장
int **pptr = &ptr;      // 이중 포인터 : ptr의 주소 저장

printf("%p", ptr);      // 100 (ptr이 가진 값 = &num)
printf("%d", *ptr);     // 10  (ptr이 가리키는 주소에 있는 값)

printf("%p", *pptr);    // 100 (pptr이 가리키는 곳에서 꺼낸 값 = ptr = &num)
printf("%d", **pptr);   // 10  (pptr → ptr → num)
pptr → 200번지(&ptr)
        ↓
       ptr → 100번지(&num)
              ↓
             num → 10

*️⃣ 활용

포인터 변수 선언 시 *

  • 해당 변수의 타입을 포인터 타입으로 승격
  • * 개수에 따라 주소 단계(레벨) 결정
// 일반 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("%p\n", (void*)(arr + 0));         // arr[0]의 주소
printf("%p\n", (void*)(arr + 1));         // arr[1]의 주소
printf("%p\n", (void*)(arr + 2));         // arr[2]의 주소

printf("%p\n", (void*)(p + 0));           // p가 가리키는 첫 번째 원소 주소 (arr[0]과 동일)
printf("%p\n", (void*)(p + 1));           // p+1 → arr[1] 주소
printf("%p\n", (void*)(p + 2));           // p+2 → arr[2] 주소

// 값 출력 →  *arr == *p == arr[0] == 1
printf("%d\n", *(arr + 0));               // arr[0] 값 (1)
printf("%d\n", *(arr + 1));               // arr[1] 값 (2)
printf("%d\n", *(arr + 2));               // arr[2] 값 (3)

printf("%d\n", *(p + 0));                 // p[0] 값 → arr[0]과 동일
printf("%d\n", *(p + 1));                 // p[1] 값 → arr[1]과 동일
printf("%d\n", *(p + 2));                 // p[2] 값 → arr[2]과 동일

printf("%d\n", arr[0]);                   // 배열 인덱스로 접근
printf("%d\n", arr[1]);
printf("%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("%p\n", (void*)pp);                // &arr2[0] = 0행 시작 주소
printf("%p\n", (void*)(pp + 1));          // &arr2[1] = 1행 시작 주소

printf("%p\n", (void*)(*(pp)));           // 0행 자체 = 0행 시작 주소
printf("%p\n", (void*)(*(pp + 1)));       // 1행 자체 = 1행 시작 주소

printf("%p\n", (void*)(*(pp + 1) + 0));   // &arr2[1][0] = 1행 0열 주소
printf("%p\n", (void*)(*(pp + 1) + 1));   // &arr2[1][1] = 1행 1열 주소
printf("%p\n", (void*)(*(pp + 1) + 2));   // &arr2[1][2] = 1행 2열 주소

// 값 출력
printf("%d\n", *(*(pp + 1) + 0));         // 1행 0열 값 (4)
printf("%d\n", *(*(pp + 1) + 1));         // 1행 1열 값 (5)
printf("%d\n", *(*(pp + 1) + 2));         // 1행 2열 값 (6)

printf("%d\n", arr2[1][0]);               // 배열 인덱스로 접근
printf("%d\n", arr2[1][1]);
printf("%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

// 길이 3인 포인터 배열 선언 (포인터를 요소로 가지는 배열)
char *parr[3] = {"fig", "pear", "apple"};  // 배열 포인터 선언 형식에서 괄호만 뺀 형태

// 이중 포인터
char **ptr_parr = parr;                    // parr[0]의 주소 100번지를 값으로 저장       

// 주소 출력
printf("%p\n", (void*)(ptr_parr + 0));           // &parr[0] (100번지)
printf("%p\n", (void*)(ptr_parr + 1));           // &parr[1] (108번지)

printf("%p\n", (void*)(*(ptr_parr + 0)));        // parr[0] (200번지, "fig" 시작)
printf("%p\n", (void*)(*(ptr_parr + 1)));        // parr[1] (300번지, "pear" 시작)

printf("%p\n", (void*)(*(ptr_parr + 0) + 1));    // &parr[0][1] (201번지, "fig"의 'i')
printf("%p\n", (void*)(*(ptr_parr + 1) + 2));    // &parr[1][2] (302번지, "pear"의 'a')

// 값 출력
printf("%s\n", parr[0]);                         // "fig"
printf("%s\n", parr[1]);                         // "pear"     

printf("%c\n", *parr[0]);                        // 'f' (문자열 시작 주소의 문자 1개)
printf("%c\n", *parr[1]);                        // 'p'

printf("%c\n", *(*(ptr_parr + 0) + 1));          // 'i' (parr[0][1] 와 동일)
printf("%c\n", *(*(ptr_parr + 1) + 2));          // 'a' (parr[1][2] 와 동일)
        100번지   108번지   116번지
parr  [ 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

Tags:

Categories:

Updated:

Leave a comment