[C] 혼공단_6주차_Chapter 09

Haeun Noh·2022년 8월 21일
0

0816


기본 미션 : 포인터의 핵심 내용을 정리하고 공유하기

선택 미션 : 나만의 언어로 포인터 정리하기

왜.. 벌써 개학이지

드디어 오늘 개학을 해버렸다.
2주동안 자격증 공부만 하다가 한 주 쉬었는데 벌써 개학날이 오늘이다.
시간은 참 빠른 것 같지만 이번 방학은 나름대로 알차게 할 거 다하면서 푹 쉰 것 같아서 나름대로 만족한 방학이다.

드디어 포인터를 배운다. C언어에서 정말 악명높은 포인터를..
선생님께서 너네 포인터 배우면 난리난다고 하셨던 말씀이 떠올라서 배우기도 전에 두렵기만 하지만 괜찮다!
뭐든 흡수해서 내 것으로 만들면 되기 때문이다.
어차피 해야하고 배울 내용인데 미리 내 것으로 만들어놓으면 수업시간에 따라가기 훨씬 쉬울 것이다.
그럼 정리를 시작해보도록 하겠다.



포인터의 핵심 내용을 정리하고 공유하기

핵심내용을 정리하고 공유하기! 뭔가 마지막이 되면 될 수록 정리하고 공유하는 미션이 많아진다. 갈수록 내용들이 어려워져서 그런 것도 같다. 이번에도 전체적으로 정리하면서 미션을 수행해보도록 하겠다.


메모리의 주소

메모리 : 우리가 데이터를 넣고 꺼내 쓰는 공간

우리는 데이터가 어디 있는지, 그 위치를 식별할 수 있어야 한다. 혼공단 미션을 했어도 그 블로그 글이 어디있는지 모른다면 제출을 할 수 없는 것이다.

이렇게 주소를 찾을 수 있게 프로그램이 사용하는 메모리의 위치주소 값으로 식별할 수 있다. 메모리의 위치를 식별하는 주소 값byte단위로 구분된다.

예를 들어 double형 변수 d메모리 1029번지부터 1037번지까지 8byte에 걸쳐 할당된다고 해보자. 변수 선언 후에는 8byte 전체를 d라는 이름으로 사용하게 된다.
따라서 d = 10.29;와 같은 문장은 메모리의 1029부터 1037번지까지의 8byte공간에 10.29의 값을 저장하는 것이며, d + 0.81;과 같은 수식은 메모리의 1029번지부터 1037번지까지의 8byte공간에 저장된 값과 0.81을 더하는 연산을 수행하는 것과 동일하다.

결국 지금까지는 변수명으로 메모리의 공간이나 값을 간단히 사용할 수 있었던 것이다.

우리가 늘 하던 방식이라 이해하기 쉬울 것이다.


주소 연산자 : &

: 변수가 할당된 메모리 공간의 시작 주소 값을 알려주는 주소 연산자

  • 단항 연산자
  • 변수만을 피연산자로 사용하여 시작 주소를 구한다.

주소 연산자의 형태는 다음과 같다.

&변수명

ex) &a

우리는 늘 저장된 공간을 이름, 즉 변수명으로 사용했었다. 이번에는 주소로 사용하는 방법에 대해서 살펴보자.

주소 : 변수가 할당된 메모리 공간의 시작 주소

주소는 주소 연산자 &를 사용해서 구할 수 있다.
예제를 통해 주소 연산자 &의 사용법을 익히고 변수가 할당된 메모리의 상태를 확인해보자.

✏️9-1 직접 해보는 손코딩_변수의 메모리 주소 확인

소스코드 예제9-1.c

#include <stdio.h>

int main(void) {
	int a;		//int형 변수 선언
	double b;	//double형 변수 선언
	char c;		//char형 변수 선언

	printf("int형 변수의 주소 : %u\n", &a);
	printf("double형 변수의 주소 : %u\n", &b);
	printf("char형 변수의 주소 : %u\n", &c);

	return 0;
}
실행결과

int형 변수의 주소 : 2911894660
double형 변수의 주소 : 2911894696
char형 변수의 주소 : 2911894724

💡프로그램의 실행결과가 달라요

컴퓨터는 프로그램 실행 후 남아있는 메모리를 활용하므로 실행결과가 저마다 다를 수 있다. 에러가 아니니 당황하지 않아도 된다.


int a;		//int형 변수 선언
double b;	//double형 변수 선언
char c;		//char형 변수 선언

위의 소스는 변수를 선언하는 부분이다.
저렇게 변수를 선언하면 프로그램 내에서 각 자료형의 크기만큼 메모리에 저장 공간이 할당된다. 이 때에 변수가 메모리 공간 어디에 할당되었는지 궁금하다면 주소 연산자 &를 사용하면 된다.


printf("int형 변수의 주소 : %u\n", &a);
printf("double형 변수의 주소 : %u\n", &b);
printf("char형 변수의 주소 : %u\n", &c);

위의 소스는 변수의 주소를 출력하는 부분이다.
출력할 때 %u는 변환문자로, 부호없는 10진수로 출력하라는 뜻이다.
주소는 보통 16진수로 표기하기 때문에 주소를 출력할 때에는 전용 변환 문자%p를 사용하지만 여기서는 설명의 편의를 위해 주소 값10진수로 출력하며 주소는 음수가 없으므로 %u 변환문자를 사용했다.

변환 문자의 종류는 다음과 같다.

1. %d : 10진수로 출력 정수형
2. %f : 실수형
3. %e : 지수형
4. %o : 8진수로 출력 
5. %x : 16진수로 출력 
6. %u : 부호없는 10진수로 출력 
7. %g : 실수형으로 자동 출력
8. %p : 포인터의 주소를 출력
9. %c : 하나의 문자로 출력 문자형 
10. %s : 문자열을 출력

변환문자 출처 - 자세한 내용은 여기를 클릭하세요


포인터와 간접 참조 연산자 : *

9-1 직접 해보는 손코딩은 단지 메모리의 주소만을 구해 출력하는 프로그램이다. 이제 변수에 할당된 메모리 주소를 활용하는 방법을 살펴보자.

포인터란?

: 변수의 메모리 주소를 저장하는 변수

메모리의 주소는 필요할 때마다 계속 주소 연산을 수행하는 것보다 한 번 구한 주소를 저장해서 사용하면 편리한데, 이 역할을 포인터가 수행한다.

포인터를 사용할 때는 변수처럼 선언하고 사용하면 되는데, 다만 선언할 때 변수 앞에 *를 붙여주고 사용해야 한다.

포인터의 형태는 다음과 같다.

자료형 *변수명;

예제를 통해 포인터의 선언과 사용법을 살펴보자.

✏️9-2 직접 해보는 손코딩_포인터의 선언과 사용

소스코드 예제9-2.c

#include <stdio.h>

int main(void) {
	int a;		//일반 변수 선언
	int *pa;	//포인터 선언

	pa = &a;	//포인터에 a의 주소 대입
	*pa = 10;	//포인터로 변수 a에 10 대입

	printf("포인터로 a 값 출력 : %d\n", *pa);
	printf("변수명으로 a 값 출력 : %d\n", a);	//변수 a값 출력

	return 0;
}
실행결과

포인터로 a 값 출력 : 10
변수명으로 a 값 출력 : 10

위의 소스코드를 면밀히 분석해보자.

포인터의 자료형은 저장할 주소가 어떤 변수의 주소인지 그 변수의 자료형을 적는다. 예를 들어 int형 변수의 주소를 저장하면 int를 사용하고 double형 변수의 주소를 저장하면 double을 사용한다.

int *pa;	// 6행. 포인터 선언

6행도 선언된 변수의 형태가 int형이므로 int를 사용하여 포인터를 선언한다.


포인터 변수가 선언되면 일반변수와 마찬가지로 메모리에 저장 공간이 할당되고 그 이후에는 변수명으로 사용할 수 있다.

pa = &a;		// 8행. 포인터에 a의 주소 대입

8행은 포인터에 a의 시작 주소를 저장하는 문장이다.


이제 포인터 pa는 변수 a가 메모리 어디에 할당되었는지 그 위치를 기억하고 있다. 이렇게 포인터가 어떤 변수의 주소를 저장한 경우에는 가리킨다라고 하고 둘의 관계를 pa -> a 처럼 화살표로 간단히 표현한다.

pa -> a		// 포인터 pa는 변수 a를 가리킨다.

위를 해석하면 pa포인터이며 변수 a의 주소를 저장하고 있다는 뜻이 된다.

이처럼 포인터가 어떤 변수를 가리키면 포인터로 가리키는 변수를 사용할 수 있다. 즉, 포인터 pa로 변수 a를 사용할 수 있다는 것이다.

포인터가 가리키는 변수를 사용할 때는 포인터에 간접 참조 연산자 (*) 또는 포인터 연산자를 사용한다.


위에서 말한 포인터 연산자가 9행에 사용된다.

포인터 paa를 가리키므로, 즉 paa의 주소값이 들어있어서 a가 있는 곳으로 가므로 *pa10을 대입하면 결국 a10을 대입하는 것과 똑같다.

*pa = 10;		// 9행. 포인터로 변수 a에 10 대입

printf("포인터로 a 값 출력 : %d\n", *pa);		// 11행
printf("변수명으로 a 값 출력 : %d\n", a);		// 12행. 변수 a값 출력

마찬가지로 11행에서 *pa를 출력하면 a값이 출력된다.
12행에서 a를 출력한 결과와 같음을 확인할 수 있다.

즉, 9-2 직접 해보는 손코딩에서 *paa의 값이 동일하다는 것을 알 수 있다.


*paa의 쓰임과 마찬가지로 대입 연산자 (=)의 왼쪽에 올 때는 pa가 가리키는 변수의 저장공간 (l-value)로 사용되고, 오른쪽에 올 때는 pa가 가리키는 변수의 값 (r-value)로 사용된다. 물론 연산하거나 출력할 때도 값으로 사용한다.


만약 여기서 scanf를 사용하면 어떻게 될까?

scanf는 입력할 변수가 메모리 어디에 할당 되었는지 저장공간의 위치를 알아야 한다. 따라서 입력할 변수의 주소를 인수로 준다.

포인터 pa를 통해 변수 a의 값을 입력할 때도 동일하다. *paa와 같으므로 &a&*pa와 같다. 즉, 간접 참조 연산자로 pa가 가리키는 변수를 구하고 다시 주소 연산자로 주소를 구한다.

그런데 paa의 주소를 저장하고 있으므로 바로 pa를 사용해도 된다.


&a로 변수 a의 저장 공간 찾기

scanf("%d", &a);

pa로 변수 a의 저장 공간 찾기

scanf("%d", pa);	// 포인터 pa값은 &a

위의 소스코드들은 전부 내가 입력한 정수값이 a변수에 저장되는 형태이다.


여러 가지 포인터 사용해보기

포인터가 어떤 변수를 가리키게 되면 그 이후에는 간접 참조 연산자를 통해 가리키는 변수를 자유롭게 사용할 수 있다.

예제를 통해 다양한 포인터의 사용법을 살펴보자.

✏️9-3 직접 해보는 손코딩_포인터를 사용한 두 정수의 합과 평균 계산

소스코드 예제9-3.c

#include <stdio.h>

int main(void) {
	int a = 10, b = 15, total;		//변수 선언과 초기화
	double avg;						//평균을 저장할 변수
	int* pa, * pb;					//포인터 동시 선언
	int* pt = &total;				//포인터 선언과 초기화
	double* pg = &avg;				//double형 포인터 선언과 초기화

	pa = &a;				//포인터 pa에 a의 주소 할당
	pb = &b;				//포인터 pb에 b의 주소 할당

	*pt = *pa + *pb;		//a값과 b값을 더한 값을 total에 저장
	*pg = *pt / 2.0;		//total을 2.0으로 나눈 값을 avg에 저장

	printf("두 정수의 값 : %d, %d\n", *pa, *pb);		//a값과 b값 출력
	printf("두 정수의 합 : %d\n", *pt);				//합계 출력
	printf("두 정수의 평균 : %.1f\n", *pg);			//평균 출력

	return 0;
}
실행결과

두 정수의 값 : 10, 15
두 정수의 합 : 25
두 정수의 평균 : 12.5

int a = 10, b = 15, total;		//변수 선언과 초기화
double avg;						//평균을 저장할 변수
int* pa, * pb;					//포인터 동시 선언
int* pt = &total;				//포인터 선언과 초기화
double* pg = &avg;				//double형 포인터 선언과 초기화

pa = &a;				//포인터 pa에 a의 주소 할당
pb = &b;				//포인터 pb에 b의 주소 할당

a의 값은 10이고 b의 값은 15이다.
포인터 pa에는 a의 주솟값이, 포인터 pb에는 b의 주솟값이, 포인터 pt에는 total의 주솟값이, 포인터 pg에는 avg의 주솟값이 저장되어 있다.


*pt = *pa + *pb;		//a값과 b값을 더한 값을 total에 저장
*pg = *pt / 2.0;		//total을 2.0으로 나눈 값을 avg에 저장

위의 말에 따라서 *pt = *pa + *pb;total = a + b;와 같다.
또한 *pg = *pt / 2.0;avg = total / 2.0;와 같다.


printf("두 정수의 값 : %d, %d\n", *pa, *pb);		//a값과 b값 출력
printf("두 정수의 합 : %d\n", *pt);				//합계 출력
printf("두 정수의 평균 : %.1f\n", *pg);			//평균 출력

a b 두 정수의 값을 출력하는 것은 *pa *pb를 출력하는 것과 같다.
a b 두 정수의 합을 출력하는 것은 *pa + *pb*pt를 출력하는 것과 같다.
a b 두 정수의 평균을 출력하는 것은 *pt / 2.0*pg를 출력하는 것과 같다.

결국 일반 변수를 a b total avg와 같은 이름으로도 사용할 수 있고 그 변수들을 가리키는 포인터를 간접 참조해도 사용할 수 있음을 보여준다.


const를 사용한 포인터

const예약어는 상수를 배울 때 배웠을 것이다. const예약어를 포인터에 사용하면, 이는 가리키는 변수의 값을 바꿀 수 없다는 의미로, 변수에 사용하는 것과는 다른 의미를 가진다.

예제를 통해 포인터에 const예약어를 사용하는 예시를 살펴보자.

✏️9-4 직접 해보는 손코딩_포인터에 const 사용

소스코드 예제9-4.c

#include <stdio.h>

int main(void) {
	int a = 10, b = 20;
	const int* pa = &a;			//포인터 pa는 변수 a를 가리킨다.

	printf("변수 a 값 : %d\n", *pa);		//포인터를 간접 참조하여 a 출력
	pa = &b;							//포인터가 변수 b를 가리키게 한다.
	printf("변수 b 값 : %d\n", *pa);		//포인터를 간접 참조하여 b 출력
	pa = &a;							//포인터 pa가 다시 a를 가리키게 한다.
	a = 20;								//변수 a 값을 직접 참조하여 20으로 바꾼다.
	printf("변수 a 값 : %d\n", *pa);		//포인터로 간접 참조하여 바뀐 값 출력

	return 0;
}
실행결과

변수 a 값 : 10
변수 b 값 : 20
변수 a 값 : 20

어라? const로 상수화한 *pa의 주소값을 변경했는데도 변경한 값 그대로 잘 출력이 됐다.

그렇다면 포인터에 사용된 const의 의미는 무엇일까?
바로 pa가 가리키는 변수 a는 pa를 간접 참조하여 바꿀 수 없다는 것이다.

만약 *pa = 20;과 같이 pa를 통해 a값을 바꾸고자 한다면 다음의 에러 메시지를 보게 될 것이다.

에러
error C2166: l-value가 const 개체를 지정합니다.

💡여기서 잠깐_포인터에 const를 사용하는 이유

변수 a는 어디까지나 포인터를 통해서만 바꿀 수 없으며 변수 a 자체를 사용하면 얼마든지 바꿀 수 있다.

그렇다면 왜 포인터에 const를 사용하는 것일까?

포인터에 const를 사용하는 대표적인 예는 문자열 상수를 인수로 받는 함수이다. 문자열 상수값이 바뀌면 안 되는 저장공간이므로 함수의 매개변수를 통해서 값을 바꿀 수 없도록 매개변수로 선언된 포인터에 const를 사용한다.



마무리 정리

  • 포인터는 메모리는 사용하는 또 다른 방법이다.
  • 주소 연산자 &로 변수가 할당된 메모리의 위치를 확인한다.
  • 포인터로 가리키는 변수를 사용할 때 간접 참조 연산자 *를 쓴다.

포인터와 연산자

구분사용 예기능
주소 연산자int a;
&a;
변수 앞에 붙여 사용하며, 변수가 할당된 메모리의 시작 주소 값을 구한다.
포인터char *pc;
int *pi;
double *pd;
시작 주소 값을 저장하는 변수며, 가리키는 자료형을 표시하여 선언한다.
간접 참조 연산자*pi = 10;포인터에 사용하며, 포인터가 가리키는 변수를 사용한다.


들어가며...

포인터는 주소를 저장하는 일정한 크기의 메모리 공간이다. 따라서 언제든지 다른 주소를 저장하거나 포인터끼리 대입할 수 있다.

그러나, 일반 변수와는 달리 대입 연산에 엄격한 기준이 적용된다. 이런 특징을 이해하는 것은 포인터를 더 잘 활용하는 데에 도움을 줄 것이다.


주소와 포인터의 차이

주소 : 변수에 할당된 메모리 저장 공간의 시작 주소 값 자체
포인터 : 그 값을 저장하는 또 다른 메모리 공간

따라서 특정 변수의 주소 값은 바뀌지 않지만, 포인터는 다른 주소를 대입하여 그 값을 바꿀 수 있다.
이 말은 한 마디로 주소상수이고, 포인터변수라는 것이다.

따라서 두 포인터가 같은 주소를 저장하는 일, 즉 하나의 변수를 동시에 가리키는 일도 가능하다.

int a;				//일반 변수 선언
int *pa, *pb;		//가리키는 자료형(여기서는 int형)이 같은 두 포인터
pa = pb = &a;		//pa와 pb에 모두 a의 주소를 저장

이 경우, a값을 바꾸거나 연산하는 데에 pa pb를 모두 사용할 수 있다.


주소도 포인터처럼 간접 참조 연산자를 쓸 수 있지만, 상수이므로 대입 연산자 왼쪽에 올 수 없다.

&a = &b;		//a의 주소를 b의 주소로 바꾸는 것은 불가능

즉, 위의 소스코드에서 알 수 있듯이 상수변수는 용도가 분명히 다르므로 주소포인터는 서로 구분하여 이해해야 한다.


주소와 포인터의 크기

포인터저장공간이므로 그 크기가 있다. 포인터의 크기저장할 주소의 크기에 따라 결정된다.

모든 주소포인터는 가리키는 자료형에 관계 없이 크키가 같다.

주소포인터의 크기는 sizeof연산자로 확인할 수 있다.
예제를 통해 확인해보자.

✏️9-5 직접 해보는 손코딩_주소와 포인터의 크기

소스코드 예제9-5.c

#include <stdio.h>

int main(void) {
	char ch;
	int in;
	double db;

	char* pc = &ch;
	int* pi = &in;
	double* pd = &db;

	printf("char형 변수의 주소 크기 : %d\n", sizeof(&ch));
	printf("int형 변수의 주소 크기 : %d\n", sizeof(&in));
	printf("double형 변수의 주소 크기 : %d\n", sizeof(&db));

	printf("char * 포인터의 크기 : %d\n", sizeof(pc));
	printf("int * 포인터의 크기 : %d\n", sizeof(pi));
	printf("double * 포인터의 크기 : %d\n", sizeof(pd));

	printf("char * 포인터가 가리키는 변수의 크기 : %d\n", sizeof(*pc));
	printf("int * 포인터가 가리키는 변수의 크기 : %d\n", sizeof(*pi));
	printf("double * 포인터가 가리키는 변수의 크기 : %d\n", sizeof(*pd));

	return 0;
}
실행결과

char형 변수의 주소 크기 : 8
int형 변수의 주소 크기 : 8
double형 변수의 주소 크기 : 8
char * 포인터의 크기 : 8
int * 포인터의 크기 : 8
double * 포인터의 크기 : 8
char * 포인터가 가리키는 변수의 크기 : 1
int * 포인터가 가리키는 변수의 크기 : 4
double * 포인터가 가리키는 변수의 크기 : 8

위의 소스코드에서 ch in db는 각각의 변수 자체의 크기는 다르지만, 그 시작 주소 값의 크기는 8로 모두 같다는 것을 볼 수 있다.


포인터의 대입 규칙

포인터는 다음 규칙에 따라 제한적으로 사용해야 한다.

1. 포인터는 가리키는 변수의 형태가 같을 때만 대입해야 한다.

포인터끼리 대입 연산을 수행하면 여러 개의 포인터로 같은 데이터를 다루는 것이 가능하다.

가리키는 자료형이 일치하지 않는 포인터의 대입을 시도하면 경고 메시지가 뜬다.

경고
warning C4133: '=' : 'int *'() 'double *' 사이의 형식이 호환되지 않습니다.

2. 형 변환을 사용한 포인터의 대입은 언제나 가능하다.

포인터가 가리키는 자료형이 다른 경우라도 형 변환 연산자를 사용하면 경고 메시지 없이 대입할 수 있다.

다음과 같은 예시를 살펴보자.

double a = 3.4;			//double형 변수 선언
double *pd = &a;		//double형 포인터 pd에 a의 주소 값 대입
int *pi;				//int형 포인터 pi 선언
pi = (int *)pd;			//double형 pd를 int로 형 변환 시켜서 pi에 대입

이처럼 형 변환을 시킨 포인터는 대입이 가능하다는 것을 알 수 있다.


포인터를 사용하는 이유

포인터를 사용하려면 추가적인 변수 선언이 필요하고, 주소 연산, 간접 참조 연산 등 각종 연산을 수행해야 한다. 그러니 포인터를 일부러 즐겨 사용할 필요는 없는 것이다.

그러나

  1. 임베디드 프로그래밍을 할 때 메모리에 직접 접근하는 경우
  2. 동적 할당한 메모리를 사용하는 경우

에는 포인터가 반드시 필요하다.


💡여기서 잠깐_임베디드 프로그래밍이 뭔가요?

임베디드 프로그래밍 : 임베디드 시스템 (Embedded System : 내장형 시스템)을 제어하기 위한 프로그램

임베디드 프로그래밍은 오늘날 만드는 거의 모든 생활 기기에서 특정 기능을 제어하기 위해 구현된다.

예를 들어 정수기에서 정수, 냉수를 구분해 물이 나오게 한다든가, 선풍기의 풍량을 조절하는 것이다. 이처럼 각 기능을 담당하는 하드웨어를 제어하는 소프트웨어를 만드는 일을 임베디드 프로그래밍이라고 한다.


두 변수의 값을 바꾸며 포인터 이해하기

두 변수의 값을 바꾸는 함수를 통해 포인터의 필요성을 확인해보자.

✏️9-7 직접 해보는 손코딩_포인터를 사용한 두 변수의 값 교환

소스코드 예제9-7.c

#include <stdio.h>

void swap(int* pa, int* pb);		// 두 변수의 값을 바꾸는 함수의 선언

int main(void) {
	int a = 10, b = 20;			//변수 선언과 초기화

	swap(&a, &b);						// a, b의 주소를 인수로 주고 함수 호출
	printf("a:%d, b:%d\n", a, b);		//변수 a, b 출력

	return 0;
}

void swap(int* pa, int* pb) {			// 매개변수로 포인터 선언
	int temp;			//교환을 위한 임시 변수

	temp = *pa;			// temp에 pa가 가리키는 변수의 값 저장, a의 값 저장
	*pa = *pb;			// pa가 가리키는 변수에 pb가 가리키는 변수의 값 저장, a에 b의 값 저장
	*pb = temp;			// pb가 가리키는 변수에 temp의 값 저장, b에 a의 값 저장
}
실행결과

a:20, b:10

이 예제에서는 두 변수의 값을 swap함수 호출을 통해 바꾼다.


먼저 9행의 함수 호출에서 바꿀 변수 a b의 주소를 인수로 준 후 swap함수를 호출한다.

int *pa에는 a의 값이, int *pb에는 b의 값이 할당된다.
교환을 위한 임시 변수 tempa의 값인 *pa를 저장한다.
a의 값이 들어있는 *pab의 값이 들어있는 *pb를 저장한다.
b의 값이 들어있는 *pba의 값이 들어있는 temp을 저장한다.

아래의 소스코드들은 풀이의 해당하는 소스코드이다.

temp = *pa;			
*pa = *pb;			
*pb = temp;

위의 소스코드는 다음과 같다.

temp = a;
a = b;
b = temp;

교환 작업은 swap함수에서 포인터를 통해 진행되지만 실제 바뀌는 값은 main함수의 a b가 된다. 결국 swap함수는 포인터를 통해 main함수의 변수 a b를 공유하므로 두 변수를 직접 바꾸는 일이 가능해진다.


포인터 없이 두 변수의 값을 바꾸는 함수는 불가능한가?

우선 swap함수에서 main함수의 a b를 이름으로 직접 사용하는 방법을 생각해보겠다.

✏️ 9-8 직접 해보는 손코딩_다른 함수의 변수 사용하기

소스코드 예제9-8.c

#include <stdio.h>

void swap(void);		// 두 변수의 값을 바꾸는 함수 선언

int main(void) {
	int a = 10, b = 20;			// 변수 선언과 초기화

	swap();									// 인수 없이 함수 호출
	printf("a:%d, b:%d\n", a, b);			// 변수 a, b 출력

	return 0;
}

void swap(void) {				// 인수가 없으므로 매개변수도 없음
	int temp;					// 교환을 위한 변수

	temp = a;
	a = b;
	b = temp;
}

이 예제는 컴파일 과정에서 다음과 같은 에러 메시지를 볼 수 있다.

에러

error C2065: 'a' : 선언되지 않은 식별자입니다.
error C2065: 'a' : 선언되지 않은 식별자입니다.
error C2065: 'b' : 선언되지 않은 식별자입니다.
error C2065: 'b' : 선언되지 않은 식별자입니다.

위의 에러가 나는 원인은 간단하다. 지역변수를 다른 함수에서 같은 함수의 변수인 것마냥 사용했기 때문이다.

함수 안에 선언된 변수명은 사용 범위가 함수 내부로 제한되므로, main함수에 있는 변수 a b는 다른 함수인 swap함수에서 그 이름을 사용할 수 없다.


다음 방법은 main함수에서 a b의 값을 swap함수에 인수로 주는 방법이다.

✏️9-9 직접 해보는 손코딩_변수의 값을 인수로 주는 경우

소스코드 예제9-9.c

#include <stdio.h>

void swap(int x, int y);		// 두 변수의 값을 바꾸는 함수 선언

int main(void) {
	int a = 10, b = 20;			// 변수 선언과 초기화

	swap(a, b);							// a, b의 값을 복사해서 swap함수에 전달
	printf("a:%d, b:%d\n", a, b);		// 변수 a, b 출력	

	return 0;
}

void swap(int x, int y) {
	int temp;						// 교환을 위한 변수

	temp = x;			// temp에 x값 저장
	x = y;				// x에 y값 저장
	y = temp;			// y에 temp값 저장
}
실행결과

a:10, b:20

swap(a, b);에서 함수를 호출할 때 main함수의 변수 a b의 값이 복사되어 swap함수의 매개변수인 x y에 저장된다. 결국 swap함수 안에서는 a b의 복사본을 바꾸므로 main함수의 a b의 값은 변함이 없게 된다.

swap함수에서 매개변수의 이름을 a b로 사용해도 결과는 같다. 왜냐면 변수명이 같아도 함수가 다르면 전혀 다른 변수로 간주하기 때문이다.


마무리 정리

  • 주소와 포인터는 상수와 변수의 차이가 있다.
  • 포인터의 크기는 주소의 크기와 같다.
  • 포인터에 주소를 저장할 때는 가리키는 자료형이 같아야 한다.
  • 포인터의 주요 기능 중 하나는 함수 간에 효과적으로 데이터를 공유하는 것이다.

간접 참조 연산자를 사용한 예 (포인터 pa가 변수 a를 가리킬 때)

구분변수 a 사용포인터 pa 사용
대입 연산자 왼쪽a = 10;*pa = 10;
대입 연산자 오른쪽b = a;b = *pa;
피연산자a + 20;*pa + 20;
출력printf("%d", a);printf("%d", *pa);
입력scanf("%d", &a);scanf("%d", pa);
scanf("%d", &*pa);

주소와 포인터의 특징

구분사용 예기능
포인터int a, b;
int *p = &a;
p = &b;
포인터는 변수이므로 그 값을 다른 주소로 바꿀 수 있다.
포인터의 크기int *p;
sizeof(p)
포인터의 크기는 컴파일러에 따라 다를 수 있으며, sizeof연산자로 확인한다.
포인터의 대입 규칙int *p;
double *pd;
pd = p; (X)
포인터는 가리키는 자료형이 일치할 때만 대입한다.


혼공단의 대장정이 끝났다.

혼공단을 시작하게 된 계기, 혼공단을 하면서 느낀점, 발전한 점 등등을 벨로그에 한 번 써보는 시간을 가질 것이다.

리뷰 링크는 여기입니다!!!

진짜 안녕~



profile
기록의 힘을 믿는 개발자, 노하은입니다!

0개의 댓글