포인터

Lucete_sw·2022년 8월 24일

C언어

목록 보기
7/8
post-thumbnail

우리가 지금까지 값을 저장할 때는 변수를 사용했다.
이 변수는 컴퓨터의 메모리(스택)에 생성된다. 이후 원하는 값을 저장하거나 가져오며 변수를 사용한 것이다.

메모리 상에 변수를 생성한 것이라면, 이 변수의 메모리 주소가 존재할 것이다.
특정 변수의 메모리 주소를 구하기 위해서는 다음과 같은 &(주소 연산자)를 사용한다.

int num = 23;

printf("%p", &num);		// %p : 주소값(16진수) 출력 서식 지정자

 참고
서식 지정자 %p 대신 16진수로 출력하는 %x, %X를 사용해도 된다.
또한 메모리 주소는 고정된 것이 아니기 때문에 컴퓨터마다, 실행할 때마다 값이 달라진다.


|   포인터 변수 선언 및 초기화


C언어에서 메모리 주소는 포인터(pointer) 변수 에 저장한다.
즉, 포인터와 메모리 주소는 같은 의미이다.

  • 자료형 * 포인터이름;
  • 포인터 = &변수;

 참고
포인터 변수를 선언할 때, 자료형 뒤에 *(Asterisk)를 붙인다.
이때, * 의 위치는 아래 세 가지 경우 모두 가능하다. (모두 같은 뜻으로 사용한다.)

int* ptr;		// 자료형 쪽에 붙임
int * ptr;		// 자료형과 변수 가운데 삽입함
int *ptr;		// 변수 쪽에 붙임


|   역참조 연산자


포인터 변수에는 메모리 주소가 저장되어 있고, 포인터 변수를 통해 메모리 주소로 이동하여 값을 가져오고 싶다면 역참조(dereference) 연산자 * 를 사용한다.

  • * 포인터
  • * 포인터 = 값

-> 위와 같이 역참조 연산자는 값을 가져올 수도 있고, 값을 저장할 수도 있다.

 입력 

#include <stdio.h>

int main()
{
	int *ptr;
    int num = 22;
    
    ptr = &num;	// num 메모리 주소를 포인터 변수 ptr에 저장
    
    printf("%d", *ptr);	// 역참조 연산자로 num의 메모리 주소에 접근하여 값을 가져옴
    
    return 0;
}

 출력 

22

즉, 포인터는 변수의 주소만 가리키며 역참조는 주소에 있는 값을 접근한다! 😮


 주의
* 는 포인터는 선언할 때도 사용하고 역참조를 할 때도 사용한다.
둘을 구분할 때는, 각각의 역할에 대해서 생각하자.

  • 포인터를 선언할 때는 '이 변수가 포인터다'라고 알려주는 역할
  • 포인터에 사용할 때(선언 후에 사용할 때)는 '포인터의 메모리 주소를 역참조 하겠다'라는 뜻
int *ptr;			// 포인터 선언할 때
printf("%d", *ptr);	// 역참조 할 때


|   void 포인터


void 포인터는 자료형이 정해지지 않은 포인터이다. 다른 말로 '범용 포인터'라고 부른다.
이러한 특성 때문에 어떤 자료형으로 정의된 포인터든지 모두 저장할 수 있다.
반대로 다양한 자료형으로 정의된 포인터에도 void 포인터를 저장할 수 있다.

즉, 직접 자료형을 변환하지 않아도 암시적으로 자료형이 변환되는 방식이다.

  • void * 포인터이름;

 주의   void 포인터는 역참조할 수 없다.
이를 위해서는 아래와 같이 자료형을 변환한 뒤 역참조 해야 한다.

int num = 22;
void *ptr;

ptr = &num;
// printf("%d", *ptr);		// 컴파일 에러
printf("%d", *(int *)ptr);	// void 포인터를 int 포인터로 변환한 뒤 역참조

🤔 역참조도 할 수 없는 void 포인터를 왜 사용할까??

  • 함수에서 다양한 자료형을 받아들일 때
  • 함수의 반환 포인터를 다양한 자료형으로 된 포인터에 저장할 때
  • 자료형을 숨기고 싶을 때

-> 추후 업로드 예정


|   포인터와 1차원 배열


배열은 사실 첫 번째 요소의 주솟값만 담고 있다.
즉, 배열은 주솟값이기 때문에 포인터에 넣을 수 있다.

따라서 다음과 같이 포인터에 배열을 할당하고, 포인터에서 인덱스로 요소에 접근이 가능하다.

 입력 

#include <stdio.h>

int main()
{
	int arr[5] = { 1, 2, 3, 4, 5 };
    int *ptr = arr;		// 포인터에 배열 할당
    
    printf("%d %d\n", *ptr, *arr);	// *arr과 arr[0]은 동일하다
    
    printf("%d %d\n", ptr[4], arr[4]);	// 포인터에서 인덱스 사용 가능
    
    printf("%d %d\n", sizeof(arr), sizeof(ptr));	// 배열의 크기와 포인터 크기
    
    return 0;
}

 출력 

1 1
5 5
20 4

🤔 포인터에 배열을 할당했는데, 왜 배열과 포인터의 크기가 다를까??

배열에 sizeof 연산자를 사용하면 배열이 차지하는 전체 공간이 출력된다.
그러나 sizeof 연산자를 사용해 포인터의 크기를 구해보면 그냥 처음 선언했던 포인터의 크기만 나온다.

포인터는 그저 주솟값을 가리킨다는 점을 잊지 말자!



|   이중 포인터

☺️ 이중 포인터부터는 2차원 배열의 내용이 자주 등장하기 때문에, 아래 두 글을 먼저 읽고 오는 것을 추천한다.


이중 포인터는 다시 말해, 포인터의 포인터이다.

  • 자료형 ** 포인터이름;

 입력 

#include <stdio.h>

int main()
{
	int *ptr1;
    int **ptr2;
    int num = 22;
    
    ptr1 = &num;	// num의 메모리 주소 저장
    ptr2 = &ptr1;	// ptr1의 메모리 주소 저장
    
    printf("%d", **ptr2);	// 포인터 두 번 역참조하여 num의 메모리 주소 접근
    
    return 0;
}

 출력 

22


|   이중 포인터와 2차원 배열


이차원 배열을 포인터에 담으려면 다음과 같은 방법을 사용해야 한다.

  • 자료형 ( * 포인터이름)[가로크기];

즉, 포인터를 선언할 때 * 와 포인터 이름을 괄호로 묶어준 뒤 []에 가로 크기를 입력한다.

int (*ptr)[4];	// 가로 크기가 4인 배열을 가리키는 포인터

 참고   int  *ptr[4]
int ( *ptr)[4]와 달리, int형 포인터 4개를 담을 수 있는 배열을 뜻한다.
즉, 괄호가 있으면 배열을 가리키는 배열 포인터이고 괄호가 없으면 포인터를 여러 개 담는 포인터 배열이다.

int num1, num2, num3, num4;
int *ptr[4] = { &num1, &num2, &num3, &num4 };

 입력 

#include <stdio.h>

int main()
{
	int arr[2][3] = {
    	{ 1, 2, 3 },
        { 4, 5, 6 }
    };
    
    int (*ptr)[3] = arr;
    
    printf("%d %d\n", arr[1][1], ptr[1][1]);
    printf("%d %d\n", sizeof(arr), sizeof(ptr));
    
    return 0;
}

 출력 

5 5
24 4


출처 : 남재윤, ⌜C언어 코딩 도장⌟, 길벗, 2021

profile
개발자_기록

0개의 댓글