C언어/문법

[C] 포인터

Jawy 2024. 1. 11. 02:29

 

나의 기준, 파이썬 등 다른 언어에 비해 유독 C언어가 직관적으로 다가오지 않는다는 느낌을 받았다.

 

그리고 그 원인은 의심할 여지 없이 포인터 이새끼 때문이었던 것 같다.

 

그래서 여러 강의들과 교재들, 그리고 수상할 정도로 코딩 지식이 해박한 아빠의 도움을 받아

 

어느 정도 포인터에 대한 감을 잡은 지금, 

 

다시 까먹지 않기 위해 내가 여태껏 이해한 내용들을 정리해보고자 한다.

 

 

나를 포함한 많은 학생들이 C언어에서 특히 포인터만 보면 발작을 일으키고 헛구역질이 나는 것으로 알고 있다. 

 

그도 그럴 것이, 포인터를 이해하려면

 

 프로세스의 메모리 처리 구조에 대해 어느정도 알고 있어야 하는 것 같다.

 

근데 컴퓨터 전공 학과가 아니면, 선행 과목으로 해당 과목들을 배우지 않은 채

 

바로 C언어 코딩부터 배우기 때문에 나같은 케이스들이 많은 것 같다.

 

 

나도 아직 컴퓨터의 구조 등에 대해 깊게 배운 상태는 절대 아니고, 

 

포인터를 이해하기 위한 최소한의 정보들만 속성으로 입력받은 상태이다.


우선 포인터란?

  • 메모리의 주소값을 저장하는 변수이다.
typedef 자료형 element; //int, char 등 필요한 자료형이 들어올 자리 (해당 코드에서는 element로 통일)

element b;
element* a = &b; //주소연산자 &

※포인터 변수(a)의 자료형은 가리키려는 변수(b)의 자료형을 따라간다는 점을 표현하기 위해 자료형을 구조체로 선언함

  • 자료형이 int라면,  a는 정수형 포인터 변수로, 정수형 변수 b의 주소를 가리키게 된다.

 

이게 포인터의 사전적 정의이다. 

 

메모리? 주소?

 

물론 이제는 저 말이 이해가 되지만, 처음 공부할 때는 저 문장 자체가 이해가 되지 않았다.

 

한마디로, 포인터 변수란

 

변수가 선언될 때 변수값을 저장할 메모리 공간을 생성하는데, 해당 공간의 주소값을 저장하는 변수라고 생각하면 된다.

 

 

변수 = 나, 메모리 공간 = 집, 주소 = 집주소 라고 치자.

 

내가 있다면 내가 살 집이 있을 텐데, 내가 사는 집의 주소! 이 놈을 포인터 변수에 저장해 주는 것이다.

 

이렇게 생각한다면 왜 포인터를 사용해야 하는지에 대해 어느정도 감을 잡을 수 있다.

 

 

나한테 물건을 전해주고 싶은데, 직접 만나서 전해주려면 여러가지 제약사항이 있을 수 있잖아?

 

그럴 때 그냥 주소만 알아내서 우편으로 보내버리면 되는거지. 

 

나를 직접 대면하지 않고도 나한테 접근할 수 있으면 편하잖아?

 

 

아무튼 그러면 이제는 아주 간단하게

 

컴퓨터의 연산 과정의 관점에서 바라봐보도록 하자.


call by value? call by reference?

 

C언어의 함수 호출 방식에는 두가지 종류가 있는데, 

 

바로 call by value와 call by reference다.

 

call by value : 값에 의한 호출

call by reference : 참조에 의한 호출

 

 

앞서 들었던 예시를 다시 사용한다면, 

 

call by value : 직접 나를 만나서 물건을 전해주기

call by reference : 내 주소로 물건을 부치기

 

뭐 이런 식으로 이해할 수 있겠다.

 

 

그렇다면 컴퓨터에서는 어떤 제약 사항들이 있길래 call by reference, 

 

즉 주소와 포인터를 사용해서 코딩을 해야하는 걸까?


 

아래 코드에서, 두 정수의 값을 교환하는 함수 swap를 call by value 형식에 따라 구현해 보았다.

#include <stdio.h>

void swap_value(int x, int y) {
	int temp = x;
	x = y;
	y = temp;
}

int main() {
	int a = 10, b = 20;

	swap_value(a, b);
	printf("call by value 방식의 swap 함수 결과\na = %d\nb = %d\n\n", a, b);

	return 0;
}

 

 

출력 결과는 다음과 같았다.

 

call by value 방식의 swap 함수 결과
a = 10
b = 20

 

함수를 호출하기 전 변수 그대로이다. 

 

왜 바뀌지 않았을까?


 

여기서 이제 앞서 말한 프로세스의 메모리 처리 구조 내용이 필요하다.

 

간단하게 얘기하자면, 

 

메모리는 code(text), data, heap, stack 영역으로 구성되어 있고, (자료 구조에서의 heap, stack과는 다른 것임)

 

프로세스는 우리가 작성한 코드를 읽어가며 각각 필요한 위치에 값을 저장한다.

 

예를 들어 전역 변수와 정적 변수는 data 영역에 저장되고, 동적 데이터는 heap 영역에서 처리한다.

 

 

그러나 우리가 지금 알아야 할 영역은 바로 stack 영역이다.

 

이 stack 영역에서는 잠시 사용되었다가 사라지는 데이터를 저장하는데,

 

함수를 호출했을 때 지역 변수, 매개 변수, 리턴 값 등을 할당해주고,

 

함수를 반환할 때 모두 소멸된다.

 

 

여기서 다시 앞선 코드로 돌아가보자.

 

swap_value 함수 내에서 생성한 지역변수와 매개변수 temp와 x, y는 모두 stack 영역에 저장되었다가,

 

함수가 종료될 때 소멸된다.

 

실질적으로 main 코드에는 아무런 영향도 주지 못하는 것이다.

 

 

따라서 call by value 방식으로 함수를 호출한다면,

 

main 코드에 영향을 줄 수 있는 방법은

 

return 값을 반환하는 방법밖에 없다.

 

따라서 해당 코드를 다음과 같이 수정해서 사용할 수는 있다.

#include <stdio.h>

int swap_value(int x, int y) {
	int temp = x;
	x = y;
	y = temp;
	return x;
}

int main() {
	int a = 10, b = 20;

	int sum = a + b;
	a = swap_value(a, b);
	b = sum - a;
	printf("call by value 방식의 swap 함수 결과\na = %d\nb = %d\n\n", a, b);

	return 0;
}

 

그러나 보다시피, C언어에서 return값은 하나만 반환할 수 있기에

 

위의 코드는 다소 번거로운 느낌이 든다.

 

그럼 call by reference 방식으로 함수를 호출해보면 어떨까?


 

이번에는 똑같은 swap 함수를 call by reference 형식에 따라 구현해 보았다.

#include <stdio.h>

void swap_reference(int* x, int* y) {
	int temp = *x;
	*x = *y;
	*y = temp;
}

int main() {
	int a = 10, b = 20;

	swap_reference(&a, &b);
	printf("call by reference 방식의 swap 함수 결과\na = %d\nb = %d\n\n", a, b);

	return 0;
}

 

출력 결과는 다음과 같았다.

 

call by reference 방식의 swap 함수 결과
a = 20
b = 10

 

우리가 의도한대로 두 변수의 값이 바뀌었다!

 

 

우선 main 함수에서 함수를 호출하는 부분을 살펴보면, 

 

call by reference라는 이름에 걸맞게

 

swap_reference(&a, &b), 주소 참조 연산자를 이용해 주소로 함수를 호출하고 있다.

 

 

다음 swap_reference 함수에서는

 

매개변수로 주소값을 저장하는 포인터 변수 x와 y를 받았다.

 

x와 y에 각각 &a, &b, 즉 a의 주소와 b의 주소가 저장된다.

 

이후 간접 참조 연산자 *를 이용해 x(&a)가 가리키는 곳의 값(a)에 접근했다. 

 

 

확실히 주소를 이용한 접근(call by reference)이 더 값들에 직접적으로 영향을 주는 느낌이라

 

자유자재로 다룰 수 있게 된다면

 

내가 짤 코드들의 수준을 확 끌어올려 줄 수 있을 것 같다.


이렇게 포인터에 대해서 다뤄보았다.

 

다음번에는 포인터와 배열 간의 관계에 대해서 정리해볼 예정!

'C언어 > 문법' 카테고리의 다른 글

[C] 표준 라이브러리 <stdlib.h>  (0) 2024.02.20
[C] <stdio.h>의 표준 입출력 함수  (1) 2024.01.15
[C] _CRT_SECURE_NO_WARNINGS  (0) 2024.01.15
[C] 동적 메모리 할당  (0) 2024.01.12
[C] 포인터와 배열 간의 관계  (1) 2024.01.11