본문 바로가기
  • 초부득3 - 어제보다 나은 내일을 위해
  • 꿈이 현실이 되는 날까지
sw사관학교 정글 2기/컴퓨터 시스템

C 포인터

by 금의야행 2021. 9. 2.

 

무시무시한 포인터

자료 출처 : https://modoocode.com/

 

포인터 

메모리 상에 위치한 특정한 데이터의 (시작)주소값을 보관하는 변수

 

ex)

int a = 2;

int *p = a; or  int* p = a;

 

(포인터에 주소값이 저장되는 데이터의 형) *(포인터의 이름);

 

 

 

단항 연산자 &, *

 

ex) 

int a = 2;
출력 : &a 
>>>0x7ffcb049bec4(주소값)

int *p;
p = &a;
출력 : *p
>>>2(주소값에 해당하는 메모리 위치에 저장된 int형 값.

 

 

 

상수 포인터

 

 

int a = 2;
int b;

constant int *p = &a;

*p = 3; //불가능
p = &b //가능

 

constant int *p 에서 constant 가 값을 바꾸지 말라고 지정하는 것은 *p의 주소값이 아니라, *p가 가르키는 주소의 값!

 

constant는 int 형 변수를 가리키는데 그 값을 절대로 바꾸지 말라 라는 의미이죠. 즉, pa 는 어떠한 int 형 변수를 가리키고 있습니다. 그런데 const 가 붙었으므로 pa 가가리키는 변수의 값은 절대로 바뀌면 안되게 됩니다.

 

반대:

 

int* constant p = &a;

*p = 3; //가능
p = &b //불가능

 

이번에는 contstant 가 p 값을 바꾸지 말라고 지정하기 때문.

 

ex)

constant int costant *p = &a;

*p = 3; //불가능
p = &b //불가능

 

 

 

포인터 연산 +, -

 

int a = 2;
int* p 
p = &a

p
>>>0x7ffd6a32fc50
p + 1
>>>0x7ffd6a32fc54  // 4 차이

주소 값 p에 + 1을 할 경우 주소값이 1이 증가하지 않고, 해당 포인터의 자료형의 크기 (int는 4byte) 만큼 증가.

 

char, short, long, double, float 등등 각각의 크기 만큼 증가한다.

 

-1 도 반대로 마찬가지

 

 

배열에 활용 -포인터 연산-

 

배열은 만들어질때 가상주소로 원소끼리 이어지게/ 연속되게 할당 받음. 그렇기에 arr[0]의 주소값 + 1은 arr[1]을 가르키는 주소값이 됌.

 

#include <stdio.h>
int main()
{
    int arr[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    int *pointer_arr;
    int i;
    pointer_arr = &arr[0];

    for (i = 0; i < 10; i++)
    {
        printf("arr[%d] 의 주소값 : %p ", i, (pointer_arr + i));
    }
    return 0;
}

>>>
arr[0] 의 주소값 : 0x7ffc196098f0 
arr[1] 의 주소값 : 0x7ffc196098f4 
arr[2] 의 주소값 : 0x7ffc196098f8
...

 

*연산자 적용시

 

//생략
pointer_arr = &arr[0];

printf("arr[3]의 값 : %d " , *(pointer_arr + 3));

>>>arr[3]의 값 : 4

 

 

 

 

arr 은 뭘 반환할까

 

 

arr 은 배열이지만 이 값을 그대로 출력할 경우, 파이썬처럼 배열 전체가 나오지 않고, 배열의 시작 주소값을 반환한다!

 

#include <stdio.h>
int main() {
  int arr[3] = {1, 2, 3};

  printf("arr 의 정체 : %p \n", arr);
  printf("arr[0] 의 주소값 : %p \n", &arr[0]);

  return 0;
}

>>>arr 의 정체 : 0x7fff1e868b1c 
>>>arr[0] 의 주소값 : 0x7fff1e868b1c

 

 

중요: 배열은 배열이고 포인터는 포인터다.

 

arr은 포인터가 아니라 그냥 첫 주소값을 반환하게 약속되어 있는거다.

 

 

[] 연산자의 역할

 

arr[1]등에 사용되는 [ ]은 엄연히 연산자이다. 그럼 무엇을 연산하는 것 일까?

 

  int arr[5] = {1, 2, 3, 4, 5};

  printf("a[3] : %d \n", arr[3]);
  printf("*(a+3) : %d \n", *(arr + 3));
  
  >>>a[3] : 4 
  >>>*(a+3) : 4

 

a[3] = *(a+3) 이다

[3] a = *(3 + a)

 

 

 

1차원 배열 가르키기

parr = arr;

parr = &arr[0]

위와 아래는 사실상 동일하다.

 

 

int *parr 은 parr++등으로 for문에 응용할 수 있지만, 

arr++는 불가능. 왜냐하면 arr은 주소값을 담은 포인터가 아니라 배열이니까!@ 단지 arr은 약속되어있을 뿐.

 

 

 

 

포인터의 포인터 (shibal)

 

int **p;

 

는 int를 가르키는 포인터를 가르키는 포인터 

 

 

그렇다면 배열의 포인터는?

 

 

배열 이름의 주소값?

 

  int arr[3] = {1, 2, 3};
  int (*parr)[3] = &arr;

  printf("arr[1] : %d \n", arr[1]);
  printf("parr[1] : %d \n", (*parr)[1]);
  
  >>>arr[1] : 2 
  >>>parr[1] : 2

 

 

int (*parr)[3] = &arr;

arr  크기가 3 인 배열 이기 때문에, &arr 을 보관할 포인터는 크기가 3 인 배열을 가리키는 포인터가 되어야 할 것입니다

 

괄호는 꼭 사용해줘야한다. C 컴파일러가 int * 원소 3 개를 가지는 배열을 정의한 것으로 오해하게 됌.

 

덤으로, parr 은 arr과 같은 값을 가지게 된다.

 

 

 

2차원 배열의 [] 연산자

 

int a[2][3];

int a[3] 짜리 배열 2 개가 메모리에 연속적으로 존재

 

  printf("arr[0] : %p \n", arr[0]);
  printf("&arr[0][0] : %p \n", &arr[0][0]);

  printf("arr[1] : %p \n", arr[1]);
  printf("&arr[1][0] : %p \n", &arr[1][0]);
  
>>>arr[0] : 0x7ffda354e530 
>>>&arr[0][0] : 0x7ffda354e530 

>>>arr[1] : 0x7ffda354e53c 
>>>&arr[1][0] : 0x7ffda354e53c

 

실은 기존의 1 차원 배열과 마찬가지로 sizeof 나 주소값 연산자와 사용되지 않을 경우, 

 

arr[0]  arr[0][0] 을 가리키는 포인터로 암묵적으로 타입 변환되고, arr[1]  arr[1][0] 을 가리키는 포인터로 타입 변환된다라는 뜻

 

 

arr[0] 과 &arr[0][0] 은 엄연히 다름. 위이이의 주의사항과 같은 이유.

 

 

 

배열 포인터 - 그렇다면 2차원 배열의 주소값은? 

 

/* (배열의 형) */ (*/* (포인터 이름) */)[/* 2 차원 배열의 열 개수 */];
// 예를 들어서
int (*parr)[3];

 

 

ex)

  int arr[2][3] = {{1, 2, 3}, {4, 5, 6}};
  int(*parr)[3];  // 괄호를 꼭 붙이세요

  parr = arr;  // parr 이 arr 을 가리키게 한다.

  printf("parr[1][2] : %d , arr[1][2] : %d \n", parr[1][2], arr[1][2]);
  
  >>>parr[1][2] : 6 , arr[1][2] : 6

 

 

이렇기에

 

  int arr[2][3];
  int brr[10][3];
  int crr[2][5];

  int(*parr)[3];

  parr = arr;  // O.K
  parr = brr;  // O.K
  parr = crr;  // 오류!!!!

arr, brr은 되지만 crr은 안된다. parr은 배열의 길이가 3인 배열을 가르키는 포인터 형이기 때문.

 

 

 

 

포인터 배열

 

배열 포인터와는 다름. 배열 포인터는 정말 포인터, 포인터 배열은 정말 배열

포인터 배열은 말그대로 포인터들이 담긴 배열.

 

/* 포인터배열*/
#include <stdio.h>
int main() {
  int *arr[3];
  int a = 1, b = 2, c = 3;
  arr[0] = &a;
  arr[1] = &b;
  arr[2] = &c;

  printf("a : %d, *arr[0] : %d \n", a, *arr[0]);
  printf("b : %d, *arr[1] : %d \n", b, *arr[1]);
  printf("b : %d, *arr[2] : %d \n", c, *arr[2]);

  printf("&a : %p, arr[0] : %p \n", &a, arr[0]);
  return 0;
}

//실행 결과

a : 1, *arr[0] : 1 
b : 2, *arr[1] : 2 
b : 3, *arr[2] : 3 

&a : 0x7ffe8a2fa4e4, arr[0] : 0x7ffe8a2fa4e4

 

 

int *arr[3];

열의 각각의 원소는 int 를 가리키는 포인터 형으로 선언된 것입니다.

따라서, int 배열에서 각각의 원소를 int 형 변수로 취급했던 것처럼 int* 배열에서 각각의 원소를 포인터로 취급할 수 있습니다

 

 

댓글