[코딩 자율학습단 17기] 12일차

네버릭스·2025년 10월 23일
post-thumbnail

// 2025년 10월 21일


Day 12 : 메모리 주소 관리하기 : 포인터

포인터란

오늘은 포인터를 학습했다.
학창시절에서도 포인터에 많이 어려웠했던 경험이 있는데,
파이팅해서 잘 해보자 !!!

변수와 메모리의 관계

변수명 앞에 &를 붙여 출력하면 변수의 주소를 알 수 있다.
변수의 주소를 출력하는 방법은, 서식 지정자에 포인터를 의미하는 %p를 사용하면 된다.
그러면 사진과 같이 메모리 주소를 16진수 형태로 출력한다.

포인터로 다른 변수의 주소와 값 알아내기

💡 포인터 변수 선언
포인터 변수를 선언할 때 자료형과 *, 변수명은 일반적으로 띄어쓴다.
띄어쓰지 않아도 문제가 없긴 한데, 필자는 개인적으로 무조건 띄어 쓰는 편이긴 하다.

자료형 * 변수명;

// 아래 예시는 모두 맞는 문법임
int * a;
int* a;
int *a;
int  *  a;

미션맨은 각 집에 방문해, 문에 적힌 암호를 확인한다.
포인터 변수명 앞에 *를 붙이면, 포인터 변수가 가리키는 주소의 값을 알 수 있다.
이처럼 미션맨이 방문한 곳의 주소와 암호가 철수, 영희, 민수의 주소와 동일하다는 걸 알 수 있다.

포인터로 다른 변수의 값 바꾸기

이번에 미션맨은 각 집의 암호에 3을 곱한다. *미션맨에 곱하기 3을 하면 된다.
사진처럼 포인터 변수는 다른 변수의 주소를 알아낼 수 있고, 알아낸 변수의 주소에 찾아가 값도 직접 바꿀 수 있다는 걸 알 수 있다.

변수의 주소를 안다 = 변수가 존재하는 메모리 공간의 주소를 안다

포인터 추가하기

스파이는 미션맨이 모르게, 미션맨이 바꾼 암호에서 1을 뺀다.
포인터 변수 2개는 같은 주소, 즉 하나의 메모리 공간을 가리킬 수 있다.
이처럼 포인터 변수로 주소를 알고 있으면, 해당 주소에 가서 저장된 값을 가져오거나 읽어 올 수도 있고 값을 변경할 수도 있다.

정리하면,
포인터 변수는 어떤 변수의 주소를 값으로 가지고 있으며, 주소를 이용해 주소에 해당하는 변수의 값을 직접 바꿀 수 있다.

포인터 변수인 미션맨 / 스파이도 주소를 확인 해 보면,

printf("미션맨의 주소 : %p\n", &미션맨);
printf("스파이의 주소 : %p\n", &스파이);

변수인 철수, 영희, 민수와 멀지 않은 곳에 있는 것을 확인할 수 있다.

// 전체 코드
#include <stdio.h>

int main(void)
{
    int 철수 = 1;
    int 영희 = 2;
    int 민수 = 3;

    printf("철수네 주소 : %p, 암호 : %d\n", &철수, 철수);
    printf("영희네 주소 : %p, 암호 : %d\n", &영희, 영희);
    printf("민수네 주소 : %p, 암호 : %d\n", &민수, 민수);

    int* 미션맨;
    미션맨 = &철수;
    printf("미션맨이 방문한 곳의 주소 : %p, 암호 : %d\n", 미션맨, *미션맨);

    *미션맨 = *미션맨 * 3;
    printf("미션맨이 암호를 바꾼 곳의 주소 : %p, 암호 : %d\n", 미션맨, *미션맨);

    printf("\n>> 스파이가 미션을 수행하는 중 <<\n");
    int* 스파이 = 미션맨;
    *스파이 = *스파이 - 1;
    printf("스파이가 방문한 곳의 주소 : %p, 암호 : %d\n", 스파이, *스파이);

    미션맨 = &영희;
    스파이 = 미션맨;
    printf("미션맨이 방문한 곳의 주소 : %p, 암호 : %d\n", 미션맨, *미션맨);

    *미션맨 = *미션맨 * 3;
    printf("미션맨이 암호를 바꾼 곳의 주소 : %p, 암호 : %d\n", 미션맨, *미션맨);

    printf("\n>> 스파이가 미션을 수행하는 중 <<\n");
    스파이 = 미션맨;
    *스파이 = *스파이 - 1;
    printf("스파이가 방문한 곳의 주소 : %p, 암호 : %d\n", 스파이, *스파이);

    미션맨 = &민수;
    printf("미션맨이 방문한 곳의 주소 : %p, 암호 : %d\n", 미션맨, *미션맨);

    *미션맨 = *미션맨 * 3;
    printf("미션맨이 암호를 바꾼 곳의 주소 : %p, 암호 : %d\n", 미션맨, *미션맨);

    printf("\n>> 스파이가 미션을 수행하는 중 <<\n");
    스파이 = 미션맨;
    *스파이 = *스파이 - 1;
    printf("스파이가 방문한 곳의 주소 : %p, 암호 : %d\n", 스파이, *스파이);

    printf("\n>> 철수, 영희, 민수가 집에 와서 바뀐 암호를 보고 놀람 <<\n");
    printf("철수네 주소 : %p, 암호 : %d\n", &철수, 철수);
    printf("영희네 주소 : %p, 암호 : %d\n", &영희, 영희);
    printf("민수네 주소 : %p, 암호 : %d\n", &민수, 민수);
    printf("미션맨의 주소 : %p\n", &미션맨);
    printf("스파이의 주소 : %p\n", &스파이);

    return 0;
}

포인터로 배열 다루기

포인터로 배열에 접근하기

포인터 변수와 배열의 관계는 아주 가깝다.

포인터 변수에 배열을 값으로 넣으면 포인터 변수는 배열의 첫 번째 요소의 주소(배열의 시작 주소)를 가리키게 된다.
그러면 포인터 변수는 시작 주소로 배열에 접근해 요소의 값을 하나씩 가져올 수 있다.

포인터 변수의 값을 바꾸기 전에는 배열의 초깃값 그대로 5, 10, 15가 출력된다.
그런데 값을 새로 넣으면 포인터 변수의 값과 배열의 값 다 바뀐다.
즉, 포인터 변수가 직접 배열의 각 요소에 접근해서 값을 바꾼다.
따라서 위 사진에서는 배열 arr 과 포인터 변수 ptr은 같은 역할을 한다고 볼 수 있다.

단, ptr은 필요하다면 arr과 같은 배열을 값으로 넣을 수 있지만, 반대로는 불가능하다.

포인터로 두 변수의 값 교환하기

실행해 보면 함수 호출 전, 후는 그대로고, 함수 안에서는 두 값이 서로 바뀐 걸 알 수 있다.

이는 main() 함수에서 swap() 함수를 호출할 때 변수 a와 b를 전달했지만, 실제로는 두 변수 자체가 아닌 두 변수의 값만 전달해서 그렇다.

변수의 주소를 출력해보면, main() 함수 안의 a와 swap() 함수 안의 a는 주소가 다르다. 서로 다른 변수라는 뜻이다.
이처럼 함수를 호출하면서 전달값으로 변수를 넘기면 호출한 함수 안에서는 변수 자체가 아닌 전달받은 변수의 값만 복사해서 사용한다. 이를 값에 의한 호출이라고 한다.

이처럼 swap_addr() 함수를 이용해 변수의 값이 아닌 주소 자체를 넘기면, 이처럼 원하는 출력 결과를 얻을 수 있다.
이처럼 함수를 호출하면서 전달값으로 변수의 주소를 넘기면 호출한 함수 안에서 변수의 주소를 참조해 값을 사용하거나 수정할 수 있다. 이를 참조에 의한 호출이라고 한다.

// 전체 코드
#include <stdio.h>

void swap(int a, int b);
void swap_addr(int * a, int * b);

int main(void)
{
	int a = 10;
	int b = 20;
	printf("a의 주소 : %p\n", &a);
	printf("b의 주소 : %p\n", &b);
	printf("swap() 함수 호출 전 => a : %d, b : %d\n", a, b);
	swap(a, b);
	printf("swap() 함수 호출 후 => a : %d, b : %d\n", a, b);
	printf("swap_addr() 함수 호출 후 => a : %d, b : %d\n", a, b);
	swap_addr(&a, &b);
	printf("swap_addr() 함수 호출 후 => a : %d, b : %d\n", a, b);
	return 0;
}

void swap(int a, int b)
{
	printf("swap() 함수 안에서 a의 주소 : %p\n", &a);
	printf("swap() 함수 안에서 a의 주소 : %p\n", &b);
	int temp = a;
	a = b;
	b = temp;
	printf("swap() 함수 안 => a : %d, b : %d\n", a, b);
}

void swap_addr(int* a, int* b)
{
	int temp = *a;
	*a = *b;
	*b = temp;
	printf("swap_addr() 함수 안 => a : %d, b : %d\n", *a, *b);
}

포인터로 배열의 값 바꾸기

changeArray() 함수를 통해 배열의 주소를 전달받아, 함수 안에서 배열의 값을 바꾼다.

arr2에서 &arr2[0]으로 바꿔도 결과는 같다.
배열은 연속된 공간에 할당하므로, 주소가 연속된다. 따라서 배열의 시작 주소인 첫 번째 요소의 주소를 전달하면 시작 주소를 기준으로 지정한 요소의 위치를 찾아 값을 바꿀 수 있다.

// 전체 코드
#include <stdio.h>

void changeArray(int * ptr);

int main(void)
{
	int arr2[3] = { 10, 20, 30 };
	changeArray(&arr2[0]);
	for (int i = 0; i < 3; i++)
	{
		printf("%d\n", arr2[i]);
	}
	return 0;
}

void changeArray(int * ptr)
{
	ptr[2] = 50;
}

12일차 후기

학교에서 포인터를 배울 땐 그냥 어렵다~ 라고만 생각하고 자세히 들여다보진 않았는데, 책에서 이렇게 하나씩 뜯어보면서 학습하니 이해가 아주 잘 됐다.
역시 하고자 하는 마음가짐이 중요한 것 같다. ㅋㅋㅋㅋㅋㅋㅋㅋ
학창시절에는 왜 이런 마음가짐을 가지지 않았나 모르겠다~


이미지 출처 : https://cafe.naver.com/gilbutitbook (길벗 코딩 자율학습단 공식 카페)
그 외의 모든 코딩 화면은 직접 작성하고, 실행하였습니다.

profile
도망친 곳에 낙원은 없다.

0개의 댓글