본문 바로가기

Programming/C

const와 pointer

반응형

오늘 살펴볼 내용은 간단하지만 종종 헷갈리는 'const' keyword이다. 

사실 'const'에 대해서 알고있다고 생각했지만, 이번에 회사 프로젝트를 하면서 'const' 사용에 있어 실수가 있었다. 

안다고 생각하는 것과, 실제로 아는 것은 역시 차이가 있다.


const에 대해 살펴보기 전에, c언어에서 '변수(variable)'의 개념을 먼저 살펴보자.

흔히 변수를 '변하는 수' 라고 생각을 하지만 

컴퓨터 세계에서 '변수'란 근본적으로 말하면 '수를 저장할 수 있는 메모리 공간'을 의미한다.

1
2
3
4
5
6
7
8
9
#include <stdio.h>
 
int main()
{
    int a = 10;  //변수
 
    return 0;
}
 
cs

5번째 줄, int a = 10 을 다음과 같이 두 단계로 생각할 수 있다.

1. int(정수) 값을 담을 수 있는 공간을 할당하고 그 이름을 'a'라고 한다.

2. a라는 메모리 공간에 10 이라는 수를 저장한다. 


1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>
 
int main()
{
    int a = 10;  //변수
 
    a = 20;
 
    a = 30;
 
    return 0;
}
 
cs

a라는 공간에 처음에 10을 저장했다가, 그 후 10대신 20을 a라는 공간에 넣어 저장할 수 있고,

20이라는 수 대신 30을 저장할 수도 있다. 이처럼 변수는 우리가 생각하는 '변할 수 있는 수'이기도 하지만

근본적으로 '메모리 공간' 을 의미한다는 것을 기억하자.  



위 코드에서 보았듯이 변수 a에 저장되는 값은 변경이 얼마든지 가능하다.

하지만 오늘 우리가 살펴볼 'const' 가 붙으면 얘기는 달라진다. 

'const'가 붙으면 해당 메모리 공간은 '특정한 수'를 위한 '전용 공간'이 된다. 



1. 변수 + const


const를 사용하는 방법은 다음과 같다.   

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
 
int main()
{
    const int variable = 2// int const variable 과 동일
 
    return 0;
}
 
cs

1. const + type + 변수이름

2. type + const + 변수이름

두 개 모두 같은 의미이며 보통 1번을 주로 사용한다(는 것 같다.)




1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
 
int main()
{
    const int variable = 2;
 
    variable = 5//error
 
    return 0;
}
 

cs

위 코드를 컴파일 해 보면 다음과 같은 compile error를 볼 수 있다.

1
2
3
4
5
6
7
8
const1.c:7:11: error: cannot assign to variable 'variable' with const-qualified type 'const int'
        variable = 5;
        ~~~~~~~~ ^
const1.c:5:12: note: variable 'variable' declared const here
        int const variable;
        ~~~~~~~~~~^~~~~~~~
 
 
cs

위 코드 5번 째 줄을 보면, 'variable'이라는 공간은 ‘2’만을 위한 공간이 되는 것이다

그런데 그 다음 코드에서 '5'값을 넣어주려고 하고 있으니 error가 나는 것이다.


여기서 궁금점 !

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
 
int main()
{
    const int variable = 2;
 
    variable = 2;
 
    return 0;
}
 
cs

그럼 '2'를 위한 공간이니, 해당 값을 다시 assign하는 것이 가능할까?

답은 '아니다'. 5번 째 줄에서 '2'와, 7번째 줄에서 '2' 모두 값은 같지만 

변수에 const를 붙이면 다른 수에게 할당이 되지 않기 위해(비록 같은 수를 넣더라도) 

assign(=) 하는 것 자체를 에러라고 간주한다. 



쉽게 말해 const는 변수를 '상수화' 하는 것이다. 



이제까지 살펴본 것은 변수의 const였고, 

이제부터 많이 헷갈리는, 이 글을 작성하게 된 이유인, 포인터와 const가 만났을 때이다.


2. 포인터 + const

사용 방법은 다음과 같다.


1. const + pointer type + 포인터 이름

2. pointer type + const + 포인터 이름

3. const + pointer type + const + 포인터 이름

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
 
int main()
{
    int a = 10;
    const int *pA = &a;
    int *const pA = &a;
    const int *const pA = &a;
 
    return 0;
}
 
cs

<사용예시, 6,7,8th line>

여기서 주의해야 할 것은 각각 다른 역할로 const가 쓰인다.


하나씩 살펴보자.


1. const + pointer type + 포인터 이름

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>
 
int main()
{
    int a = 10;
    int b = 20;
    const int *pA = &a;
 
    *pA = 30;     // Error
    pA = &b;      // okay
 
    return 0;
}
 
cs


위 코드를 컴파일 하면 아래와 같은 error를 만나게 된다.

1
2
3
4
5
const2.c:9:6: error: read-only variable is not assignable
        *pA = 30;
        ~~~ ^
1 error generated.
 
cs

9 번째 줄에서 pA가 가리키는 공간에 들어있는 값을 30으로 변경할 때 에러가 난다.

즉, 'const + pointer type + 포인터 이름'은 '포인터가 가리키는 공간에 들어있는 값'을 상수화한다. 



2. pointer type + const + 포인터 이름

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>
 
int main()
{
    int a = 10;
    int b = 20;
    int *const pA = &a;
 
    *pA = 30; //okay
    pA = &b; //error
 
    return 0;
}
 
cs


위 코드를 컴파일하면 아래와 같은 error를 만나게 된다.

1
2
3
4
5
6
7
8
const3.c:10:5: error: cannot assign to variable 'pA' with const-qualified type 'int *const'
        pA = &b;
        ~~ ^
const3.c:7:13: note: variable 'pA' declared const here
        int *const pA = &a;
        ~~~~~~~~~~~^~~~~~~
1 error generated.
 
cs

10번째 줄, pA = &b  pA가 가리키는 공간을 바꾸려고 하면 다른 공간으로 바꾸려고 하면 error가 난다.

즉, 'pointer type + const + 포인터 이름'은 '포인터가 가리키는 공간'을 상수화한다.


3. const + pointer type + const + 포인터 이름

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>
 
int main()
{
    int a = 10;
    int b = 20;    
    const int *const pA = &a;
    
    *pA = 30; //error
    pA = &b; //error
 
    return 0;
}
 
cs


위 코드를 컴파일 해보면 아래와 같은 error가 나온다.

1
2
3
4
5
6
7
8
9
10
11
const4.c:9:6: error: read-only variable is not assignable
        *pA = 30;
        ~~~ ^
const4.c:10:5: error: cannot assign to variable 'pA' with const-qualified type 'const int *const'
        pA = &b;
        ~~ ^
const4.c:7:19: note: variable 'pA' declared const here
        const int *const pA = &a;
        ~~~~~~~~~~~~~~~~~^~~~~~~
2 errors generated.
 
cs

pA가 가리키는 공간에 들어있는 값을 바꾸려 할 때(9번째 줄),

pA가 가리키는 공간을 다른 공간으로 바꾸려고 할 때(10번째 줄)  에러가 난다.


'const + pointer type + const + 포인터 이름'은 '포인터가 가리키는 공간'도 상수화하고, '그 공간에 들어있는 값'도 상수화한다.

즉 1번과 2번을 동시에 적용하는 것이다. 



정리하면 다음과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const int *pA1    -->  (*pA1) 을 상수화. 
                     즉,*pA1의 type인 int를 상수화. 
                     pA1이 가리키고 있는 ‘int형 공간’ 안에 가지고 있는 ‘값’을 상수화 
                     pA1 = &b; pA2 = &c; 가능.
 
 
int *const pA2    -->  (pA2) 을 상수화. 
                     pA2의 type인 int* 를 상수화. 
                     즉 pA2가 가리키고 있는 ‘int형 공간’ 을 상수화. 
                     다른 공간을 가리킬 수 없게 됨. 
                     가리키고 있는 공간의 값은 변경 가능 
                     *pA2 = 3*pA2 = 4; 가능.
 
 
const int *const pA3 --> 위 두개 모두 적용
 
 
cs

const 뒤에 나오는 type을 상수화 한다는 것을 명심하자.

1st line : *pA1 이 나옴. --> *pA1은 int type 

7th line : pA2가 나옴  -->  pA2는 int* type




3. 1차원(단일) 이상의 포인터는?

1차원 포인터+const에서 본 것과 원리는 똑같다. 2차원 포인터(더블포인터), 3차원 포인터, ..., n차원 포인터 모두 원리는 같으므로

2차원 포인터만 한번 살펴보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
include <stdio.h>
 
int main()
{
    int a = 10;
    int *pA = &a;
 
    const int **ppA = &pA;
    int *const *ppA = &pA;
    int **const ppA = &pA;
  const int *const *const ppA = &pA;
    return 0;
}
 

cs

8,9,10th line처럼 쓰일 수 있다. 

하나씩 살펴보기 전에, 한번 각 line의 const가 무엇을 상수화 시키는 지에 대해 생각해보고 

맞게 생각했는지 확인해보자.


1. const int **ppA = &pA


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>
 
int main()
{
    int a = 10;
    int b = 20;    
    int *pA = &a;
    const int **ppA = &pA; // what meaning?
    int *pB = &b;
    int **ppB = &pB;
 
    ppA = ppB;
    *ppA = *ppB;
    **ppA = **ppB; //error
 
    return 0;
}
 

위 파일을 컴파일 해보면 다음과 같은 error를 만나게 된다(warning은 무시하기로 하자).

1
2
3
4
5
6
7
8
9
10
11
12
const5.c:8:14: warning: initializing 'const int **' with an expression of type 'int **' discards qualifiers in nested pointer types
      [-Wincompatible-pointer-types-discards-qualifiers]
        const int **ppA = &pA; // what meaning?
                    ^     ~~~
const5.c:12:6: warning: assigning to 'const int **' from 'int **' discards qualifiers in nested pointer types
      [-Wincompatible-pointer-types-discards-qualifiers]
        ppA = ppB;
            ^ ~~~
const5.c:14:8: error: read-only variable is not assignable
        **ppA = **ppB;
        ~~~~~ ^
2 warnings and 1 error generated.
cs

const int **ppA 즉, const 뒤에 나오는 **ppA(int type)를 상수화 한다. 고로 14번째 줄이 error를 발생시킨다.

ppA는 pa를 통해 a에 접근할 수 있다. 

이 때, ppA를 사용하여 a에 접근할 시, a변수의 값을 바꿀 수 없다. 



2. int *const *ppA = &pA


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>
 
int main()
{
    int a = 10;
    int b = 20;    
    int *pA = &a;
    int *const *ppA = &pA; // what meaning?
    int *pB = &b;
    int **ppB = &pB;
 
    ppA = ppB;
    *ppA = *ppB; //error
    **ppA = **ppB;
 
    return 0;
}
 
cs


위 파일을 컴파일 해보면 다음과 같은 error를 만나게 된다
1
2
3
4
5
const6.c:13:7: error: read-only variable is not assignable
        *ppA = *ppB;
        ~~~~ ^
1 error generated.
 
cs

int *const *ppA 즉, const 뒤에 나오는 *ppA(int* type)를 상수화 한다. 
ppA를 사용하여 pA에 접근할 시, pA가 가리키는 공간(a현재 pA는 a공간을 가리킴)을 바꿀 수 없게 된다.


3. int **const ppA = &pA 


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>
 
int main()
{
    int a = 10;
    int b = 20;    
    int *pA = &a;
    int **const ppA = &pA; // what meaning?
    int *pB = &b;
    int **ppB = &pB;
 
    ppA = ppB;
    *ppA = *ppB; //error
    **ppA = **ppB;
 
    return 0;
}
 
cs


위 파일을 컴파일 해보면 다음과 같은 error를 만나게 된다
1
2
3
4
5
6
7
8
const7.c:12:6: error: cannot assign to variable 'ppA' with const-qualified type 'int **const'
        ppA = ppB;
        ~~~ ^
const7.c:8:14: note: variable 'ppA' declared const here
        int **const ppA = &pA; // what meaning?
        ~~~~~~~~~~~~^~~~~~~~~
1 error generated.
 
cs

int **const ppA 즉, const 뒤에 나오는 ppA(int** type)를 상수화 한다. 
ppA가 현재 가지고 있는 값을 바꿀 수 없게 된다. 즉 ppA는 pA공간만을 가리키게 된다.




4. const int *const *const ppA = &pA


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
include <stdio.h>
 
int main()
{
    int a = 10;
    int b = 20;    
    int *pA = &a;
    const int *const *const ppA = &pA
    int *pB = &b;
    int **ppB = &pB;
    
    ppA = ppB;
    *ppA = *ppB;
    **ppA = **ppB;
    
    return 0;
}
 
cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const8.c:8:26: warning: initializing 'const int *const *const' with an expression of type 'int **' discards qualifiers in nested pointer types
      [-Wincompatible-pointer-types-discards-qualifiers]
        const int *const *const ppA = &pA;
                                ^     ~~~
const8.c:12:6: error: cannot assign to variable 'ppA' with const-qualified type 'const int *const *const'
        ppA = ppB;
        ~~~ ^
const8.c:8:26: note: variable 'ppA' declared const here
        const int *const *const ppA = &pA;
        ~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~
const8.c:13:7: error: read-only variable is not assignable
        *ppA = *ppB;
        ~~~~ ^
const8.c:14:8: error: read-only variable is not assignable
        **ppA = **ppB;
        ~~~~~ ^
1 warning and 3 errors generated.
 
cs

이 상황은 1,2,3을 모두 합쳐 놓은 것이다.

n차원 포인터 + const가 이렇다. 차원만 확장될 뿐 원칙은 같다.




4. 그 외 헷갈리게 하는 것

처음에 1. 변수 + const 에서 

1. const + type + 변수이름

2. type + const + 변수이름

위 두 상황에서 의미 차이가 없었다.



pointer + const에서도 const의  위치에 따라 의미 차이가 없는 경우가 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>
 
int main()
{
    int a = 10;
    int b = 20;    
    int *pA = &a;
    const int *const *const ppA = &pA;
    int const *const *const ppA = &pA;
   
    return 0;
}
 
cs

위 경우 8th line, 9th line은 서로 같은 의미이다. 


헷갈려할 필요가 없다. const 뒤에 무엇이 오는지만 집중해서 보면 된다!

반응형