네.. 많은 분들이 두려워하시는 그 포인터 부분입니다.

딱 한번만 넘어가면 어느 정도 익숙해 지는 파트인데, 첫 접근이 참 힘듭니다.

전공생 비전공생 너 나 할 것 없이 모두 여기서 한 번쯤 좌절하고 넘어가는 부분이기도 하죠.

하지만 C/C++의 매력은 메모리를 직접 건들 수 있다는 것이기 때문에

C를 공부하겠다고 마음먹었다면 반드시 넘어야 하는 장벽이기도 합니다.

 

완벽하게 이해하지 못해도 괜찮습니다. 저 역시도 완벽하지 않은 걸요..

다만 포기는 하지 맙시다. 여기서 포기하면 다른 일 못해요. 이만큼 어려운 게 다른 과목, 다른 일에도 산더미인걸요..

꾹 참고 딱 한번만 넘깁시다!! 파이팅!!!

 

목차

     


    0. 포인터의 기본

    포인터주소를 저장하는 변수입니다.

    먼저 포인터 변수의 특징부터 알아봐야겠죠?

     

    다시 말해 포인터 변수는 다른 변수를 가리키는 변수입니다. 주소를 이용해서 특정 변수에 접근할 수 있도록 도와주는 것이죠.

     

    포인터 변수의 크기(주소의 크기)는 상황에 따라 다릅니다. 64bit로 컴파일하면 포인터 변수의 크기는 8바이트이고, 32bit로 컴파일하면 포인터 변수의 크기는 4바이트입니다.

     

    하지만 포인터 변수의 크기는 포인터 변수가 가리키는 변수의 데이터형에 관계없이 항상 같습니다.

    32bit으로 컴파일했다면 char이든 int이든 double이든 포인터 변수의 크기는 모두 4바이트고,

    64bit으로 컴파일했다면 char이든 int이든 double이든 포인터 변수의 크기는 모두 8바이트입니다.

    이해에 있어서 크기는 크게 중요한 부분이 아닙니다. 그냥 상황마다 다르구나~ 하고 넘어가시면 됩니다!!

    32bit 컴파일 기준!!

     

    포인터를 선언할 때는 포인터가 가리키는 변수의 데이터형 뒤에 *를 붙입니다. 그리고 그 뒤에 포인터 변수명을 적어줍니다.

    char*는 char형 변수의 주소, int*는 int형 변수의 주소, double*은 double형 변수의 주소를 저장합니다.

    char*형 변수는 char형 변수를 가리키고, int*형 변수는 int형 변수를 가리키고, double*형 변수는 double형 변수를 가리키는 것이죠.

     

    포인터를 사용하려면, 변수의 주소값을 알아야 합니다.

    그렇다면 그 주소값은 어떻게 알 수 있을까요?

    바로 &이라는 연산자를 사용하면 됩니다.

    주소를 구하고 싶은 변수 앞에 &를 붙이면 그 값은 주소값이 됩니다.

    즉, x가 우리가 원하는 변수이고, 이 x의 주소값은 &x 로 구할 수 있습니다.

    &x 라는 주소를 int형 포인터변수인 p에다가 넣어줍니다.

    현재 p가 가리키고 있는 것은 x의 주소값(&x)입니다.

    이제 p가 가리키고 있는 주소(&x)에 있는 데이터는 *p로 접근할 수 있습니다.

    위 설명은 아래 그림으로 보다 깔끔하게 정리할 수 있습니다.

     

    &연산자는 &다음에 나오는 변수의 주소를 구하는데 사용합니다.

     

    &연산자는 반드시 변수명 앞에만 사용할 수 있으며, 상수나 수식에는 사용할 수 없습니다.

     

    간접 참조 연산자(*)는 포인터 변수가 가리키는 변수에 접근해서 값을 읽어오거나 변경하는 데 사용합니다.

    포인터 변수는 p이고, p가 가리키는 주소에 있는 값은 *p로 접근할 수 있는 것이죠.

     

    * 연산자는 반드시 포인터 변수 앞에서만 사용할 수 있습니다.

     

    아래는 포인터 변수의 선언 및 사용의 예입니다.

    *p, p, &x가 무엇을 가리키고, &x, x가 무엇을 가리키는지 눈으로 확인할 수 있습니다.

    추가로 32bit와 64bit로 컴파일 할 때의 차이점도 눈으로 확인하실 수 있도록 올렸습니다!

    /* Ex08_02.c */
    #include <stdio.h>
    
    int main(void)
    {
    	int x;
    	int* p;
    
    	p = &x;
    	*p = 10;
    
    	printf("*p = %d\n", *p);
    	printf("x = %d\n", x);
    
    	printf("p = %p\n", p);
    	printf("&x = %p\n", &x);
    
    	printf("&p = %p\n", &p);
    
    	return 0;
    }

     

     

    이번엔 이중포인터입니다. 이중포인터는 포인터 변수의 주소를 저장하는 포인터 변수입니다.

     

    이중포인터가 가리키는 포인터를 이용해서 변수에 접근하려면 *를 두 번 사용하여 간접참조를 해야합니다.

    위 그림을 이해하셨다면 쉽게 이해하실 수 있을 겁니다!!

     

    포인터 변수의 데이터형은 반드시 포인터 변수가 가리키는 변수의 데이터형과 일치해야합니다.

    short형 변수의 주소를 int형 포인터 변수에 담을 수 없는 것이죠.

     

    포인터 변수도 초기화를 하지 않으면 쓰레기값이 들어갑니다.

    이렇게 초기화가 되지 않은 상태로 사용하면 실행 에러가 발생합니다.

    아래 예시에서는 10을 쓰레기값 주소에 넣으려고 해서 에러가 발생한 모습입니다.

     

    만약 포인터가 어떤 변수도 가리키지 않는 상황을 원한다면 NULL로 초기화합니다.

     

    보다 안전하게 포인터를 사용하기 위해서는 포인터가 NULL 포인터인지를 확인하고 사용합니다.

     

     

    포인터도 연산이 가능합니다.

    만약 포인터 변수에 1을 더하면 현재 포인터 변수가 가리키는 주소에서 포인터 변수가 가리키는 데이터형 1개 크기만큼 증가된 주소가 연산의 결과가 됩니다.

    1을 빼면 현재 포인터 변수가 가리키는 주소에서 포인터 변수가 가리키는 데이터형 1개 크기만큼 감소된 주소가 연산의 결과가 됩니다.

    1이 아닌 다른 정수 n이 들어가도 마찬가지입니다.

     

    이번엔 포인터와 숫자의 연산이 아닌 포인터끼리 뺄셈을 하게 되는 경우가 있을 수 있습니다. (덧셈은 아무 의미가 없습니다)

    이 경우에는 단순 주소계산으로 배수가 나올 것이라고 생각할 수 있지만, 포인터 뺄셈연산은 데이터의 개수를 반환합니다. 주소의 간격을 포인터 변수의 크기로 나눈 값을 반환하는 것이죠.

    만약 원래대로 주소의 차를 구하고 싶다면 연산 값에다가 sizeof(자료형) 값을 곱해주면 됩니다.

     

    포인터에 상수를 더할 수 있으므로, 증감연산자 역시 사용이 가능합니다.

     


    1. 포인터의 배열

    배열을 담은 변수를 포인터로 가리키는 경우, 배열 포인터를 다루는 방법에 대해서 알아보겠습니다.

     

    인덱스 없이 배열명만 사용하면 배열의 시작 주소를 의미합니다.

    이 말은 배열명을 포인터처럼 사용할 수 있다는 말과 같습니다.

    즉, 인덱스를 사용하는 대신 배열의 시작 주소로 포인터 연산을 하면 배열의 특정 원소에 접근할 수 있습니다.

     

    그리고 다음 원소의 주소는 + 1과 같은 방식으로 접근할 수 있습니다.

    주소에 접근할 수 있다면 그 주소 앞에 간접 참조 연산자 *를 붙여줌으로써 주소에 있는 값도 구할 수 있습니다.

    배열에 +1을 더하는 명령을 내리면 값을 더할 수는 없기 때문에 컴퓨터는 포인터라고 생각하고 주소를 연산하게 됩니다.

     

    아래는 포인터로서의 배열의 사용 예입니다.

    배열 원소의 주소계산과 배열 원소의 값을 구하는 과정을 살펴보시면 되겠습니다!

    /* Ex08_05.c */
    #include <stdio.h> 
    int main(void)
    {
    	int arr[5] = { 10, 20, 30, 40, 50 };
    	int i;
    
    	for (i = 0; i < 5; i++) // 배열 원소의 주소
    	{
    		printf("&arr[%d] = %p, ", i, &arr[i]);
    		printf("arr+%d = %p\n", i, arr + i);
    	}
    
    	for (i = 0; i < 5; i++) // 배열 원소의 값
    	{
    		printf("arr[%d] = %d, ", i, arr[i]);
    		printf("*(arr+%d) = %d\n", i, *(arr + i));
    	}
    
    	return 0;
    }

    32bit 컴파일 기준, 서로 같은 모습을 볼 수 있다.

     

    조금 꼬아서 배열의 시작 주소로 초기화된 포인터를 이용해서 배열의 모든 원소에 접근할 수 있습니다.

    포인터 변수를 배열 이름처럼 사용할 수 있는 것이죠.

    이런 방식도 있다~ 정도로 생각하시면 됩니다.

     

    배열의 원소를 가리키는 포인터는 배열의 어떤 원소도 가리킬 수 있습니다.

     

    포인터가 배열의 원소가 아닌 일반 변수를 가리킬 때에도 *(p+i) == p[i]는 항상 성립합니다.

    이 경우 *p 대신 p[0]으로 사용되는 것이죠. (일반 변수이기 때문에 p[1]을 가져오려고 하면 쓰레기값을 가져옵니다.

    index 1을 가져오면??

     

    배열과 포인터의 차이점은 배열은 메모리에 할당되고 나면 배열의 시작 주소를 변경할 수 없습니다.

    반면 포인터 변수는 값을 변경할 수 있으므로 포인터 변수에 보관된 주소는 변경할 수 있습니다.

     


    2. 포인터와 문자열

    문자열 리터럴문자열 리터럴의 주소를 의미합니다.

    문자열 리터럴은 다른 리터럴과는 달리 메모리에 보관해두고 사용합니다.

    특히 그 메모리는 값을 변경할 수 없는 영역입니다.

     

    값을 읽을 수만 있고 변경할 수는 없는 영역에 값이 있기 때문에 문자열의 내용을 바꾸려고 하면 실행 에러가 발생합니다.

    strcpy_s로 바꾸려 했지만 아래 두 케이스 모두 오류..

     

    이 경우 char*형 변수 p는 문자열 리터럴의 내용을 변경할 수는 없지만, char*형 포인터 변수에 다른 문자열 리터럴의 주소를 대입할 수는 있습니다.

    값을 변경하는 대신 담고 있는 주소를 통째로 바꿔버리는 것이죠.

    0x7000 대신 0x8000을 char*에 대입하면 깔끔하게 바꿀 수 있습니다.

     

    char*형 변수에는 문자열 리터럴의 주소를 저장할 수도 있고, 문자 배열의 주소를 저장할 수도 있습니다.

    두 케이스를 잘 구분해보면 리터럴을 가리키는 경우는 문자열에 직접 접근하여 바꾸는 것이니 불가능하고, 문자 배열을 가리키는 경우 문자열을 바꾸는 것이 아니라 p가 가리키는 주소만 바꾸는 것이므로 문제가 없습니다.

     

    하지만 문자 배열을 가리키는 경우라고 해도 const가 붙으면 포인터가 가리키는 문자열을 바꿀 수 없기 때문에 오류가 발생합니다.

     

    이 const는 데이터형 앞에 들어가거나 포인터 변수명 앞에 들어갈 수 있습니다.

     

    데이터형 앞에 위치한다면 포인터가 가리키는 변수의 값을 읽어볼 수만 있고 변경할 수는 없습니다.

    포인터 변수 자신의 값(주소)은 변경할 수 있습니다.

    즉, 원래 데이터에 const를 거는 것이죠.

     

    이번엔 const가 포인터 변수명 앞에 있는 경우 포인터 변수 자신의 값(주소)를 변경할 수 없습니다.

    대신 포인터가 가리키는 변수의 값은 변경할 수 있습니다.

    데이터형 앞이면 가리키는 주소의 내부값을 못바꾸고, 포인터 변수명 앞이면 가리키는 주소를 못바꾼다고 생각하시면 되겠습니다.

     

    만약 const가 2개면? 부값도 가리키는 주소도 바꿀 수 없다는 이야기가 되겠죠?

     


    길다면 길고, 짧다면 짧았던 포인터의 설명이 끝났습니다.

    쓰면서도 처음 보는 사람이 이걸 이해할 수 있을까 하는 생각이 들어서

    설명을 계속 넣다보니 오히려 포스팅이 길어져서 조금 아쉬운 부분이 있습니다.

    이해가 안되시는 부분이 있다면 언제든지 댓글로 남겨주시면 답변 남겨드리겠습니다!

     

    그렇다면 다음 구조체 포스팅에서 뵙도록 하겠습니다!! 감사합니다 :)

    반응형
    • 네이버 블로그 공유하기
    • 네이버 밴드에 공유하기
    • 페이스북 공유하기
    • 카카오스토리 공유하기