프로그래밍 언어/C

[자료형(data type)] C 파생형 포인터의 기본

순정법사 2023.08.17
더보기
자료형 중 파생형 포인터 공부 /  https://ddanzimind.tistory.com/32

 

A. 포인터

1. 포인터의 개념

a. 주소값의 이해

데이터의 주소값이란 해당 데이터가 저장된 메모리의 시작 주소를 의미

 

C언어에서는 주소값을 1바이트 크기의 메모리 공간으로 나누어 표현

 

b. 포인터란?

🌟 메모리의 주소값을 저장하는 변수

 

포인터 변수라고도 부름

 

💥 WARNING

포인터 연산자와 헷갈리면 안됨!
포인터 연산자는 포인터 변수에 있는 데이터를 가져오는 기호!

👉 포인터 연산자 공부하기 (기타)
 

연산자 총정리 -2 (비교, 논리, 비트, 기타 등)

5. 비교 연산자 a. 비교 연산자란? 🌟 피연산자 사이의 상대적인 크기를 판단하는 연산자 b. 비교 연산의 특징 두 개의 피연산자를 가지는 이항 연산자 피연산자들의 결합 방향 : ➡ c. 비교 연산

monamu.tistory.com

 

c. 포인터의 선언

📘 문법

타입* 포인터이름; //선언만 하는법

or 

타입* 포인터이름 = &변수이름;	//1) 선언 & 초기화 하기
타입* 포인터이름 = 주소값; 	//2) 선언 & 초기화 하기

 

  • 타입 : 포인터가 가리키고자 하는 변수의 타입
  • 포인터 이름 : 포인터가 선언된 후에 포인터에 접근하기 위해 사용

 

👉 포인터 선언 후 참조 연산자를 사용하기 전에 포인터는 반드시 초기화 되어야 함 (문법 아래와같이)

💥 그렇지 않으면 의도하지 않은 메모리 값을 변경하게 되어 오류를 발생시킴

 

🤓 예제

int n = 100;   // 변수의 선언

int *ptr = &n; // 포인터의 선언

 

🎨 도식화

 

 

포인터 변수 *ptr은 n의 주소를 저장함 (메모리에 있는 값 부터 주소를 저장한다)

 

d. 포인터의 참조

선언된 포인터는 참조 연산자(*)를 사용하여 참조

 

🤓 예제 1

int x = 7;        // 변수의 선언

int *ptr = &x;    // 포인터의 선언

int *pptr = &ptr; // 포인터의 참조

 

🎨 도식화

 

 

 

🤓 예제 2

int num01 = 1234;
double num02 = 3.14;  
int* ptr_num01 = &num01;
double* ptr_num02 = &num02;  


1) printf("포인터의 크기는 %d입니다.\n", sizeof(ptr_num01));
2) printf("포인터 ptr_num01이 가리키고 있는 주소값은 %#x입니다.\n", ptr_num01);
3) printf("포인터 ptr_num02가 가리키고 있는 주소값은 %#x입니다.\n", ptr_num02);
printf("포인터 ptr_num01이 가리키고 있는 주소에 저장된 값은 %d입니다.\n", *ptr_num01);
printf("포인터 ptr_num02가 가리키고 있는 주소에 저장된 값은 %f입니다.\n", *ptr_num02);

✨ 실행결과

1) 포인터의 크기는 8입니다.
2) 포인터 ptr_num01이 가리키고 있는 주소값은 0x7c255e4입니다.
3) 포인터 ptr_num02가 가리키고 있는 주소값은 0x7c255e8입니다.
포인터 ptr_num01이 가리키고 있는 주소에 저장된 값은 1234입니다.
포인터 ptr_num02가 가리키고 있는 주소에 저장된 값은 3.140000입니다.

 

🧡 1) 포인터의 크기가 8인 이유

포인터변수는 메모리에서 변수의 위치를 나타내는 주소를 다루는 변수 => 크기는 CPU에따라서 결정

32비트 CPU에서는 1word = 32bit = 4byte => 포인터 변수의 크기 또한 4byte

64비트 CPU에서는 2배라서 포인터의 크기가 8

 

2) 3) 에서는 포인터가 가리키는 변수의 타입에 따라 포인터의 타입도 같이 바꿈

👉 포인터의 타입은 참조 연산자를 통해 값을 참조할 때 참조할 메모리의 크기를 알려주는 역할

 

 

2. 포인터 연산

a. 한 포인터의 연산

  • 포인터와 👉 정수 연산 가능 ⭕ (실수 ❌)

 

🤓 예제

char* ptr_char = 0;
int* ptr_int = NULL;
double* ptr_double = 0x00;  

printf("포인터 ptr_char가 현재 가리키고 있는 주소값은 %#x입니다.\n", ptr_char);
printf("포인터 ptr_int가 현재 가리키고 있는 주소값은 %#x입니다.\n", ptr_int);
printf("포인터 ptr_double이 현재 가리키고 있는 주소값은 %#x입니다.\n", ptr_double);  

printf("포인터 ptr_char가 1 증가 후에 가리키고 있는 주소값은 %#x입니다.\n", ++ptr_char);
printf("포인터 ptr_int가 1 증가 후에 가리키고 있는 주소값은 %#x입니다.\n", ++ptr_int);
printf("포인터 ptr_double이 1 증가 후에 가리키고 있는 주소값은 %#x입니다.\n", ++ptr_double);

✨ 실행결과

포인터   ptr_char가 현재 가리키고 있는 주소값은 0입니다.
포인터    ptr_int가 현재 가리키고 있는 주소값은 0입니다.
포인터 ptr_double이 현재 가리키고 있는 주소값은 0입니다.
포인터   ptr_char가 1 증가 후에 가리키고 있는 주소값은 0x1입니다.
포인터    ptr_int가 1 증가 후에 가리키고 있는 주소값은 0x4입니다.
포인터 ptr_double이 1 증가 후에 가리키고 있는 주소값은 0x8입니다.

 

📝 예제 설명

 

1을 증가시키는 포인터 연산을 하면 주소는 포인터 타입에 따라서 달라짐

증가폭은 포인터가 가리키는 변수의 타입과 크기가 같게 됨!

뺄셈도 동일하게 적용됨

 

🎨 도식화

 

int형 포인터의 증가폭은 int형 타입의 크기인 4바이트만큼 증가

 

b. 두 포인터의 연산

  • 포인터끼리 + * /  👉 의미 ❌
  • 포인터끼리 - 👉 상대적 거리 ↔
  • 포인터끼리 대입 / 비교 ⭕

 

🤓 예제

int num01 = 10;
int num02 = 20;
int *ptr_num01 = &num01;
int *ptr_num02 = &num02;   

if (ptr_num01 != ptr_num02) // 포인터끼리의 비교 연산
{
    printf("포인터 ptr_num01이 가리키고 있는 주소에 저장된 값은 %d입니다.\n", *ptr_num01);
    printf("포인터 ptr_num02가 가리키고 있는 주소에 저장된 값은 %d입니다.\n", *ptr_num02);
    printf("포인터 ptr_num01과 ptr_num02는 현재 다른 주소를 가리키고 있습니다.\n\n");
    ptr_num02 = ptr_num01; // 포인터끼리의 대입 연산
}  

printf("포인터 ptr_num01이 가리키고 있는 주소에 저장된 값은 %d입니다.\n", *ptr_num01);
printf("포인터 ptr_num02가 가리키고 있는 주소에 저장된 값은 %d입니다.\n", *ptr_num02);  

if (ptr_num01 == ptr_num02) // 포인터끼리의 비교 연산
{
    printf("포인터 ptr_num01과 ptr_num02는 현재 같은 주소를 가리키고 있습니다.\n");
}

✨ 실행결과

포인터 ptr_num01이 가리키고 있는 주소에 저장된 값은 10입니다.
포인터 ptr_num02가 가리키고 있는 주소에 저장된 값은 20입니다.
포인터 ptr_num01과 ptr_num02는 현재 다른 주소를 가리키고 있습니다.


포인터 ptr_num01이 가리키고 있는 주소에 저장된 값은 10입니다.
포인터 ptr_num02가 가리키고 있는 주소에 저장된 값은 10입니다.
포인터 ptr_num01과 ptr_num02는 현재 같은 주소를 가리키고 있습니다.

 

 

3. 다양한 포인터

a. 포인터의 포인터

🌟 포인터 변수를 가리키는 포인터

 

참조 연산자(*)를 두 번 사용하여 표현, 이중 포인터라고 부름

 

포인터와 포인터의 포인터와의 동작 상 차이점

 

🤓 예제

int num = 10;              // 변수 선언
int* ptr_num = #       // 포인터 선언
int** pptr_num = &ptr_num; // 포인터의 포인터 선언   

printf("변수 num가 저장하고 있는 값은 %d입니다.\n", num);
printf("포인터  ptr_num가 가리키는 주소에 저장된 값은 %d입니다.\n", *ptr_num);
printf("포인터의 포인터 pptr_num가 가리키는 주소에 저장된 포인터가 가리키는 주소에 저장된 값은 %d입니다.\n",
    **pptr_num);

✨ 실행결과

변수 num가 저장하고 있는 값은 10입니다.

포인터 ptr_num가 가리키는 주소에 저장된 값은 10입니다.

포인터의 포인터 pptr_num가 가리키는 주소에 저장된 포인터가 가리키는 주소에 저장된 값은 10입니다.

 

 

b. void 포인터(void pointer)

🌟 대상이 되는 데이터의 타입을 명시하지 않은 포인터 = 주소값을 저장하는 것 이외에는 아무것도 할 수 없는 포인터

 

변수, 함수, 포인터 등 어떠한 값도 가리킬 수 ⭕

포인터 연산이나 메모리 참조 ❌

 

void 포인터를 사용 ➡ 반드시 먼저 사용하고자 하는 타입으로 명시적 타입 변환 작업을 거친 후에 사용

 

🤓 예제

int num = 10;         // 변수 선언
void* ptr_num = # // void 포인터 선언  

printf("변수 num가 저장하고 있는 값은 %d입니다.\n", num);
printf("void 포인터 ptr_num가 가리키는 주소에 저장된 값은 %d입니다.\n", *(int*)ptr_num);  

*(int*)ptr_num = 20;  // void 포인터를 통한 메모리 접근  
printf("void 포인터 ptr_num가 가리키는 주소에 저장된 값은 %d입니다.\n", *(int*)ptr_num);

✨ 실행결과

변수 num가 저장하고 있는 값은 10입니다.

void 포인터 ptr_num가 가리키는 주소에 저장된 값은 10입니다.

void 포인터 ptr_num가 가리키는 주소에 저장된 값은 20입니다.

 

c. 함수 포인터(function pointer)

프로그램에서 정의된 함수 => 실행될 때 모두 메인메모리에 올라감

이때 함수의 이름 = 메모리에 올라간 함수의 시작주소를 가리키는 포인터 상수가 됨

 

🌟 즉, 함수의 시작 주소를 가리키는 포인터 상수

 

포인터 타입은 함수의 반환값과 매개변수에 의해 결정 = 함수의 원형을 알아야 함

 

📘 문법

void func (int, int);	//함수 원형

void (*ptr_func) (int, int);	//함수 포인터

//함수 포인터 사용시 연산자의 우선순위 때문에 반드시 *ptr_func 부분을 괄호(())로 둘러싸야 함

 

d. 널 포인터(null pointer)

🌟 0이나 NULL을 대입하여 초기화한 포인터

 아무것도 가리키지 않는 포인터라는 의미