본문으로 바로가기

[C] 포인터 보고서

category Layer 7/assignment 2019. 4. 3. 21:15

[1] 포인터는 뭘까?


먼저 주소값이란 뭘까?

데이터의 주소값이란 해당 데이터가 저장된 메모리의 시작 주소를 의미한다.

C언어에서는 이러한 주소값을 1바이트 크기의 메모리 공간으로 나누어 표현한다. 

예를 들어, int형 데이터는 4바이트의 크기를 가지지만, char형 데이터의 주소값은 시작 주소 1바이트만을 가리킨다.

이를 이해하였으면 다음의 예제를 보자.

다음의 예제를 보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>
int main()
{
    int a;
    printf(" 변수 a를 입력하세요 : ");
    scanf("%d",&a); // a 입력
    int *pa=&a; // pa라는 포인터변수에 a의 시작주소 저장
    // * 는 역참조 연산자로 해당 메모리주소에 있는 값을 말한다.
    // & 는 참조 연산자로 해당 메모리주소 값을 말한다.
    printf(" 10 진수 a : %d \n a의 메모리 시작주소 : 0x%x \t %p\n",a,&a,&a); // a의 값, a의 메모리주소 참조, a의 메모리주소 참조 
    printf(" 10 진수 a : %d \n a의 메모리 시작주소 : 0x%x \t %p\n",*pa,pa,pa); // *pa = pa 메모리주소에 있는 값 출력 
                                                                      // pa에 a의 메모리 시작주소가 저장되어있음
                                                                      // 따라서 pa로 메모리 주소를 출력가능함
    return 0;
}
 
cs

pa라는 포인터변수를 선언하였고, a의 메모리주소를 포인터변수에 저장하였다.

위의 예제의 결과에서 볼수있듯이 &a = pa 이다. 

이를 정리하면, 포인터는 변수의 메모리 주소를 담고있는 변수이다. 

포인터 자체도 변수이기때문에, 이론상으로는 포인터를 이중, 삼중 ..... 으로도 사용할수 있다.

일단 위의 예제를 통해 포인터와 &, 그리고 *이 무엇인지만 이해하고, 다음으로 넘어가자.






[1-2] 포인터의 두가지 선언방법

1
2
3
int *pt = &a
int* pt = &a
 
cs

몹시 간단한다. 역참조 연산자 ( * )를 어디에다가 사용하느냐에 따른 차이이다. ( 취향차이! )

실행결과는 변하지 않는다.





[2] 포인터 연산


C언어의 포인터 연산에는 다음과 같은 규칙이 있다.

 

1. 포인터끼리의 덧셈, 곱셈, 나눗셈은 아무런 의미가 없다.

2. 포인터끼리의 뺄셈은 두 포인터 사이의 상대적 거리를 나타낸다.

3. 포인터에 정수를 더하거나 뺄 수는 있지만, 실수와의 연산은 허용하지 않는다.

4. 포인터끼리 대입하거나 비교할 수 있다.


이를 이해했으면 아래의 포인터 연산 예제를 보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>
int main()
{
    char *ptr_char = 0;
    int *ptr_int = NULL// 0으로 초기화한것과 같으
    double *ptr_double = 0x00;
 
    printf("포인터 ptr_char가 현재 가리키고 있는 주소값은 %x 입니다.\n", ptr_char);
    printf("포인터 ptr_int가 현재 가리키고 있는 주소값은 %x 입니다.\n", ptr_int);
    printf("포인터 ptr_double이 현재 가리키고 있는 주소값은 %x 입니다.\n", ptr_double);
 
    printf("포인터 ptr_char가 1 증가 후에 가리키고 있는 주소값은 %x 입니다.\n"++ptr_char);
    printf("포인터 ptr_int가 1 증가 후에 가리키고 있는 주소값은 %x 입니다.\n"++ptr_int);
    printf("포인터 ptr_double이 1 증가 후에 가리키고 있는 주소값은 %x 입니다.\n"++ptr_double);
}
 
cs

char 형 포인터를 1더했을 경우에는 포인터가 가르키는 주소값의 크기는 sizeof(char) = 1 만큼 증가한다.

int 형 포인터를 1더했을 경우에는 포인터가 가르키는 주소값의 크기는 sizeof(int) = 4 만큼 증가한다.

이렇게 포인터변수의 +,- 는 포인터변수의 자료형에 따라서 주소값의 크기 변동이 결정됨을 알수있다.




[3] 포인터의 활용 - call by value & call by reference

call by value는 말그대로 값에 의한 호출, call by reference는 참조에 의한 호출을 뜻한다.

먼저 call by value의 예제를 보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>
 
void swap(int a,int b){
    int tmp;
 
    tmp = a;
    a = b;
    b = tmp;
}
int main(){
    int a,b;
    scanf("%d %d",&a,&b);
    swap(a,b);
    printf("%d %d",a,b);
}
 
cs

우리가 이 프로그램을 실행했을때 원하는것은 a와 b를 바꾸는것이다. 하지만 우리가 원하는대로 작업이 수행되지 않았다.  이유는 무엇일까?

만약 우리가 main 함수의 scanf에서 4,5를 입력했다고 해보자. 그럼 swap 함수의 인자의 4와 5가 들어갈것이다.

그리고는 코드대로 b를 a로, a를 b로 만들것이다. 하지만 이렇게 값을 바꾼다고 해도 이 값은 단지 swap함수에서의 인자로 실행되었기 때문에 main 함수에는 아무런 영향을 주지않는다. 따라서,  메인함수에 전달하지않으면 아무런 소용이 없다. 

그렇기때문에 a,b의 값을 return을 통해 전달해야 하는데, 리턴은 최대 한개까지밖에 받을수 없다.

이럴때 포인터를 사용하여 call by reference 방식으로 인자를 전달하면 우리가 원하는 프로그램을 만들수있다.

이번에는 call by reference의 예제를 보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>
 
void swap(int *a,int *b){
    int tmp;
 
    tmp = *a;
    *= *b;
    *= tmp;
}
int main(){
    int a,b;
    scanf("%d %d",&a,&b);
    swap(&a,&b);
    printf("%d %d",a,b);
}
 

먼저, int형 포인터변수 a,b를 선언한다. 그리고 main의 scanf에서도 main함수의 지역변수 a,b의값을 입력받는다. 그후 이 값들이 있는 메모리주소를 swap 함수에 넘긴다.

왜 메모리 주소를 넘길까? 라는 의문점이 들수도있는데, 포인터는 위에서도 말했듯이 변수의 메모리주소를 뜻한다. 따라서 입력값도 변수의 메모리주소여야 한다. 

이렇게 main의 지역변수 a,b의 메모리주소를 넘긴다. 그후에 역참조연산자( * )가 해당메모리주소의 값을 읽는다. 그럼 이 값은 우리가 scanf에서 입력했던 a,b의 값이다.

그후 메모리주소를 직접적으로 다루며 ( 포인터변수 이용 ) swap 함수의 코드를 실행한다.

우리는 swap함수에 main함수 a,b의 메모리 주소를 넣었다. 따라서 swap 함수에서 다뤄지는 값은 main 함수의 a,b의 메모리주소이고,  이는 곧 main함수  "a,b의  메모리 주소 " 가  직접적으로 변경된다는 것이다.

따라서, swap함수가 끝난 뒤에는 a와 b의 메모리주소가 바뀌어있을것이고, 여기있는값을 printf로 출력하게되면 a와 b의 값은 우리가 원하는대로 나올것이다.

이러한 내용을 정리하면 아래처럼 요악할 수 있다.


Call by Value => 함수에서 변경한 내용이 다른함수에 적용되지 않음 / 인자를 다루는 메모리 위치가 다름

Call by Reference => 함수에서 변경한 내용이 다른함수에 적용됨 / 인자를 메모리주소가 같음









[4] 포인터의 활용 - 포인터와 배열 응용 ( malloc을 통해 데이터 할당하기 )


우리는 평소 고정된 크기의 배열만 사용하였다. 하지만 malloc함수를 통하여 이런식으로 메모리를 할당하여 배열을 사용할수있다는것을 알아두자. 

아래의 코드를 보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>
#include <stdlib.h>    // malloc, free 함수가 선언된 헤더 파일
 
int main()
{
    int size;
    scanf("%d"&size);
 
    int *numPtr = (int *)malloc(sizeof(int* size);    // (int 크기 * 입력받은 크기)만큼 동적 메모리 할당
    for (int i = 0; i < size; i++)    // 입력받은 크기만큼 반복
    {
        numPtr[i] = i;                // 인덱스로 접근하여 값 할당
    }
 
    for (int i = 0; i < size; i++)    // 입력받은 크기만큼 반복
    {
        printf("%d\n", numPtr[i]);    // 인덱스로 접근하여 값 출력
    }
 
    free(numPtr);    // 동적으로 할당한 메모리 해제
    return 0;
}
cs

주석을 보면 이해하기 쉬울것이다.

int *numPtr = (int *)malloc(sizeof(int* size);

중요한 부분은 이부분이다. int형의 포인터를 선언하였고, 이 포인터의 이름은 numPtr이다. 

그리고 malloc함수로 { int크기 ( 4byte ) * 입력한 값 } 만큼 메모리를 할당시켜준다.

이과정에서 malloc 앞에 int*를 붙인 이유는 int형으로 형변환을 시켜주어야 자료형이 맞기때문이다.

이제부터 우리는 할당한 메모리를 배열처럼 인덱스값을 넣어서 사용할수있다.

그리고 for문으로 배열의 원소를 size만큼까지 채워넣는다.

리고 for문으로 이 원소들을 출력하는것을 볼수있다.

free(numPtr);

malloc 함수로 할당해준 데이터는 free로 할당해준다. free로 할당하지않으면 해당메모리를 통해 여러가지를 할수있다고 한다. ( 해킹에 이용 등 )





[5] 포인터의 활용 - 문자열 배열 vs 문자열 상수


우리는 문자열 배열을 선언할때 아래와 같이 선언한다. 아래 코드를 보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>
#include <stdlib.h>
 
int main()
{
    char no_pointer[]= "Hello world!";
    char *yes_pointer= "Hello world!";
 
    printf("%s\n",no_pointer);
    printf("%s\n",yes_pointer);
 
    no_pointer[6]='W';
 
    printf("%s\n",no_pointer);
    // yes_pointer[7]='W'; 작동 XXXXX
 
}
 
cs


위의 실행결과에서 볼수있듯이 배열형태로 문자열을 선언하게되면 " 문자열 변수 " 로 선언되기때문에 직접 인덱스를 통하여 값을 바꿀수 있다. 

하지만 주석처리된 부분의 코드도 같이 컴파일 시키면 오류가 나오게 된다. 

위의 코드처럼 포인터형태로 문자열을 선언하는것을 " 문자열 상수 " 라고 하는데, 이는 읽기전용이고 값을 수정할수없다는것을 알아두자.








[6] 배열과 포인터의 차이


배열과 포인터에는 어떤 차이가 있을까?

첫번째 차이는 포인터는 변수이고 배열은 상수라는것이다. 문자열 상수와 문자열 변수와는 다른개념이다.


포인터는 가르키는 대상을 유동적으로 변경할수있지만, 배열은 변경할수없다는것의 차이이다.

두번째 차이는 포인터가 가르키는 배열의 크기는 malloc으로 동적할당할수있지만, 배열의 크기는 선언할때 결정된다는것이다.


세번째 차이는 배열은 그 자체를 함수의 인수로 전달할수없지만, 포인터는 대상이 무엇이든지 함수로 전달할수있다.

그래서 배열을 함수로 전달할때는 포인터를 사용해야한다.








'Layer 7 > assignment' 카테고리의 다른 글

[C] 동적할당 보고서  (0) 2019.04.12
[C] 달팽이 배열  (0) 2019.04.09
[C] 함수 보고서  (0) 2019.04.03
[C] 배열 보고서  (0) 2019.03.30
[C] 짝수는 빼고 홀수는 더하자!  (0) 2019.03.29