C언어) 포인터 - 포인터를 조금 더 깊이

Lapis0875·2022년 10월 19일
0

c언어

목록 보기
2/21
post-thumbnail

📑포인터 시리즈

포인터 5부작 중 2부에요. 1부의 내용들을 알고 있다는 전제로 설명하니, 1부를 아직 안읽으셨다면 읽어주세요!

❓포인터 변수의 크기

C언어에는 특정 자료형, 변수 등이 메모리를 몇 바이트 차지하는지 알려주는 sizeof 함수가 있어요. 포인터 변수는 메모리를 얼마나 차지하는지 sizeof 함수를 사용해 알아볼게요.

int i, *ip = &i;
printf("%ld, %ld\n", sizeof(i), sizeof(ip));
4 8
double d, *dp = &d;
printf("%ld, %ld\n", sizeof(d), sizeof(dp));
8 8

int형 포인터 변수인 ip와 double형 포인터 변수인 dp 모두 8바이트를 가짐을 알 수 있어요. 포인터 변수는 가리키는 형과 관계없이 크기가 동일해요.

⚠️주의 : 포인터 변수의 크기는 시스템에 따라 다를 수 있어요!

🪐void 포인터

void형 포인터는 말 그대로 형이 없는 포인터에요. 아래 코드를 보며 이해해봐요.

int *p;
void *v;
float x;

p = &x;		// 오류 : p와 x의 형이 다름.
v = &x;
p = v;

위 코드를 컴파일해보면, 컴파일러가 p = &x;에서 경고해줘요. x는 float형이지만, p는 int형을 가리키는 포인터이기 때문이에요. 포인터는 자동 형 변환을 지원하지 않아, p가 x를 가리키게 할 수 없어요.
p가 x를 가리키게 할 경우, 컴파일러가 경고해줘요.

하지만, v = &x; 에서는 오류가 발생하지 않아요. v는 void형 포인터라, 어떤 형의 변수든지 자유롭게 가리킬 수 있어요.

void 형 포인터 변수를 사용할 때에는 적절히 형 변환을 해줘야 해요.

int i, j = 20;
void *v;

v = &j;
i = *((int*) v) + 10;		// i = j + 10;

위 코드에서, v는 int형 변수 j를 가리키고 있어요. 하지만 v는 void형 포인터라, 자신이 가리키는 메모리 공간이 어떤 형의 값을 저장하고 있는지 알지 못해요. void형 포인터로 값을 다루기 위해서는 가리키는 변수의 자료형에 맞게 형변환을 명시적으로 진행해줘야 해요.
*((int*) v) 는 j의 형에 맞게 (int *)형으로 v를 형변환한 후, 역참조 연산자를 사용했어요.

⏩포인터의 포인터

앞서 포인터는 특정한 메모리 공간을 가리킨다고 했어요. 포인터 변수도 자신의 메모리 공간을 할당받으니, 포인터를 가리키는 포인터도 만들 수 있어요.

int i = 100, *p = &i, **q = &p;

printf("i == *p == **q == %d\n", **q);
printf("&i == p == *q == %u\n", *q);

➕포인터 연산

포인터 변수로도 연산을 수행할 수 있어요.

int arr[3] = {1, 2, 3};
int *p = &arr[0], *q = &arr[2];

// q - p를 계산하면, q와 p 사이의 원소 개수를 얻을 수 있어요.
printf("q - p : %d\n", q - p);
// p + 1을 계산하면, p가 가리키는 주소에서 int형 크기만큼 1칸 이동한 주소를 얻을 수 있어요.
printf("arr[1] = %d\n", *(p + 1));

int형 포인터 변수 p와 q 각각 arr이라는 배열의 처음과 끝을 가리키고 있어요. 두 포인터 변수 p와 q의 차를 계산하면, 두 주소 사이의 원소 개수를 구할 수 있어요. 이 때, 원소의 개수는 두 포인터 변수가 가리키는 변수의 형에 따라 계산돼요.

포인터 변수에 정수 값을 더하거나 빼면, 포인터 변수의 자료형 크기만큼 앞이나 뒤로 이동한 메모리 주소를 구해요. 위 코드에서는 p에 1을 더해, arr[1]의 주소를 찾아 값을 구했어요.

📞값에 의한 호출 vs 주소에 의한 호출

C언어는 함수에 인자를 전달 할 때, "값에 의한 호출" (call by value) 방식을 사용해요. 다시 말해서, 함수가 인자를 받을 때는 원본을 그대로 받지 않고, 값을 복사해온다는 뜻이에요.

void swap(int a, int b)
{
	int temp = a;
    a = b;
    b = temp;
}

두 변수의 값을 바꿔주는 함수 swap을 작성했어요. 의도한 대로라면, a와 b에 저장된 값은 함수 실행 이후에 서로 바뀌어야 해요.

int i = 3, b = 5;
swap(i, j);
printf("i = %d, j = %d\n", i, j);

하지만, 실제로 출력된 결과를 확인해 보면 i와 j는 처음의 값에서 변하지 않은 것을 알 수 있어요. C언어가 값에 의한 참조로 함수에 인자의 값을 복사해서 전달하기 때문에, 함수 내부에서 원본 변수의 값을 변경하지 못한거에요.
만약 인자를 포인터 변수로 받으면 어떻게 될까요?

void swap(int *a, int *b)
{
	int temp = *a;
    *a = *b;
    *b = temp;
}
int i = 3, b = 5;
swap(&i, &j);
printf("i = %d, j = %d\n", i, j);

이제 의도한대로 i와 j의 값이 변경되었어요! 바꾸고자 하는 두 int형 변수를 포인터 변수로 받아, 함수 내부에서 직접 메모리에 접근해 원본에 영향을 주었기 때문이에요. 이렇게 값이 아닌 주소로 인자를 전달하는 방식을 "주소에 의한 호출" (call by reference) 이라고 불러요.


배운 내용들을 정리해보고 있어요. 잘못 기재된 내용이 있다면, 댓글로 지적해주시면 수정할게요.

profile
새내기 대학생 개발자에요 :D

0개의 댓글