이번 포스팅은 포인터 대상의 const 선언을 매우 간단히 이해하고 마치겠습니다.


포인터 대상의 const 선언


그저 간단하게 const 뒤에 있는건 상수입니다.


원래 포인터 변수 선언 시 *는 연산자로써의 기능이 아닌 포인터 변수임을 알리는 용도이지만,

 이번 내용을 이해할 땐 연산자처럼 여기는게 이해하기 간편합니다.



1.

const 자료형 *변수;     // (*변수) 가 상수이다. == 포인터 변수가 가리키는 값을 변경 시 에러 발생 but 포인터 변수의 값, 즉 주소값은 변경 가능

자료형 const *변수;     // 위와 마찬가지. *변수 가 const 뒤에 있으므로 *변수 가 상수이다.



2.

자료형 * const 변수;     // const 뒤에 변수만 있으므로, 포인터 변수 자체의 값, 즉 주소값을 변경하지 못한다. but *변수 는 변경 가능.



3.

const 자료형 * const 변수;     // 변수도 상수이고, *변수도 상수이다. 다 못 바꾼다.






마지막으로 예시를 통해 확인하겠습니다.

void main() {
	int num1 = 1;
	int num2 = 2;

	const int *p1 = &num1;
	int * const p2 = &num1;
	const int * const p3 = &num1;

	p1 = &num2;
	*p1 = num2; //에러 발생

	p2 = &num2; //에러 발생
	*p2 = num2;

	p3 = &num2; //에러 발생
	*p3 = num2; //에러 발생

}

expression must be a modifiable lvalue 에러가 발생하며 수정 가능한 값을 수정하라 말합니다.


const 선언되어 수정 불가능한 값을 수정하려할 때 에러가 발생함을 알 수 있습니다.

글 거의 다 썼다가 두 번은 다 갈아 엎었습니다...


지난번처럼 잘 설명된 글의 링크를 첨부하고 이를 바탕으로 이해하기 쉽게 재서술하겠습니다.



Call by Reference on 나무위키

ㄴ 나무위키에 작성된 call by reference 글입니다. 읽어보시길 권합니다.(어려울 수 있습니다)





첫번째 예시) call by value의 예시입니다.

void justFunc(int num) {
	num++;
}

void main() {
	int n = 0;
	justFunc(n);
	printf("%d", n);

}

결과 : 0


인자로 전달 받은 수에 1을 더하는 justFunc를 실행해도 n의 값은 바뀌지 않습니다.

이는 call by value, 즉 값에 의한 호출로, 함수를 호출할 때 값(n)을 복사해서 매개변수(num)로 피호출 함수(justFunc)에 넘겨줍니다.


따라서 값을 복사해서 넘겨주었으므로, main함수에서 변수(n)은 변하지 않으며, justFunc의 매개변수 num만이 바뀌게 됩니다.




그러나 다른 함수(보통 사용자 지정 함수)를 통해 변수(n)를 변경하고 싶은 경우가 있었고, 그래서 사용하는 방식이 call by reference(call by address)입니다.


두번째 예시) 

void justFunc(int* p) {
	(*p)++;
}



void main() {
	int n = 0;

	justFunc(&n);
	printf("%d", n);

}

결과 : 1

------------------------------------------------------------------------------------------------------------------------------------------------------------------------


justFunc를 실행하고 main함수의 변수 n의 값이 1로 바뀝니다. 

n의 주소를 복사해서 매개변수(p)로 넘겨주고 p(n의 주소)가 가르키는 것, 즉 n의 값을 +1함으로써 다른 함수에서의 변수의 값을 변경하였습니다.


  이렇게 피호출 함수(justFunc)가 호출 함수(main)의 변수에 영향을 미치는 경우를 주로 call by reference, 참조에 의한 호출 이라고 하곤 합니다.




하지만 정확하게 말하면 이는 call by reference가 아니고 call by address라고 불립니다. 그리고 이것은 사실 call by value를 사용한 것입니다.


왜냐하면 다른 함수에서의 변수를 변경한건 맞으나, 이 또한 값을 복사한 매개변수(여기선 주소값)를 함수에 넘겨준 것이기 때문이죠.





지금부터는 좀 더 깊게 들어가서 call by value, call by address, call by reference의 차이점에 대해 알아보겠습니다.


우선 용어를 명확하게 하고 들어가겠습니다.


방금 첫 번째 예시에서 justFunc(n); 을 했을 때 호출한 주체인 함수 main을 호출 함수라 합니다.

그리고 호출 당한 함수 justFunc를 피호출 함수라 합니다.


호출 함수(main)에서 피호출 함수(justFunc)에 전달한 것(n)을 실질 매개변수(actual parameter)라 합니다.

피호출 함수(justFunc)에서 num을 형식 매개변수(formal parameter)라 합니다.





Call by address vs Call by reference


위에서 제가 두 번째 예시를 call by address라 했습니다. 이는 실 매개변수(&n)로 주소값을 전달하고, 이를 복사한 값이 형식 매개변수(p)에 들어가는 방식으로 작동하였고, 이는 말 그대로 '주소에 의한 호출'이기 때문입니다. 또한 값을 복사해서 전달했으므로 call by value, 값에 의한 호출이기도 하죠.


그러면 call by reference는 대체 뭐냐?


일단 결론부터 말하면 C에는 call by reference가 존재하지 않습니다. call by reference가 뭔지 제대로 알고 싶으면 c++을 입문하고 제가 올린 나무위키 링크의 예제를 보시길 바랍니다.


간단히만 말하자면 일단 call by reference는 call by address, call by value와 달리 실질 매개변수를 제외하고는 변수(메모리)를 추가로 사용하지 않습니다.


첫 번째 예시를 보면 실질 매개변수 n을 제외하고 형식 매개변수 num을 추가로 사용하면서 메모리를 사용합니다.

두 번째 예시를 보면 실질 매개변수 n을 제외하고 형식 매개변수 p를 추가로 사용하면서 메모리를 사용합니다. 혹시 헷갈리시면 p의 주소값과 n의 주소값을 비교해보시길 바랍니다. 주소값이 다르며 따라서 다른 변수라는 것을 알 수 있습니다.


그러나 call by reference는 저 둘과 다르게 변수를 추가로 사용하여 메모리를 사용하지 않고, c++의 기능 중 하나인 '참조자 &'라는 것을 이용하여 오직 실질 매개변수만으로 호출하고 합니다.




Call by value vs Call by reference


call by value와 call by reference의 차이점은 다음과 같이 세 가지입니다.


1. 복사한 값을 전달한다 vs 복사가 아닌 실질 매개변수 그 자체를 전달한다.

2. 호출 함수에 영향을 미치지 못한다 vs 호출 함수에 영향을 미칠 수 있다.

3. 인자(실질 매개변수)를 다루는 메모리의 위치가 다르다(형식 매개변수의 메모리 위치) vs 인자를 다루는 메모리의 위치가 같다(실질 매개변수의 메모리 위치)


이를 바탕으로 이제 call by value와 call by reference와 call by address를 구분하면 다음과 같습니다.




Call by address는 복사한 값(주소값)을 전달한다는 점과 인자를 다루는 메모리의 위치가 다르다는 점에서 Call by value, 그래서 결론적으로 Call by value가 맞습니다.


하지만 호출 함수에 영향을 미칠 수 있다는 점에서 Call by reference의 특성을 띄고, 그래서 많이들 Call by reference로 여기고 사용하고는 합니다.


그러므로 이번 기회에 정확하게 알고 가되, 남들과 소통할 땐 이런 점을 감안해서 이해하시기를 바랍니다.





추가로 잘 정리된 블로그 링크 올립니다. 도움이 되시길 바랍니다.


잘 정리된 블로그 바로가기

쓰려했는데 잘 쓴 글이 여럿 있네요. 그냥 요거 리터럴부터 읽으시길.


https://modoocode.com/33





제가 이 글을 읽다가 헷갈리던 부분을 서술하겠습니다.


char str[] = "Hello";


위 문장은 str에 "Hello" literal의 주소를 넣은 것으로 생각되고, 그러면 수정이 불가능한데 어째서 수정이 가능할까요?





char str[] = "Hello"; 는 널문자를 포함해서 6개의 일차원 배열이 할당됩니다.

그 안에 문자가 H, e, l, l, o, \0이 이렇게 6개가 들어가는 거죠.


즉, char str[] = "Hello";는


char arr[] = {72, 101, 108, 108, 111, 0};

char* str = arr; //arr은 배열의 이름, 주소


이와 같은 원리로 이해할 수 있습니다.

배열은 변수의 영역이므로 수정 가능하고요 :)

큰 따옴표와 작은 따옴표의 차이점?

⇒ 큰 따옴표(쌍 따옴표)는 "여러", 작은 따옴표(홑 따옴표)는 '홑'이다.

 

 

예시를 들자면

ex) 에러가 발생하는 경우

 

1)

char str[] = "HELLO";

str[0] = "B";

 

2) printf("%c","A"); printf("%s", 'A');

 

ex) 올바른 사용

 

1)

char str[] = "HELLO";

str[0] = 'B';

 

2) printf("%c", 'A'); printf("%s", "A");

 

 

자세히 설명하면 다음과 같다

 

 In C and in C++ single quotes identify a single character, while double quotes create a string literal. 'a' is a single a character literal, while "a" is a string literal containing an 'a' and a null terminator (that is a 2 char array). //stack overflow 참고

 

 

즉, 우선 한 개는 홑따옴표, 여러 개는 쌍따옴표가 맞다. 그러나 한 개를 표현할때도 쌍따옴표를 쓰면 뒤에 널문자가 붙는다는 것!

따라서 널문자가 나타날때까지 읽는 %s에서는 한 문자를 쓰더라도 쌍따옴표(double quotations)를 사용해야하는 것이다.

※읽다보면 헷갈리는 부분이 있을 수 있습니다. 그럴 때 거기에 막혀서 검색해보기 전에, 일단 모두 읽어보시길 추천합니다!
서술된 내용 뒤에 보충 설명을 달아놓은 경우가 많습니다^^

 

 

 

 

포인터란?


 


C에서 포인터 변수는

자료형* 변수명;                 <- 이렇게 선언되고는 합니다.

 

※포인터 변수 선언 방식

1. 자료형* 변수명;

2. 자료형 * 변수명;

3. 자료형 *변수명;

모두 가능합니다.

그러나 저는 1번 방식을 추천드립니다

 

 


저는 포인터=주소명, *포인터=주소에 도착한 포인터 라고 이해했습니다.



다음의 간단한 예제를 보며 이해를 해보겠습니다.(이해에 앞서, *연산자는 주소에 접근, &연산자는 주소를 반환합니다.)

int main(void) {

    int num = 5;
    int* ptr; //int형 포인터 변수 ptr 선언

    ptr = &num; //포인터 변수 ptr에 num의 주소를 저장(&num은 num의 주소를 반환합니다)



    printf("주소명(포인터) = 0x%x \n", ptr); //주소명 표시
    printf("주소에 도착한 포인터(*포인터) = %d \n", *ptr); //주소에 도착한 포인터 변수 ptr이 마치 num인듯이 행동

    return 0;
}


실행결과



주소를 16진수로 표현하는 컴퓨터 업계에서 주로 컴퓨터의 언어인 2진수로 쉽게 표현할 수 있고, 2진수를 줄여서 쓸 수 있기 때문이라 합니다. 




좀 더 자세히 설명하자면(사실상 저는 위의 예제 하나면 포인터의 개념을 모두 설명했다 생각합니다.)


포인터 변수란 메모리의 주소 값을 저장하기 위한 변수입니다.

&연산자를 통해 num의 주소 값을 반환하여,

int형 포인터 변수 ptr에는 변수 num의 '시작번지 주소'가 저장됩니다.

그리고 *연산자를 이용해 포인터가 가르키는 메모리 공간에 접근합니다.(*ptr을 통해 num이 가르키는 메모리에 접근하여 num처럼 행동하였습니다)


이러면 포인터 개념 설명 끝!


+추가)


포인터 변수에도 다양한 타입이 존재합니다. int, double, char, unsigned int 등등..

포인터 변수에 이러한 타입이 왜 존재하느냐!하면 다음 코드를 보고 생각해봅시다.


double num = 3.14;
int* pnum=&num;


double형의 크기는 8byte, int형의 크기는 4byte입니다.

포인터 변수 pnum는 int형이고 크기는4byte인데 여기에는 double형 num의 주소가 담겨있습니다. 이는

 "num의 시작 주소부터 4byte를 읽겠다"를 의미합니다.

허나 num의 크기는 8byte이므로 포인터 변수 pnum은 *pnum으로 num에 접근하였을 때 읽다말아서(8byte 중에서 4byte만 읽어서) 원하는 값을 표시하지 않습니다.


따 라 서~~ 포인터 변수의 타입의 존재 이유는 * 연산자로 메모리를 올바르게 읽기 위해서라 할 수 있을것 같습니다.


++추추가)


잘못된 포인터의 사용과 널 포인터


다음의 포인터 선언의 잘못된 예를 보시고 무엇이 잘못되었는지 맞춰보시기 바랍니다.


int* ptr;
*ptr = 0;




C언어에서 변수를 초기화하지 않고 선언 시, 쓰레기값이 저장됩니다.

int *ptr; 을 실행하였을 때, 포인터 변수 ptr에는 임의의 주소값(쓰레기 값)이 저장될 것 입니다.

*ptr = 0;을 실행한다면? 그 임의의 주소값에 0을 저장하겠죠.


근데 만약 *ptr이 가르키는 곳이 메모리상의 중요한 부분이였다면 어떻게 될까요?? 컴퓨터를 다신 사용할 수 없을지도 모릅니다..

다행히 요즘 운영체제는 이런 사고를 방지시켜준다 하네요.

그렇다면 올바르게 사용하려면 어떻게 해야할까요??



int* ptr = &num; //<- 처럼 선언과 동시에 초기화 시켜주던가,

int* ptr = 0;
int* ptr = NULL;  //<- 처럼 주소값에 0또는 NULL을 저장합니다.

이는 0번지를 의미하는 것이 아닌, 아무것도 가리키지 않는다를 의미한답니다.

 

 

포인터 설명을 위한 포스팅은 계속 됩니다. 다음 포스팅을 참고하시길 바랍니다

 

 

 

 

이 포스터는 윤성우 열혈 C 프로그래밍을 참고하여 작성하였습니다.

 

궁금한 점은 댓글로 물어보시길 바랍니다.

부족한 점이 많으니 태클 환영합니다!




+ Recent posts