[C] 포인터

JOOYEUN SEO·2023년 8월 24일

C 언어

목록 보기
8/13
post-thumbnail

1. 포인터 선언과 초기화

프로그램에서 기억공간(메모리 영역)을 가리키는 방법에는 두 가지가 있다.
① 기억공간에 붙여진 이름(식별자)을 사용
② 변수가 할당된 메모리의 실제 주소를 사용. ❗️주소는 상수(주소 자체는 절대 바뀔 수 없음)

②번의 실제 주소값을 포인터(pointer)라고 한다.
주소값은 byte 단위로 0번지부터 차례로 부여되며, 이를 통해 위치를 식별할 수 있게 된다.

ex) int형 변수 하나 선언 : 메모리에 4byte가 할당됨, 100(시작 주소값 예시) ~ 103번지의 주소값을 부여받음
→ 이 시작 주소값(첫번째 byte의 주소값)이 포인터이다.
⇒ 쉽게 말해 포인터는 값이 저장된 곳의 주소만 저장하는 특별한 변수이다.

주소연산자 &

포인터를 사용하기 위해서는 주소값을 알아야 한다. 식별자 앞에 &를 붙이면 그 변수가 할당된 기억공간의 시작 주소값을 구할 수 있다. scanf 함수를 사용할 때 항상 변수명 앞에 엠퍼샌드(&)를 붙이는 이유도 입력받은 데이터를 저장할 위치를 알려줘야 하기 때문이다.

참조(내용)연산자 *

어떤 포인터 변수가 가리키는 기억공간의 내용을 읽거나, 확보된 기억공간에 값을 대입하는 등 포인터를 통해 기억공간을 사용하는 것을 참조라고 한다. 붙은 *의 갯수에 따라 포인터 변수의 등급(클래스)이 달라지게 된다.

  • 포인터 변수를 선언할 때 : 자료형 *포인터 변수명;
// 포인터 변수 선언 시, 등급을 하나씩 올린다

int a = 10;				// 주소값 : 100(a)
int b = 20;				// 주소값 : 104(b)

int *pa = &a;			// 주소값 : 200(pa), 저장된 값 : 100(a) → 일반 변수의 주소
int **ppa = &pa;		// 주소값 : 300(ppa), 저장된 값 : 200(pa) → *의 주소
int ***pppa = &ppa;		// 주소값 : 400(pppa), 저장된 값 : 300(ppa) → **의 주소
  • 포인터 변수를 참조할 때 : *포인터 변수명;
// 포인터 변수 선언 이후 사용 시, 등급을 하나씩 내림

*pa = 100;			// 등급 1단계 하강, a의 값이 100으로 변경, *pa == a
*ppa = &b;			// 등급 1단계 하강, 일반 변수의 주소 저장 가능, *ppa == pa
**ppa = 100;		// 등급 2단계 하강, b의 값이 100으로 변경, **ppa == b
***pppa = 1000;		// 등급 3단계 하강, b의 값이 1000으로 변경, ***pppa == b

참조연산자를 포인터 앞에 붙이면 해당 포인터가 가리키는 변수의 값(내용)을 반환한다.
만약 참조연산자를 사용해 포인터가 가리키는 기억공간 안에 값을 대입하려면, 먼저 주소연산자로 해당 포인터가 어떤 공간을 가리키는지 위치를 정의해줘야 한다.
❗️주소를 알 수 없는 변수 또는 상수를 대입하면 임의의 기억공간에 값을 대입시켜버리는 결과 발생

차이점 정리일반 변수포인터 변수
선언int a; → 정수형 변수 a 선언int *a; → 포인터 변수 *a를 정수형으로 선언
값 할당int a = 100; → 변수 a에 100 할당*a = 100; → a 주소에 100 할당
주소 참조&a → 변수 a의 주소a → a 자체가 주소
주소 연산연산 불가능a--; → 포인터 1 감소

⌨️ 예제 1

#include <stdio.h>

int main() {
    int a = 20;     // int형 변수 a 선언과 초기화
    int *ptr_a;     // 포인터 변수 ptr_a 선언(a를 가리킬 것이므로 int형으로)
    ptr_a = &a;     // ptr_a에 a의 메모리 주소를 대입

    printf("a의 값 : %d\n", a);
    printf("a의 주소값 : %p\n", &a);
    printf("ptr_a에 저장된 값 : %p\n", ptr_a);
    printf("ptr_a가 가리키는 변수의 값 : %d\n", *ptr_a);    // a의 값이 출력됨
}

🖥️ 실행 결과

a의 값 : 20
a의 주소값 : 0x7ff7b6b1130c
ptr_a에 저장된 값 : 0x7ff7b6b1130c
ptr_a가 가리키는 변수의 값 : 20

포인터 변수를 선언하면 변수명(ptr_a)은 해당 자료형이 수록된 주소를 갖고 있게 된다. 따라서 연산자 &를 통해 주소값을 대입할 수 있다.


⌨️ 예제 2

#include <stdio.h>

int main() {
    int a = 10;
    int b = 20;
    int *ptr;

    ptr = &a;
    printf("ptr이 가리키는 변수에 저장된 값 : %d\n", *ptr);

    ptr = &b;
    printf("ptr이 가리키는 변수에 저장된 값 : %d\n", *ptr);
}

🖥️ 실행 결과

ptr이 가리키는 변수에 저장된 값 : 10
ptr이 가리키는 변수에 저장된 값 : 20

⌨️ 예제 3

#include <stdio.h>

int main() {
    int a = 10;
    int *ptr;

    ptr = &a;
    printf("a의 값 : %d\n", a);

    *ptr = 20;  // "a = 20"과 같은 결과(ptr이 가리키고 있는 변수)
    printf("a의 값 : %d\n", a);

🖥️ 실행 결과

a의 값 : 10
a의 값 : 20

⌨️ 예제 4

#include <stdio.h>

int main() {
    int a = 10;
    // 1번
    int *ptr_a = &a;    // 포인터 변수 선언과 초기화를 동시에(a 값을 포인터의 값으로 저장)
    printf("%d\n", a);

    // 2번
    *ptr_a = 20;        // 1번과 다른점은 a에 20을 대입한다는 것
    printf("%d\n", a);
}

🖥️ 실행 결과

10
20

형태가 비슷해서 헷갈릴 수 있는 예시


⌨️ 예제 5 (이중 포인터)

#include <stdio.h>

int main() {
    int a = 10;
    
    int *ptr;         // 변수 a를 가리키는 포인터 변수
    ptr = &a;

    int **ptr_ptr;    // 포인터 변수 ptr를 가리키는 포인터 변수
    ptr_ptr = &ptr;

    printf("a = %d\n", a);                  // a의 값
    printf("&a = %p\n", &a);                // a의 주소값

    printf("ptr = %p\n", ptr);              // 저장된 a의 주소값
    printf("&ptr = %p\n", &ptr);            // ptr의 (자체)주소값

    printf("ptr_ptr = %p\n", ptr_ptr);      // 저장된 ptr의 주소값

    printf("*ptr_ptr = %p\n", *ptr_ptr);    // ptr을 가리키고 있기 때문에 ptr의 값
    printf("**ptr_ptr = %d\n", **ptr_ptr);  // **ptr_ptr → *ptr → a의 값
}

🖥️ 실행 결과

a = 10
&a = 0x7ff7b451130c
ptr = 0x7ff7b451130c
&ptr = 0x7ff7b4511300
ptr_ptr = 0x7ff7b4511300
*ptr_ptr = 0x7ff7b451130c
**ptr_ptr = 10

이중 포인터는 포인터를 가리키는 포인터이다. "**로 선언된 이중 포인터가 가리키는 주소 → 자료가 있는 기억공간의 주소값 → 자료"의 순으로 자료를 참조하게 된다.


2. 포인터와 배열의 관계

⌨️ 예제 6

#include <stdio.h>

int main() {
    int a = 10;
    int *ptr_a = &a;
    
    printf("ptr_a의 값 : %p\n", ptr_a);
    printf("ptr_a + 1의 값 : %p\n",ptr_a + 1);
}

🖥️ 실행 결과

ptr_a의 값 : 0x7ff7badd630c
ptr_a + 1의 값 : 0x7ff7badd6310

포인터 + 정수값 → 포인터 시작 주소값 + (포인터가 가리키는 자료형 크기 × 정수값)
포인터에 정수값을 더할 때는 포인터가 가리키는 자료형의 크기를 곱해서 더해줘야 한다. 예시에서 출력된 값을 십진수로 나타내면 각 140732210913292와 140732210913296로, int형이기 때문에 1을 더하면 실제로는 4(4byte × 1)가 더해진다는 것을 알 수 있다.


⌨️ 예제 7

#include <stdio.h>

int main() {
    int arr[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

    printf("arr의 값 : %p\n\n", arr);			// 배열 arr 자체의 값
    for(int i = 0; i < 10; i++) {
        printf("&arr[%d] = %p\n", i, &arr[i]);	// 배열의 각 요소의 주소 출력
        printf("arr + %d = %p\n", i, arr + i);
    }
}

🖥️ 실행 결과

arr의 값 : 0x7ff7b00cc2e0

&arr[0] = 0x7ff7b00cc2e0
arr + 0 = 0x7ff7b00cc2e0
&arr[1] = 0x7ff7b00cc2e4
arr + 1 = 0x7ff7b00cc2e4
&arr[2] = 0x7ff7b00cc2e8
arr + 2 = 0x7ff7b00cc2e8
&arr[3] = 0x7ff7b00cc2ec
arr + 3 = 0x7ff7b00cc2ec
&arr[4] = 0x7ff7b00cc2f0
arr + 4 = 0x7ff7b00cc2f0
&arr[5] = 0x7ff7b00cc2f4
arr + 5 = 0x7ff7b00cc2f4
&arr[6] = 0x7ff7b00cc2f8
arr + 6 = 0x7ff7b00cc2f8
&arr[7] = 0x7ff7b00cc2fc
arr + 7 = 0x7ff7b00cc2fc
&arr[8] = 0x7ff7b00cc300
arr + 8 = 0x7ff7b00cc300
&arr[9] = 0x7ff7b00cc304
arr + 9 = 0x7ff7b00cc304 

먼저, 각 요소의 주소값이 4byte씩 늘어나고 있음을 볼 수 있다.

arr[10] → ☐☐☐☐☐☐☐☐☐☐

보통 배열을 설명할 때 위처럼 네모 박스를 그려서 각 요소가 n개의 이어진 기억공간을 차지하고 있다고 하는데, 예시를 통해 실제로도 메모리에서 연속으로 붙어있는 공간을 할당한다는 것을 알 수 있다. 이는 첫번째 배열 요소의 시작 주소값만 알면 나머지 기억공간들의 위치도 알 수 있게 된다는 것을 뜻한다. 그러므로 배열 자체의 값과 배열의 시작 주소값은 같은 값이고, 배열을 변수처럼 참조할 수도 있다. ❗️"배열=주소값"이므로 배열은 상수(arr++ 같은 연산 불가)

  • arr == &arr[0]
    • arr + 1 == &arr[0] + 1 == &arr[1]
    • arr + i == &arr[i]
  • *arr == *(arr + 0) == arr[0]
    • *(arr + 1) == arr[1]
    • *(arr + i) == arr[i]

⌨️ 예제 8

// 배열과 포인터의 활용
#include <stdio.h>

int main() {
    int arr[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

    for(int i = 0; i < 10; i++) {   // 배열의 요소 모두 출력
       printf("%d ", arr[i]);
    }
    printf("\n");

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

    for(int *ptr = arr; ptr < arr + 10; ptr++) { // 선언한 포인터 변수에 &arr[0]를 대입
        printf("%d ", *ptr);
    }
    printf("\n");
}

🖥️ 실행 결과

1 2 3 4 5 6 7 8 9 10
1 2 3 4 5 6 7 8 9 10
1 2 3 4 5 6 7 8 9 10

3. 배열 포인터(배열을 가리키는 포인터)

⌨️ 예제 9

#include <stdio.h>

int main() {
    int arr[3] = {1, 2, 3};
    int *ptr = arr;     			// 포인터 변수에 배열을 대입
    
    for(int i = 0; i < 3; i++) {
        printf("%d ", *(ptr + i));  // ptr대신 arr 사용 가능
    }
    printf("\n");

    for(int i = 0; i < 3; i++) {
        printf("%d ", ptr[i]);      // ptr대신 arr 사용 가능
    }
    printf("\n");

    for(int i = 0; i < 3; i++) {
        printf("%d ", i[ptr]);  	// ptr[i] == i[ptr]
    }
    printf("\n");
}

🖥️ 실행 결과

1 2 3
1 2 3
1 2 3

ptr[i]가 i[ptr]이 되는 과정(덧셈의 교환법칙으로 ptr과 i의 위치변경)

arr[i] == *(arr + i) == *(ptr + i) == *(i + ptr) == i[ptr]

왜냐하면 C 언어가 a[b] → *(a + b)로 인식하기 때문❗️실무에서는 쓰이지 않는 표현!

< 정리하기 (배열 포인터 ptr 대신 배열명 arr 대입 가능) >

  • ptr == &ptr[0]
  • *ptr == ptr[0]
  • ptr + 1 = ptr에 sizeof(*ptr)을 더한 값 (int형이라면 4만큼)

이렇게 배열 포인터 자리에 배열명을 대입해도 같은 값을 가지지만, 배열명과 배열 포인터가 같다는 것은 아니다. 두 경우 모두 정수값을 더하여 다른 배열 요소들을 참조할 수 있다는 공통점은 있지만, 배열명은 자기자신의 값을 바꿀 수 없고 배열 포인터는 바꿀 수 있다는 차이점이 있다. 배열명의 경우 상수이기 때문에 arr += 1; 과 같은 식을 쓸 수 없지만, 배열 포인터는 기억공간이므로 자신의 값을 바꾸면서 참조할 수 있다.


⌨️ 예제 10

#include <stdio.h>

int main() {
    int arr[3] = {1, 2, 3};

    printf("arr = %p\n", arr);
    printf("arr + 1 = %p\n", arr + 1);

    printf("&arr = %p\n", &arr);
    printf("&arr + 1 = %p\n", &arr + 1);
}

🖥️ 실행 결과

arr = 0x7ff7b53932fc
arr + 1 = 0x7ff7b5393300
&arr = 0x7ff7b53932fc
&arr + 1 = 0x7ff7b5393308

순서대로 십진수 변환 시 140168664477436, 140168664477440, 140168664477436, 140168664477448

arr와 &arr는 같은 값이지만 arr +1을 하면 4만큼 더해지는데, &arr +1을 하면 12만큼 더해진다.
그 이유는 arr == &arr[0] 이고, &arr는 배열 그 전체를 가리키기 때문이다. 그래서 배열 자체인 &arr에 +1을 하면 그 다음 배열로 넘어가게 된다. arr는 int형 요소 3개짜리인 배열이기 때문에 12byte의 크기를 가지고 있어서 12를 더하는 결과가 나온다.

(1) 배열 포인터의 선언과 초기화

⌨️ 예제 11

// 배열 포인터 선언과 초기화
#include <stdio.h>

int main() {
    int arr[3] = {1, 2, 3};

    int(*ptr_arr)[3];   // 길이 3인 int형 배열을 가리키는 포인터를 선언
    ptr_arr = &arr;     // 배열 arr의 주소값으로 초기화

    for(int i = 0; i < 3; i++) {		// 배열의 요소 모두 출력
        printf("%d ", (*ptr_arr)[i]);   // ptr_arr를 배열처럼 사용 가능
    }
    printf("\n");
}

🖥️ 실행 결과

1 2 3 

int(*ptr_arr)[3] = &arr; 처럼 선언과 초기화를 동시에 해도 되지만 예제처럼 따로 하는게 파악하기 편하다.


(2) 2차원 배열과 배열 포인터

포인터를 이용하여 1차원 배열 뿐만 아니라 2차원 이상의 배열의 자료도 참조 가능하다. 2차원 배열은 논리적으로는 행과 열이 존재하지만, 실제 기억공간에서는 일렬로 배열된다. 따라서 2차원 배열의 시작 주소를 포인터 변수에 할당 후, 포인터에 +나 -연산을 해서 다른 행을 참조할 수 있다. 배열 포인터로 선언할 경우 이차원 배열의 한 행을 가리키게 된다.

⌨️ 예제 12

// 이차원 배열과 포인터의 연관성
#include <stdio.h>

int main() {
    int arr[2][3] = {       // 이차원 배열 선언
        {1, 2, 3},  // 0행
        {4, 5, 6}   // 1행
    };

    printf("%d\n", sizeof(arr));        // 4byte × 6(1, 2, 3, 4, 5, 6)
    printf("%d\n", sizeof(arr[0]));     // 4byte × 3(1, 2, 3)
    printf("%d\n", sizeof(arr[0][0]));  // 4byte × 1(1)

    printf("%p\n", &arr);           // 이차원 배열의 전체 주소값
    printf("%p\n", &arr[0]);        // 0번째 행의 주소값
    printf("%p\n", &arr[0][0]);     // 0번째 행의 0번째 열의 주소값
}

🖥️ 실행 결과

24
12
4
0x7ff7b759e2f0
0x7ff7b759e2f0
0x7ff7b759e2f0

arr[0]은 이차원 배열의 첫번째 행을 arr[0]이라는 일차원 배열로 취급
arr[0][0]은 일차원 배열 arr[0]의 첫번째 요소 == 이차원 배열의 0행 0열

따라서 &arr == &arr[0] == &arr[0][0] 이 된다.


⌨️ 예제 13

// 일차원 배열과 이차원 배열에서의 포인터 사용 차이
#include <stdio.h>

int main() {
    int arr1[3] = {0};            // 일차원 배열 선언, 0으로 초기화

    printf("%p\n", &arr1);        // 일차원 배열 전체의 시작 주소를 가리킴
    printf("%p\n", arr1);         // 첫번째 요소의 시작 주소를 가리킴
    printf("%d\n", *arr1);        // 첫번째 요소 arr1[0]의 값 출력
    printf("\n");

    int arr2[2][2] = {0};         // 이차원 배열 선언, 0으로 초기화

    printf("%p\n", &arr2);        // 이차원 배열 전체의 시작 주소 가리킴
    printf("%p\n", arr2);         // 첫번째 행(일차원 배열) 전체의 시작 주소 가리킴 == &arr2[0]
    printf("%p\n", *arr2);        // 첫번째 행 첫번째 요소(0열)의 시작 주소 가리킴 == *(&arr2[0])

    printf("%p\n", &arr2[0]);     // 첫번째 행 전체의 시작 주소 가리킴 == arr2
    printf("%p\n", arr2[0]);      // 첫번째 행 첫번째 요소(0열)의 시작 주소 가리킴
    printf("%d\n", *arr2[0]);     // 첫번째 요소의 값 출력(시작 주소에 * 붙이면 값 자체)

    printf("%p\n", &arr2[0][0]);  // 첫번째 행 첫번째 요소(0열)의 시작 주소 가리킴 == arr2[0]
    printf("%d\n", arr2[0][0]);   // 이차원 배열 0행 0열에 들어있는 값 자체 출력
    printf("\n");

    printf("%p\n", arr2[0] + 1);  // == &arr2[0][0] + 1 → 첫번째 행의 두번째 요소의 시작 주소
    printf("%p\n", &arr2[0] + 1); // == arr2 + 1 → 두번째 행 전체의 시작 주소
    printf("%p\n", arr2 + 1);     // 바로 윗 줄과 같음
    printf("%p\n", &arr2 + 1);    // == 이차원 배열 전체 + 1 → 다음 배열의 전체의 시작 주소
}

🖥️ 실행 결과

0x7ff7b16092fc
0x7ff7b16092fc
0

0x7ff7b16092e0
0x7ff7b16092e0
0x7ff7b16092e0
0x7ff7b16092e0
0x7ff7b16092e0
0
0x7ff7b16092e0
0

0x7ff7b16092e4
0x7ff7b16092e8
0x7ff7b16092e8
0x7ff7b16092f0

⌨️ 예제 14

// 이차원 배열을 순회하며 원소를 출력하는 프로그램 1
#include <stdio.h>

int main() {
    int arr[3][2] = {		// 3행 2열 이차원 배열 선언
        {1, 2},
        {3, 4},
        {5, 6}
    };
    
    int(*ptr)[2] = arr;     /* 길이 2의 배열 포인터 선언 후 arr(== &arr[0])을 대입.
    						배열 포인터를 이차원 배열처럼 사용할 수 있음 */

    for(int i = 0; i < 3; i++) {
        for(int j = 0; j < 2; j++) {
            printf("%d ", ptr[i][j]);   // ptr[i][j] == arr[i][j]
        }
        printf("\n");
    }
}

🖥️ 실행 결과

1 2
3 4
5 6

int형 배열 arr[3][2]에서의 포인터 변수 ptr

arr[0][0] (4byte)
&arr[0] = a(시작주소값)
ptr
arr[0][1] (4byte)


arr[1][0] (4byte)
&arr[1] = a + (8 × 1)
ptr + 1
arr[1][1] (4byte)


arr[2][0] (4byte)
&arr[2] = a + (8 ×2)
ptr + 2
arr[2][1] (4byte)


  • 검증 1 : ptr[i] == arr[i]
    만약 ptr == arr 라면, ptr == &arr[0]
    그렇다면 ptr[i] == *(ptr + i) 가 되어야 한다.
    위의 표에서 볼 수 있듯 ptr + i → i번째 행의 주소를 가리킴
    → *(ptr + i)는 arr의 i번째 행이 가리키는 주소에 있는 값을 불러옴
    → ptr[i]는 배열의 i번째 저장공간에 있는 값을 불러옴
    따라서 *(ptr + i) == ptr[i]는 성립된다.

  • 검증 2 : ptr[i][j] == arr[i][j]
    검증 1에서 ptr[i] == arr[i]를 확인했기 때문에, 자연스럽게 이 조건도 참이라는 것을 알 수 있음

  • 검증 3 : ptr == arr
    검증 1, 2를 통해 ptr과 arr는 호환된다는 것을 알 수 있음

⌨️ 예제 15

// 이차원 배열을 순회하며 원소를 출력하는 프로그램 2
#include <stdio.h>

int main() {
    int arr[2][3] = {{1, 2, 3}, {4, 5, 6}};

    for(int(*row)[3] = arr; row < arr + 2; row++) {		// row에 arr(&arr[0]) 저장 
        for(int *col = *row; col < *row + 3; col++) {	// col에 *row 저장
            printf("%d ", *col);
        }
        printf("\n");
    }
}

🖥️ 실행 결과

1 2 3 
4 5 6 

arr[2][3]col(0열)col + 1col + 2
row(0행)1(4byte)2(4byte)3(4byte)
row + 14(4byte)5(4byte)6(4byte)
  • row : 요소 3개짜리 길이의 일차원 배열 하나를 가리킨다(해당 행의 시작 주소).
    row → arr 전체를 가리킴 == 첫번째 행(0행)
    row + 1 → 12byte가 더해져서 두번째 행을 가리킨다

  • col : *row를 대입하여 row가 가리키는 행의 첫번째 원소를 가리킨다. 즉, 주소값은 &(*row)[0] 부터 시작한다.
    col → row가 가리키는 행의 첫번째 열을 가리킴(0열)
    col + 1 → 4byte가 더해져서 두번째 열을 가리킨다
    col + 2 → 4byte가 더해져서 세번째 열을 가리킨다

4. 포인터 배열(포인터들의 배열)

여러 개의 동일한 형의 포인터 변수들을 배열로 선언한 것으로, 포인터들의 집합이라고 할 수 있다.
배열 포인터와 이름도 선언 방법도 비슷해서 헷갈릴 수 있지만 둘은 완전히 다르다.

< 배열 포인터와 포인터 배열의 차이 예시>

  • 배열 포인터
                                  100번지  104번지 ...
    [  100번지  ]   →   [     1      ][     2     ][     3     ][     4     ] (배열 arr)
       (포인터)
  • 포인터 배열
         PA[0]          PA[1]          PA[2]          PA[3]
    [  100번지  ][  112번지  ][  120번지  ][  136번지  ] (배열 PA)
       (포인터)       (포인터)       (포인터)       (포인터)
            ↓                 ↓                  ↓                 ↓
     (포인터들은 각자 어떤 배열 또는 변수들을 가리킴)

⌨️ 예제 16

#include <stdio.h>

int main() {
    int a = 10, b = 20, c = 30, d = 40;

    int *PA[4];    // 길이 4인 포인터 배열 선언(배열 포인터 선언 형식에서 괄호만 뺀 형태)

    PA[0] = &a;	   // 변수 a의 주소를 포인터 배열 첫번째 요소에 저장
    PA[1] = &c;
    PA[2] = &d;
    PA[3] = &b;

    printf("%d %d %d %d\n", *PA[0], *PA[1], *PA[2], *PA[3]);
}

🖥️ 실행 결과

10 30 40 20 

⌨️ 예제 17

#include <stdio.h>

int main() {
    int arr[4] = {1, 2, 3, 4};
    int *PA[4];

    for(int i = 0; i < 4; i++) {
        PA[i] = &arr[i];          // 배열 arr의 원소의 주소를 포인터 배열 요소에 대입
    }
    for(int i = 0; i < 4; i++) {
        printf("%d ", *PA[i]);    // 포인터 배열 요소의 내용 == 각 주소가 가리키는 기억공간의 값
    }
    printf("\n");
}

🖥️ 실행 결과

1 2 3 4

⌨️ 예제 18

#include <stdio.h>

int main() {
    char str[] = "Hello";   // 길이 6(\0 포함)인 char형 배열 선언(문자열 선언)
    printf("%s\n", str);    // 배열 str == &str[0] (문자열의 첫 번째 문자를 의미)
}

🖥️ 실행 결과

Hello

< %s 의 기능 >
첫번째(0번째) 문자의 주소를 가리키는 포인터를 입력하면, null 문자가 나올 때까지 해당 문자열의 모든 문자들을 차례대로 출력한다.


⌨️ 예제 19

#include <stdio.h>

int main() {
    char strings[3][10] = {"iPhone", "AirPods", "Mac"}; // 이차원 배열로 여러 개의 문자열 선언
    char *p_str[3];     // 길이 3짜리 포인터 배열 선언

    for(int i = 0; i < 3; i++) {
        p_str[i] = &strings[i][0];  // p_str의 각 포인터가 strings의 i행 0열을 가리킴
    }

    for(int i = 0; i < 3; i++) {
        printf("%s\n", p_str[i]); 	// strings[i] 또는 &strings[i][0]와 같은 결과
    }
}

🖥️ 실행 결과

iPhone
AirPods
Mac

배열 arr == &arr[0] 공식을 이용해서 배열 strings에 첨자를 붙이면, strings[i] == &(strings[i])[0] 이 되기 때문에 for문으로 i행과 0열을 한번에 가리킬 수 있다.




▷ 김형근·곽덕훈·정재화, 『C프로그래밍』, 한국방송통신대학교출판문화원, 2020
▷ 서현우, 『뇌를 자극하는 C 프로그래밍』, 한빛미디어, 2005
▷ 두들낙서, “두들낙서의 C/C++ 강좌”, https://youtube.com/playlist?list=PLlJhQXcLQBJqywc5dweQ75GBRubzPxhAk&si=s8RNpRcpJTi_Lz8M

0개의 댓글