codingCat banner
codingCat banner
2018.11.01 12:06

const 위치에 따른 구문의 의미


네이버 지식iN에 이런 질문이 올라와서 이에 답변을 달았던 적이 있다.

예제보니까 parameter에
void foo(int* const* bar)
이렇게 있더라고요;; 이게 definition으로서 가능해요? 만일 가능하다면 도대체 이렇게 정의하는 목적이 뭔지;;
그리고 만일 이게 된다면 const* int* bar도 되나요?? 답변 부탁드리겠습니다 ㅠ

- [네이버 지식iN] C++ int*const* 변수이름 은 도대체 뭐죠??

본디 상수 특성을 지정하는 const 키워드는 그 놓인 위치에 따라 약간의 미묘한 의미 차이를 갖는다. 본 포스팅에서는 const 키워드가 들어간 구문의 정확한 의미를 정리해 보고자 한다.

const의 선언 위치


const는 선언 문장 내 여러 곳에 놓일 수 있다. 그 때문에 의미가 혼동되고 읽기 불편할 수도 있다.

먼저 변수를 선언할 때에는 다음과 같이 const가 올 수 있다.

[const] 자료형 [const] [포인터] [const] 변수명;

 

함수를 선언할 때에는 다음과 같이 const가 올 수 있다.

[const] 자료형 [const] [포인터] 함수명(매개변수…) [const] { 함수본문 }

 

const 키워드가 어느 곳에 붙든 결국 원칙은 이것이다.

const가 걸리는 위치에서는 값 변경이 안 된다.

그렇다면 값 변경이 안 되는 경우(어디서 값 변경이 안 되는가?)를 찾는 것이 관건이다. 질문 원문에서 소개 된 3가지 경우를 예로 들어보면,

  1. const int ** foo;
  2. int * const * foo;
  3. int ** const foo;

이 3가지의 자료형이 있다. const가 붙는 위치가 조금씩 다름을 알 수 있는데 이 차이를 구분할 수 있다면 C의 고수이다.

위의 3가지 형식을 괄호로 쳐 본다면 다음과 같이 묶을 수 있다.

  1. (const int) ** a;
  2. (int * const) * a;
  3. (int **) const a;

 

'const 자료형 **'의 해설


1번은 const int 형에 대한 이중 포인터이다. 즉 int형 상수에 대한 이중 포인터라는 뜻이다. 보다 쉽게 나타낸다면 이런 과정으로 만들어지는 타입이다.

{
const int a = 10;
const int * b = &x; 
const int ** c = &y;
}

 

위와 같이 &b의 자료형이 const int ** 형이라 보면 된다. 알맹이 값인 a = 10이라는 값이 상수처리 되는 것이다.

반복하면,

{
  c : const int ** 형 (값 변경 O)
 *c : const int * 형 (값 변경 O)
**c : const int 형 (값 변경 X)
}

이므로,

int main(int argc, char * argv[]) {
    const int a = 10;
    const int * b = &a;
    const int ** c = &b; // 이게 const int ** 형식임.
    
      c = 0x00000000; // 대입시 에러 없음
     *c = 0x00000000; // 대입시 에러 없음
    **c = 5; // 여기서 에러.
    
    return 0;
}

 

'자료형 * const *'의 해설


2번은 int * 형이 갖는 주소 값을 상수로 다룬다는 뜻이다. 즉 int *형 상수에 대한 포인터라는 뜻이다. 보다 쉽게 나타낸다면 이런 과정을 통해 만들어지는 타입이다.

{
int a = 10;
int * const b = &a;
int * const * c = &b;
}

 

위와 같이 &b의 자료형이 int * const * 형이라 보면 된다. 여기서 알맹이 값인 a = 10은 수정이 자유로우나, 이것이 위치한 주소(a의 주소)를 갖고 있는 b가 상수처리 되는 것이다.

반복하면,

{
  c : int * const * 형 (값 변경 O)
 *c : int * const 형 (값 변경 X)
**c : int 형 (값 변경 O)
}

이므로,

int main(int argc, char * argv[]) {
    int a = 10;
    int * const b = &a; // 여기서 &b가 100% 완벽한 int * const * 형을 갖게 된다.
    int * const * c = &b; // 이게 int * const * 형이다.
    
      c = 0x00000000; // 대입시 에러 없음
     *c = 0x00000000; // 여기서 에러.
    **c = 5; // 대입시 에러 없음
    
    return 0;
}

 

'자료형 ** const'의 해설


3번은 int ** 타입 자체가 상수라는 뜻이다, 즉 이런 과정을 통해 만들어지는 타입이다.

{
int a = 10;
int * b = &a;
int ** const c = &b;
}

 

위와 같이 &b의 자료형을 상수로 처리하는 것이 int ** const 형이라 보면 된다.

{
  c : int ** const 형 (값 변경 X)
 *a : int *  형 (값 변경 O)
**a : int 형 (값 변경 O)
}

이므로,

int main(int argc, char * argv[]) {
    int a = 10;
    int * b = &a;
    int ** const c = &b;
    
      c = 0x00000000;// 여기서 에러.
     *c = 0x00000000; // 대입시 에러 없음
    **c = 5; // 대입시 에러 없음
    
    return 0;
}

 

마무리


지식 iN에도 올렸지만, 다시 한번 표로 정리하면 아래와 같이 요약 가능하다.

선언된 형식const int ** aint * const * aint ** const a
a의 형식const int ** 형 (값 변경 O)int * const * 형 (값 변경 O)int ** const 형 (값 변경 X)
*a의 형식const int * 형 (값 변경 O)int * const 형 (값 변경 X)int * 형 (값 변경 O)
**a의 형식const int 형 (값 변경 X)int 형 (값 변경 O)int 형 (값 변경 O)

 

덧 1. 문자열 상수와 const


strcpy 같은 함수를 보면 두 번째 인수가 const char * 형으로 되어 있을 것이다. 풀이해 보면, 그 문자열의 주소는 경우에 따라 가변적이지만 문자열이 갖는 자체의 내용만큼은 변함이 없다. 이런 뜻이다.

이를 테스트해보자.

void test(const char * str) {
    str = "이건 뭥미?"; // 대입 가능
}

str의 주소가 가리키는 문자열이 매개변수를 통해 넘어온 문자열이 아니라 전혀 새로운 문자열로 대치가 가능하다. 주소 자체에는 상수성이 없기 때문이다.

 

void test(const char * const str) {
    str = "이건 뭥미?"; // 대입 불가
}

문자열의 내용 자체가 불변인 것은 위와 같다. 또한 문자열을 가리키는 주소 또한 상수성을 갖고 있으므로 전혀 다른 문자열로 대치하는 것 또한 불가하다.

 

void test(char * str) {
    str = "ABCD"; // 대입 가능
    str[0] = 'A'; // 대입 가능
}

문자열의 내용과 문자열이 가리키는 주소 모두 상수성이 없다. 자유롭게 대치 및 수정 가능하다.

 

void test(const char * str) {
    str = "ABCD"; // 대입 가능
    str[0] = 'A'; // 대입 불가
}

문자열이 가리키는 주소 자체는 상수성이 없으므로 전혀 다른 문자열로 대치가 가능하다. 그러나 문자열의 내용에 대해서는 상수성이 있으므로 const char *로 선언된 문자열은 일부 내용의 수정이 불가하다.

 

덧 2. 멤버 함수와 const


C 언어에서 사용되는 const는 이 정도에서 끝나지만 C++로 넘어올 경우 하나가 더 있다. 멤버 함수의 scope에 붙이는 const 키워드이다.

class Integer {
    private:
        int value;
    public:
        int getInteger() const;
        void setInteger(int value);
}

int Integer::getInteger() const {
    return this->value;
}

void Integer::setInteger(int value) {
    this->value = value;
}

 

위와 같이 멤버 함수의 scope에도 상수성을 부여할 수 있다. 이것의 의미는...

이 함수는 멤버 변수의 값을 읽을 수는 있어도 수정할 수는 없다.

이다. 만일,

int Integer::getInteger() const {
    this->value = -1; // 오류 발생
    return this->value;
}

위와 같은 코드를 실행한다면 const가 부여된 scope 내에서 멤버 변수의 수정을 시도하므로 오류가 발생할 것이다. 또한,

int Integer::getInteger() const {
    this->setInteger(-1); // 오류 발생
    return this->value;
}

또한 위와 같이 scope에 const 속성이 없는 다른 함수를 호출하는 것도 오류가 발생한다. 상수 함수에서 호출 가능한 멤버함수는 같은 상수 함수 뿐이다. 반대로 scope에 const가 없는 함수는 상수 함수이든 비 상수 함수이든 호출이 가능하다. 이렇게 선언하는 이유는 객체의 상수성 때문이다.

{
    Integer i1;
    const Integer i2;

    i1.setInteger(-1); // 가능
    i1.getInteger(); // 가능
    i2.setInteger(-1); // 불가
    i2.getInteger(); // 가능
}

객체 i1은 상수성이 없고, 객체 i2는 상수성이 있다. 상수성이 있는 객체는 멤버 변수의 수정이 불가능하므로 scope에 const가 명시된 멤버 함수만이 호출 가능하다. 따라서 i2.setInteger(-1);와 같은 호출은 불가능하다.

 


trackbacks: 0, comments: 0
 
Comments
 
 
'Language/C & C++' Related Articles