크래프톤정글4주차 - 포인터,포인터의 연산

김태성·2024년 2월 2일
0
post-thumbnail

포인터

포인터가 무엇인가를 검색하면 어떤 특정한 값을 가리키는 사람들이 나온다.
포인터는 무엇인지 정의부터 알아보자.

  • 포인터(pointer)는 프로그래밍 언어에서 다른 변수, 혹은 그 변수의 메모리 공간주소를 가리키는 변수를 말한다. 포인터가 가리키는 값을 가져오는 것을 역참조라고 한다.

그렇다. 포인터는 특정한 변수, 메모리 공간주소를 가리키는 변수를 말하는 것이다.
그래서 포인터는 다른 변수의 포인터를 가리킬 수 있는 것이다.

다른 짤로는 이런게 있다.
이 짤을 해석하자면

int를 가리키는 int*
int* 를 가리키는 int**
int**를 가리키는 int***

이렇게 쭉 가는 것이다.
즉 주소값의 포인터도 있는 것이다.

포인터는 왜 어려운가?

포인터는 C 언어에서 정말 중요한 개념 중 하나이다.
왜냐하면 포인터를 사용해서 특정 데이터를 직접 조작하거나 다른 데이터로 전환할 수 있기 때문이다.
C언어는 다른 언어와는 달리 저수준의 언어이기 때문에 메모리에 직접 접근하고 제어할 수 잇다.
다른말로 다시 설명하게 되면

파이썬을 비롯한 고급언어들은 포인터의 개념을 전혀 알지 못해도 언어에서 자동으로 해 주지만
포인터를 재대로 사용한다는 뜻은
C언어를 사용할때는 내 코드와 컴퓨터의 메모리가 의 상호작용을 이해한다는 것이기 때문이다.

그래서 포인터를 잘못 사용하게 되면 메모리의 누수도 심각하고, 문법도 어렵고 디버깅도 힘들고 메모리에 직접 접근하는 것이라 위험성도 커지게 된다.

포인터의 연산

포인터의 연산을 찾아보기 전에 2개를 확실히 알고 넘어가야 한다.

바로 *참조연산자, &주소연산자 이다.

정의를 살펴보자

  • 주소연산자 : 해당 변수의 주소값을 반환
  • 참조연산자 : 포인터에 가리키는 주소에 저장된 값을 반환

둘이서 쌍으로 날 괴롭히고 있다.
그래도 뭔 소리인지는 알아봐야되니까 집중해서 글을 보자.




주소연산자는 해당 변수의 주소값을 반환한다고 한다.
그러니까 &a 라고 쓰면 a의 주소값을 반환한다는 것이다.

참조연산자는 주소에 저장된 값을 반환한다고 한다.
그러니까 *ptr = &a 라고 하면 a의 주소에 저장된 값을 반환한다는 소리다.

#include<stdio.h>

int main() {
	int a = 123;		//값을 저장할 변수
	int *ap;			//a의 값을 저장할 포인터변수
	ap = &a;			//ap에 a의 포인터를 저장

	printf("a의 값 : %d\n", a);
	printf("a의 주소값 : %u\n", &a);
	printf("ap의 값 : %u\n", ap);
	printf("ap를 통해 구한 a의 값 : %d\n", *ap);
	printf("ap의 포인터 : %u\n", &ap);
}
출처: https://angangmoddi.tistory.com/14 [Language Note:티스토리]

다음은 내가 본 정말 좋은 설명이다.
과정을 살펴보자.

  • a의 값은 123이다.
  • a의 주소값은 15726336이다.
  • a의 주소값을 받은 ap의 값은 15726336이다.
  • *ap의 값은 123이 나온다.
  • ap의 주소값은 15726324가 나온다.

즉 다시 말해서

  • int *ap 는 포인터 변수라고 해서 주소값을 받아오는 변수이다.
  • *a를 printf 하게 되면 오류가 뜬다.(a는 int 변수이고 주소가 아니기 때문에)
  • 하지만 ap는 포인터 변수이기 때문에 *를 붙이면 그 주소의 값을 가져올 수 있다.
  • ap도 하나의 변수이기 때문에 특정 주소를 배정받아 저장이 된다.

이걸로 포인터 변수와 주소연산자, 참조연산자에 대한 기본적인 이해가 되었다.

#include <stdio.h>

int main()
{
    int numArr[5] = { 11, 22, 33, 44, 55 };
    int *numPtrA;
    int *numPtrB;
    int *numPtrC;

    numPtrA = numArr;    // 배열 첫 번째 요소의 메모리 주소를 포인터에 저장

    numPtrB = numPtrA + 1;    // 포인터 연산
    numPtrC = numPtrA + 2;    // 포인터 연산
    
    printf("%p\n", numPtrA);    // 00A3FC00: 메모리 주소. 컴퓨터마다, 실행할 때마다 달라짐
    printf("%p\n", numPtrB);    // 00A3FC04: sizeof(int) * 1이므로 numPtrA에서 4가 증가함
    printf("%p\n", numPtrC);    // 00A3FC08: sizeof(int) * 2이므로 numPtrB에서 8이 증가함

    return 0;
}

코딩도장의 예시이다.
이렇게 했을때 출력값도 주석으로 적혀있는데 살펴보게 되면

포인터의 값에 상수를 더하게 되면 int의 배열 크기가 4bite이기 때문에
포인터가 가르키는 주소는 4씩 증가하는걸 볼 수 있다.

반대로 빼기에도 똑같다.

#include <stdio.h>

int main()
{
    int numArr[5] = { 11, 22, 33, 44, 55 };
    int *numPtrA;
    int *numPtrB;
    int *numPtrC;

    numPtrA = &numArr[2];     // 배열 세 번째 요소의 메모리 주소를 포인터에 저장

    numPtrB = numPtrA - 1;    // 포인터 연산
    numPtrC = numPtrA - 2;    // 포인터 연산
    
    printf("%p\n", numPtrA);    // 00A3FC08: 메모리 주소. 컴퓨터마다, 실행할 때마다 달라짐
    printf("%p\n", numPtrB);    // 00A3FC04: sizeof(int) * -1이므로 numPtrA에서 4가 감소함
    printf("%p\n", numPtrC);    // 00A3FC00: sizeof(int) * -2이므로 numPtrB에서 8이 감소함

    return 0;
}

이번에는 빼기의 결과이다.

마찬가지로 포인터의 주소가 int자료형의 크기의 배수만큼 줄어드는 것을 볼 수 있다.

#include <stdio.h>

int main()
{
    char *cPtr1 = NULL;
    short *numPtr1 = NULL;
    long long *numPtr2 = NULL;

    printf("%p\n", cPtr1 + 1);      // 00000001: 0x000000에서 1바이트만큼 순방향으로 이동
    printf("%p\n", numPtr1 + 1);    // 00000002: 0x000000에서 2바이트만큼 순방향으로 이동
    printf("%p\n", numPtr2 + 1);    // 00000008: 0x000000에서 8바이트만큼 순방향으로 이동

    return 0;
}

다른 예시를 보자.
처음 cPtr1, numPtr1, numPtr2는 모두 같은 주소인 0x000000을 가르키고 있었을 것이다.
하지만 각각의 자료형은 char, short, longlong로 다르기 때문에
상수를 더해준다면 각각의 자료형 크기만큼 더해질 것이다.

이때, c의 증감연산자 ++와 --에도 같은 방식으로 적용이 된다.
너무나 간단한 예시이기 때문에 생략을 하도록 하자.

추가로 하나 더 적으면
arr[i] = *(arr + i) 가 성립된다는 소리다!

profile
닭이 되고싶은 병아리

0개의 댓글

관련 채용 정보