본문 바로가기
Language&Framework&Etc/C

포인터와 함수에 대한 이해(14-3) 포인터 대상의 const 선언

by 머리올리자 2020. 12. 8.

기존의 const 선언은 변수를 상수화하는 목적의 const 선언

이러한 const 선언은 포인터 변수를 대상으로도 선언이 가능하다.

포인터 변수가 참조하는 대상의 변경을 허용하지 않는 const 선언

#include <stdio.h>


int main(void)
{
	int num = 20;
	const int* ptr = &num;
	*ptr = 30; // 컴파일 에러!
	num = 40;  // 컴파일 성공!

}

const 선언에서 주의 깊게 봐야 할 것은 const의 선언 위치

맨 앞부분에 선언이 되면, 포인터 변수 ptr을 대상으로 다음의 의미가 담겨짐

 

"포인터 변수 ptr을 이용해서 ptr이 가리키는 변수에 저장된 값을 변경하는 것을 허용하지 않겠다"

 

때문에 *ptr=30에서 컴파일 에러가 발생한다.

 

그렇다고 해서 포인터 변수 ptr이 가리키는 변수 num이 상수화되는 것은 아님

다음과 같이 변수 num에 저장된 값을 변경하는 것은 허용

 

num=40;

 

위의 const 선언은 값을 변경하는 방법에 제한을 두는 것이지 무엇인가를 상수로 만드는 선언은 아니다.

 

 

헷갈려서 이해해보기..

 

const가 없었을 때는 포인터 변수를 통해 값의 변경을 허용했다.

#include <stdio.h>


int main(void)
{
	int num = 20;
	printf("%d\n", num);

	int* ptr = &num;
	*ptr = 30;
	printf("%d\n", num);

	num = 40;
	printf("%d\n", num);

}

그러나 "포인터 변수"에 const가 선언된 이후로 "포인터 변수"를 통해 값을 변경하는 것을 허용하지 않는다.

 

그러나 아래와 같이 main 함수에 선언된 num 변수는 값의 변경을 허용하며 실제로도 값이 바뀌어 있다.

#include <stdio.h>


int main(void)
{
	int num = 20;
	printf("%d\n", num);

	const int* ptr = &num;
	//*ptr = 30; // 컴파일 에러로 주석처리

	num = 40;
	printf("%d\n", num);

	printf("%d\n", *ptr);
}

 

그렇다면 궁금해졌다.... 그렇다면 변수 num에 const를 붙이면 포인터를 통해 값의 변경이 가능할까?

 

테스트 해보자

 

#include <stdio.h>


int main(void)
{
	const int num = 20; // 변수 num에 const
	printf("%d\n", num);

	int* ptr = &num; // 변수 num에 대한 포인터 변수
	*ptr = 30; // 포인터 변수를 통해 값을 변경해보자
	printf("%d\n", num);

	//num = 40; // const가 선언되었기 때문에 컴파일 에러
	printf("%d\n", num);
	printf("%d\n", *ptr);
}

보면 당연히 int num은 const 상수이기 때문에 num=40으로 값의 변경이 불가능하다.

 

그러나 분명 상수인데 포인터 변수(int* ptr = &num)로 접근해서 값을 변경(*ptr = 30) 하니 값이 변경된 것을 알 수 있다.

 

const 변수라고 해서 값을 무조건 변경을 못하는 것이 아니고 저자 말대로 "const 선언은 값을 변경하는 방법에 제한을 두는 것이지 무엇인가를 상수로 만드는 선언이 아니다" 라는 말이 이해가 된다.

 

포인터 변수의 상수화

int* const ptr = &num
  • 이렇게 되면 포인터 변수 ptr은 상수가 된다.
  • 포인터 변수 ptr이 상수라는 뜻은 한번 주소 값이 저장되면 그 값의 변경이 불가능하다는 뜻이며
  • 한번 가리키기 시작한 변수를 끝까지 가리켜야 한다
#include <stdio.h>


int main(void)
{
	int num1 = 20;
	int num2 = 30;
	int* const ptr = &num1;
	ptr = &num2; // 컴파일 에러!
	*ptr = 40;  // 컴파일 성공!

}
  • ptr이 num1을 가리키고 있다.
  • 다음, ptr =&num2를 통해 가리키는 대상을 바꾸기 위한 연산을 진행한다
  • 하지만 ptr은 상수 → 컴파일 에러가 발생
  • 그러나, ptr이 가리키는 대상에 저장된 값을 변경하는 연산(*ptr=40)은 문제가 되지 않는다.

const 선언을 동시에 할 수도 있음

const int* const ptr = &num

이러면 주소 값도 변경(ptr=&num)하지 못하고, 포인터 변수가 가르키는 값도 변경(*ptr=30)하지 못한다.

 

 

위 내용들에 대해 아래와 같이 정리해보았다.

#include <stdio.h>


int main(void)
{
	int num1 = 20;
	int num2 = 30;
	int num3 = 40;

	const int* pnum1 = &num1;
	pnum1 = &num2;
	*pnum1 = 30; // 컴파일 에러

	int* const pnum2 = &num2;
	pnum2 = &pnum1; // 컴파일 에러
	*pnum2 = 40;

	const int* const pnum3 = &num3;
	pnum3 = &num1; // 컴파일 에러
	*pnum3 = 30;   // 컴파일 에러
}

 

위 내용을 쉽게 정리해보면...

 

일단 const에 대한 역할을 다시 한번 적어보자

 

"const 선언은 값을 변경하는 방법에 제한을 두는 것"

 

방법에 제한을 두는 것이다

 

- 우선 const가 어디에 붙냐부터 보자

 

1. 포인터 변수 자료형 앞에 붙는다 const int* (pnum1)

   - 즉, 포인터 연산자*로 접근하여 값을 변경하는 방법에 제한을 둔다

   - *pnum1 = value 형태로 값을 바꿀 수 없다

   - 그러나 pnum1 앞에는 const가 없기 때문에 pnum1의 값을 바꿀 수 있다.

 

2. 포인터 변수 이름 앞에 붙는다  (int*) const pnum1

   - 즉, 포인터 변수 자체 값을 변경하는 방법에 제한을 둔다

   - *pnum1 = value 형태로 값을 바꿀 수 있다

   - 그러나 pnum1 앞에는 const가 있기 때문에 pnum1의 값을 바꿀 수 없다.

 

const 선언이 갖는 의미

const 선언은 중요하고 유용하다. 

const 선언을 많이 하면 그만큼 프로그램 코드의 안정성은 높아진다.

 

#include <stdio.h>


int main(void)
{
	double PI = 3.1415;
	double rad;

	PI = 0.7; // 실수로 잘못 삽입된 문장, 컴파일 시 발견 안됨
	scanf("%lf", &rad);

	printf("circle area %f \n", rad * rad * PI);

	return 0;
}

위는 원의 넒이를 계산하여 출력

원주율에 해당하는 값이 저장된 변수 PI의 값을 변경하는 실수

PI는 변경하면 안되는 값임

 

더 큰 문제는 컴파일러가 이러한 문제점을 발견하지 못한다는데 있음

컴파일러가 발견하지 못하는 오류 상황은 쉽게 발견되지 않음

 

따라서 다음과 같이 코드 작성

#include <stdio.h>


int main(void)
{
	const double PI = 3.1415;
	double rad;

	PI = 0.7; // 실수로 잘못 삽입된 문장, 컴파일 시 발견 안됨
	scanf("%lf", &rad);

	printf("circle area %f \n", rad * rad * PI);

	return 0;
}

위 const 선언을 함으로써 PI에 대한 안전성이 확보 되었다.

 

즉 const 선언 하나에는 매우 큰 가치를 부여할 수 있다.

 

참고 : [윤성우 열혈 C 프로그래밍] - 대부분의 내용 및 코드는 이 책에서 개인 공부 정리 목적으로 참고하였습니다.