본문 바로가기
c

15_포인터(Pointer)

by RongBee 2023. 4. 17.

Pointer는 가리키다라는 뜻이 있다. 

 

C에서 다른 변수, 혹은 그변수의 메모리 공간 주소를 저장하는 변수로 사용된다.

 

메모리는 컴퓨터의 물리적인 공간으로, 변수는 이 메모리 공간에 값을 저장하고 접근하는데 사용된다.

 

포인터가 가리키는 값을 가져오는 것을 역참조라고 한다.

 

포인터 p라는 변수에 a라는 

 

포인터 변수 p  = (*p)
*p의 주소 : 3000
*p =  &a[0] = 1000
주소 : 3004  주소 : 3008 주소 : 3012

 

먼저 int a[4]의 변수가 있고,

 

p라는 포인터 변수에 a의 첫번째 인덱스 값을 넣는다.

 

int *p = &a[0]; (포인터 변수 p는 a[0]의 주소 1000을 가리키고 있다.)

 

-> 포인터 변수의 주소와 int a[4]의 주소는 임의로 설정한 주소이다.

 

int a[4]
a[0] 주소 : 1000
a[0]의 값 : 15
 a[1] 주소 : 1004
a[1]의 값 : 25
a[2] 주소 : 1008
a[2]의 값 : 35
a[3] 주소 : 1012
a[3]의 값 : 45

 

그리고 for문을 통해 출력해보면 a[0]부터 a[3]까지의 주소가 나온다.

 

 

 

 

 

이번엔 int Num, p, p2의 어떤 데이터를 저장하고,

어떤 데이터를 주고 받는지 알아보자.

 

 

int Num = 5;

printf("Num이 저장하고 있는 값은 %d 입니다.\n", Num);    // 5 
printf("Num의 위치(주소값)는 %p 입니다.\n ",   &Num);    // Num의 주소값

int* p = &Num; // 포인터 변수 p는 Num의 주소값을 저장하는 정수형 메모리 공간이다.

printf("p가 저장하고 있는 값은 %p 입니다.\n", p);  // Num의 주소값 출력
printf("Num의 위치(주소값)는   %p 입니다.\n",&p);  // p의 주소값 출력 

printf("p가 가르키고 있는(주소값)은 %d 입니다.\n", *p); // p가 가르키고 있는 값은 5출력

*p = 10; // p가 가리키는 것을 10으로 초기화
printf("  p가 가르키고 있는(주소값)은 %d 입니다.\n", *p); // p가 가리키는 것의 값은 10 출력
printf("Num이 저장하고 있는(주소값)은 %d 입니다.\n",Num); // Num이 저장하고 있는 값은 10 출력

*p -= 1; // p가 가리키는 것에 -1
printf("  p가 가르키고 있는(주소값)은 %d 입니다. \n", *p);  // -1된 p가 가리키는 것에 값은 9 출력
printf("Num이 저장하고 있는(주소값)은 %d 입니다. \n", Num); // Num이 저장하고 있는 값은 9 출력

int* p2 = &Num; // 포인터 변수 p2는 Num의 주소값을 저장하는 정수형 메모리 공간이다.
*p2 = 100;      // p2가 가리키는 것을 100으로 초기화
printf(" p가 가르키고 있는(주소값)은   %d 입니다.\n", *p); // p가 가리키고 있는 값은 100 출력
printf(" p가 가르키고 있는 변수의 값은 %d 입니다.\n", *p); // p가 가리키고 있는 값은 100 출력
printf("Num이 저장하고 있는(주소값)은  %d 입니다.\n" ,Num);// Num이 저장하고 있는 값은 100 출력

 

 

포인터 변수 p, p2는 Num의 주소값을 저장하고 있다.

 

p와 p2가 가리키는 것을 초기화해준다면 p가 가리키는 것이나,

 

p2가 가리키는 것이나 같은 것을 볼 수 있다.

 

 

그렇다면 이번엔 내가 배웠던 배열 중 배열의 이름배열의 시작 주소를 의미한다.

 

예제를 통해 배열과 주소의 관계를 알아보자.

 

int Arr[5] = {1, 2, 3, 4, 5}; // 배열의 이름 Arr은 배열의 시작 주소이다.

printf("Arr  : %p\n ", Arr);  // 이를 출력시 Arr의 첫번째 주소가 나오게 된다.
printf("*Arr : %d\n ",*Arr);  // 이를 출력시 Arr이 가르키는 첫번째 요소의 값인 1이 나오게 된다. *(Arr + 0) = 1

for(int i = 0; i < 5; ++i) printf("Arr[%d] 의 주소 : %p\n" , i, &Arr[i]);
// 이를 출력시 
// Arr[0] = 첫번째 요소의 주소값
// Arr[1] = 첫번째 요소의 주소값 + 4
// Arr[2] = Arr[1] = 첫번째 요소의 주소값 + 4 + 4
// Arr[3] = Arr[1] = 첫번째 요소의 주소값 + 4 + 4 + 4
// Arr[4] = Arr[1] = 첫번째 요소의 주소값 + 4 + 4 + 4 + 4

for(int i = 0; i < 5; ++i) printf("Arr  + %d : %p\n" , i, Arr + i);
// 이를 출력시 
// Arr + 0 = Arr[0]의 주소값
// Arr + 1 = Arr[1]의 주소값
// Arr + 2 = Arr[2]의 주소값
// Arr + 3 = Arr[3]의 주소값
// Arr + 4 = Arr[4]의 주소값

int* pArr = Arr; // int* pArr = &Arr 과 같은 의미이다. 왜냐하면 Arr은 배열의 시작 주소이기 때문에 

for(int i = 0; i < 5; ++i) printf("pArr이 가르키고 있는 Arr[%d] 주소의 값 : %d \n", i, pArr[i]);
// 이를 출력시
// pArr이 가르키고 있는 Arr[0] 주소의 값 : Arr[0]의 주소값이 나오게 된다.
// pArr이 가르키고 있는 Arr[1] 주소의 값 : Arr[1]의 주소값이 나오게 된다.
// pArr이 가르키고 있는 Arr[2] 주소의 값 : Arr[2]의 주소값이 나오게 된다.
// pArr이 가르키고 있는 Arr[3] 주소의 값 : Arr[3]의 주소값이 나오게 된다.
// pArr이 가르키고 있는 Arr[4] 주소의 값 : Arr[4]의 주소값이 나오게 된다.

for(int i = 0; i < 5; ++i) printf("pArr이 가르키고 있는 Arr[%d] 주소의 값 : %d \n", i, *(pArr + i));
// 이를 출력시
// pArr이 가르키고 있는 Arr[0] 주소의 값 : *(Arr + 0)의 주소값이 나오게 된다.
// pArr이 가르키고 있는 Arr[1] 주소의 값 : *(Arr + 1)의 주소값이 나오게 된다.
// pArr이 가르키고 있는 Arr[2] 주소의 값 : *(Arr + 2)의 주소값이 나오게 된다.
// pArr이 가르키고 있는 Arr[3] 주소의 값 : *(Arr + 3)의 주소값이 나오게 된다.
// pArr이 가르키고 있는 Arr[4] 주소의 값 : *(Arr + 4)의 주소값이 나오게 된다.

int A = 10; int B = 20;
int* p = &A;
printf("*p = %d \n", *p); 
// 포인터 변수 p에 A의 주소값이 저장되어 있고 포인터 변수 p가 가르키고 있는 값은 10이다 . 
p = &B; // p라는 메모리 공간에 B의 주소값을 초기화해준다. 만약 p를 가르킬 시 20이라는 값이 나오게 된다.
printf("*p = %d \n", *p);
// 포인터 변수 p에 B의 주소값이 저장되어 있고 포인터 변수 p가 가르키고 있는 값은 20이다.

 

 

요번엔 const를 사용하여 배열이 왜 포인터 상수인지 알아보고 포인터와 연관지어보자.

 

 

int A = 10; int B = 20;

// 포인터
int* p1 = &A;  
*p1 = 50;  // 포인터로 값의 변경이 가능하고 
p1 = &B;   // 주소값 또한 변경이 가능하다
printf(" p1 = %p \n" , p1);
printf(" p1 = %d \n" , *p1);

// 상수 포인터 : 값의 변경만 가능하다!
const int* p2 = &A; 
*p2 = 30; // 포인터로 값의 변경이 불가능한 상수 포인터 (error)
p2 = &B;  // 하지만 주소값의 변경은 가능하다.

// 포인터 상수 : 주소값의 변경만 가능하다!
int* const p3 = &A;
*p3 = 40; // 포인터로 값의 변경이 가능한 포인터 상수이다. 
p3 = &B;  // 하지만 주소값의 변경은 불가능하다. (error)
          // 배열과 마찬가지로 주소의 변경은 불가능하나 요소의 값은 변경 가능하다. 
          // 배열은 포인터 상수이다.

// 상수 포인터 상수
const int* const p4 = &A;
*p4 = 50;  // 포인터로 값의 변경이 불가능하다.
p4 = &B;   // 주소값 역시 변경이 불가능한 상수 공간이다.

 

 

 

이젠 함수의 매개변수를 포인터 형식으로 만들어 어떠한 데이터들을 주고 받는지 알아보자.

 

 

 

void CallByValue(int a) // 일반 매개변수 : 메인 함수에서 int A = 5;라는 값을 받아왔다. (int a = 5;)
{
    printf(" a value     :  %d \n" , a);   // a라는 매개변수는 5
    printf(" a address   :  %p \n" , &a);  // a라는 매개변수의 주소값은 a의 매개변수 a의 주소값이 나옴
    a = 10;                                // a를 10으로 초기화 즉 int A = 10; (다시 아래로)
}

void CallMyAddress(int* a) // 포인터 매개변수 : 메인 함수에서 &A를 받아왔다. (int* a = &A);
{
    printf(" a value     :  %d  \n" , a);  // a라는 매개변수는 5가
    printf(" a address   :  %p  \n" , &a); // a라는 매개변수의 주소값은 A의 주소값이 나옴
   *a = 10;                                // a가 가리키는 것(&A)을 10으로 초기화 (다시 아래로)
}

int main()
{
   int A = 5;                            // A라는 메모리 공간에 5라는 값을 저장과 동시에 A의 메모리 주소가 생성된다.
   printf("A address  :  %p \n" , &A);   // A의 주소 출력
   printf("A value    :  %d \n" , A);    // A의 값 5를 출력

   CallByValue(A);                       // 함수 호출 : CallByValue에 A라는 변수를 매개변수로 넘긴다. (위로가서)
   printf("A value    :  %d \n" , A);    // A의 값은 5가 나오게 된다. 왜냐하면 함수가 종료되고 나면 소멸된다.
   
    CallMyAddress(&A);                   // 함수 호출 : CallMyAddress에 A의 주소값을 매개변수로 넘긴다. (다시 위로)
    printf("A address :  %d \n" , A);    // A의 값은 10이 나오게 된다. 왜냐하면 함수의 포인터 매개변수를 통해
                                         // 메인함수의 A라는 변수의 주소에 값을 넣어 저장(초기화)를했기 때문에 
                                         // A는 10이 나오게 된다.
    return 0;
}

 

 

 

이젠 구조체(struct)와 typedef을 통해 나만의 메모리 공간을 정의하여

 

그 공간을 포인터 변수로 활용하고, 직접접근과 간접접근 연산자는 무엇이고 어떻게 사용하는지 알아보자.

 

 

typedef struct tag_Wallet       // tag_Wallet이라는 구조체 변수를 선언과 동시에 사용자 정의 자료형 선언
{
    const char* Name;           // 상수 포인터 Name이라는 변수 생성 (배열과 같은 형태)
    int        Money;           // Money라는 변수 생성
}Wallet;                        // Wallet이라는 별칭 선언

void AddSalary(Wallet* wallet)  // Wallet이라는 구조체 자료형을 가진 포인터 매개변수 wallet을 포함하는 함수 선언
{
   wallet->Money += 10000;      // wallet 변수가 Wallet의 멤버 Money에 간접접근하여 값을 변경해줌
}

void PrintWallet(Wallet wallet) // Wallet이라는 구조체 자료형을 가진 매개변수 wallet을 포함하는 함수 선언
{
   printf("Name  :  %s \n" , wallet.Name);   // 구조체 변수 Wallet에 있는 Name에 직접접근하여 값을 출력
   printf("Money :  %d \n" , wallet.Money);  // 구조체 변수 Wallet에 있는 Money에 직접접근하여 값을 출력
}

int main()
{
   Wallet wallet;                 // 구조체 변수 Wallet 자료형을 가진 wallet 변수 선언
   wallet.Name = "My Wallet";     // wallet의 멤버 Name에 직접접근하여 "My Wallet" 이라는 상수를 저장
   wallet.Money = 1000;           // wallet이 멤버 Money에 직접접근하여 1000을 저장
   PrintWallet(wallet);           // wallet을 PrintWallet 함수의 매개변수로 보내면서 호출
 
   Wallet* pWallet = &wallet;     // Wallet자료형인 포인터 변수 pWallet은 wallet의 주소값을 저장
   (*pWallet).Money = 3000;       // 포인터 변수 pWallet이 Money에 직접 접근하여 3000을 저장
   pWallet->Money = 3000;         // 변수 pWallet이 Money에 간접 접근하여 3000을 저장
   PrintWallet(*pWallet);         // *pWallet을 PrintWallet 함수의 매개변수로 보내면서 호출

   AddSalary(pWallet);            // pWallet을 AddSalary 함수의 매개변수로 보내면서 호출
   PrintWallet(*pWallet);         // *pWallet을 PrintWallet 함수의 매개변수로 보내면서 호출
    
   AddSalary(&wallet);            // wallet의 주소값을 AddSalary 함수의 매개변수로 보내면서 호출
   PrintWallet(wallet);           // wallet을  PrintWallet 함수의 매개변수로 보내면서 호출

   return 0;
}

 

 

 

'c' 카테고리의 다른 글

17_정적변수(static)  (2) 2023.04.23
16_지역, 전역변수(Local, Global Variable)  (0) 2023.04.20
14_함수(Function)  (0) 2023.04.14
13_문자열(string)  (0) 2023.04.11
12_배열(Array)  (0) 2023.04.08

댓글