포인터( pointer ) - 1 본문

C

포인터( pointer ) - 1

disso1p1 2020. 6. 24. 15:10

 

 

 

포인터( pointer ) 란 ?

메모리의 주소값 혹은 변수의 주소값을 가르키는( 저장하는 ) 변수 이다.

 

32bit 환경에서의 주소값은 4byte 이고, 64bit 환경에서의 주소값은 8byte 이다.

 

 

선언 방식

자료형 *변수명; // ex) int *Ptr;
자료형 * 변수명; // ex) int * Ptr;
자료형* 변수명; // ex) int* Ptr;

* 은 어느 쪽에 써도 상관 없다.

 

 

포인터 변수 초기화

int a=1;
int *Ptr=&a;

 

int 형 변수 a 를 선언하고, int * 형 변수 Ptr 를 선언해 a 의 주소값을 넣을 수 있다.

 

& 는 주소값을 가져오는 '주소 연산자' 이다.

a 의 주소값이 0x12345678 이라면, &a 의 값은 0x12345678 이 된다.

 

// pointer.c
#include<stdio.h>

int main()
{
    int a=1;
    int *Ptr=&a;
    
    printf("a 의 주소값 : %p, a 의 값 : %d\n", &a, a);
    printf("Ptr 의 주소값 : %p, Ptr 의 값 : %d\n", Ptr, *Ptr);
    
    return 0;
}

/*
[출력]
a 의 주소값 : 0x7ffeefbff4f8, a 의 값 : 1
Ptr 의 주소값 : 0x7ffeefbff4f8, Ptr 의 값 : 1
*/

 

int *형 Ptr 변수에 a의 주소값을 넣었으므로 Ptr의 값과 a의 주소값, a 값과 Ptr 값에 있는 주소의 값이 같다.

 

정리하자면,

Ptr == &a

*Ptr == a

이다.

 

여기서 * 은 '역참조 연산자' 로 말 그대로 역참조를 해서 값을 가져온다.

위 예제에서 a 의 주소값이 0x7ffeefbff4f8 이므로, Ptr 에도 0x7ffeefbff4f8 가 들어가고,

*Ptr 는 0x7ffeefbff4f8 주소에 있는 값, 즉 1 이 출력되는 것이다.

( 변수를 선언 할 때 있는 *은 포인터 형임을 알려주는 용도로 역참조 연산자와는 다르다. )

 

여기서 궁금한 게 생겼다.

포인터 변수는 주소를 저장하는데 모두 int *형으로만 하면 저장하면 되는 거 아닐까 ?

float 형 변수의 주소값을 int *형 변수에 넣어보고, 반대로도 해보고 등등 많이 해봤는데 제대로 된 값이 나오지 않는다.

 

찾아보니 엄청난 사실을 알았다.

자료형에 따라 메모리에 접근하는 방법이 달라지는 것 때문이다.

주소 값만을 저장하는 것은 상관없을지라도 *Ptr 이런 식으로 역참조를 할 때 문제가 생긴다.

 

예를 들어, long long 형 변수는 8 byte 이므로 역참조를 하면 8 byte 만큼 가져와야 하고,

char 형 변수도 마찬가지로 1 byte 이므로 역참조를 할 때 1 byte 만큼만 가져와야 한다.

 

여기서 int 형 변수를 사용하면 4 byte 만큼 가져오기 때문에 문제가 생기는 것이다.

 

 

포인터 연산

주소 값에 1 씩 증가하면 어떻게 될까?

나는 모든 자료형의 주소값이 1 byte 씩 증가할 것이라고 예상했다.

 

 

예제를 보자.

#include<stdio.h>

int main()
{
    int a;
    int *aa = &a;
    char b;
    char *bb = &b;
    
    printf("a의 주소값 :\n");
    printf("%p\n", aa);
    printf("%p\n", aa+1);
    printf("%p\n", aa+2);
    
    printf("b의 주소값 :\n");
    printf("%p\n", bb);
    printf("%p\n", bb+1);
    printf("%p\n", bb+2);
    
    return 0;
}
/*
[출력]
a의 주소값 :
0x7ffeefbff4f8
0x7ffeefbff4fc
0x7ffeefbff500
b의 주소값 :
0x7ffeefbff4ef
0x7ffeefbff4f0
0x7ffeefbff4f1
*/

 

위 예제는 int 형 변수와 char 형 변수의 연산 값을 출력하는 코드이다.

 

출력 내용을 보면,

int 형인 a 의 주소값에 1씩 더할 때마다 주소 값은 4 byte 씩 증가하고,

char 형인 b 의 주소값에 1씩 더할 때마다 주소 값은 1 byte 씩 증가한다.

 

즉, 자료형의 byte 수 만큼 증가하는 것을 알 수 있다.

 

정리하면,

주소값의 연산은 (주소 값을 가진 변수 값) + n*(그 변수의 자료형 크기) 이 된다.

- 연산도 마찬가지이다.

 

#include<stdio.h>

int main()
{
    int a;
    int *aa = &a;
    char b;
    char *bb = &b;
    
    printf("a의 주소값 :\n");
    printf("%p\n", aa);
    printf("%p\n", ++aa);
    printf("%p\n", ++aa);
    
    printf("b의 주소값 :\n");
    printf("%p\n", bb);
    printf("%p\n", ++bb);
    printf("%p\n", ++bb);
    
    return 0;
}
/*
[출력]
a의 주소값 :
0x7ffeefbff4f8
0x7ffeefbff4fc
0x7ffeefbff500
b의 주소값 :
0x7ffeefbff4ef
0x7ffeefbff4f0
0x7ffeefbff4f1
*/

 

증감연산자로도 할 수 있다.

 

 

call by value & call by reference

call by value 는 '값에 의한 호출', call by reference 는 '참조에 의한 호출'이다.

 

 

예제로 보자.

#include<stdio.h>

void func(int a, int b, int sum)
{
    sum = a+b;
}

int main()
{
    int a=1, b=2, sum=0;
    
    func(a, b, sum);
    
    printf("sum = %d", sum);
    
    return 0;
}
/*
[출력]
sum = 0
*/
#include<stdio.h>

void func(int a, int b, int *sum)
{
    *sum = a+b;
}

int main()
{
    int a=1, b=2, sum=0;
    
    func(a, b, &sum);
    
    printf("sum = %d", sum);
    
    return 0;
}
/*
[출력]
sum = 3
*/

 

코드의 차이는

첫번째 예제에서는 call by value 예제로, main 함수에서 func 함수에 변수 a, b, sum 의 값을 보낸다.

두번째 예제에서는 call by reference 예제로, main 함수에서 func 함수에 변수 a, b의 값과 sum 의 주소 값을 보낸다.

 

그런데 우리가 출력하고 싶은 값은 3 인데, 첫번째 예제의 출력 값은 0 이고, 두번째 예제의 출력 값은 3 이다.

왜 그런 것일까?

 

func 함수에 값을 보냈을 때 받는 변수는 main 함수와 다른 함수이고, 값만 복사해온다.

 

그렇기 때문에 main 함수의 변수 sum 과 func 함수의 변수 sum 은 다른 변수이므로,

첫번째 예제에서는 func 함수에서 연산한 sum 은 결과적으로는 무의미해지는 것이다.

하지만 두번째 예제는 주소값을 복사했기 때문에, 그 주소에 있는 값을 바꾸면 main 함수에 있는 변수 sum 의 값도 바꿀 수 있게된다. 

 

 

char 형 변수 & char *형 변수

#include<stdio.h>

int main()
{
    char arr1[]="hihi";
    char *arr2="hihi";
    
    printf("arr1[0], arr1 주소 값 : %p, %p\n", &arr1[0], arr1);
    printf("arr2 주소 값 : %p", arr2);
    
    return 0;
}
/*
[출력]
arr1[0], arr1 주소 값 : 0x7ffeefbff4f7, 0x7ffeefbff4f7
arr2 주소 값 : 0x100000f76
*/

 

char 형 변수 arr1 의 주소값은 arr[0] 의 주소값이고,

char *형 변수 arr2 는 메모리 공간에 'hihi' 라는 문자열을 저장하고, 첫번째 문자인 'h' 의 주소값을 반환해 arr2 에 저장된다.

 

주소만 봐도 두 변수가 다른 영역에 저장되는 것을 알 수 있다.

 

 

그럼 두 변수의 차이는 무엇일까?

#include<stdio.h>

int main()
{
    char arr1[]="hihi";
    char *arr2="hihi";
    
    printf("arr1[0], arr1 주소 값 : %p, %p\n", &arr1[0], arr1);
    printf("arr2 주소 값 : %p\n", arr2);
    
    printf("%s\n", arr1);
    printf("%s\n", arr2);
    
    arr1[0]='H';
    arr2[0]='H';
    
    printf("%s\n", arr1);
    printf("%s", arr2);
    
    return 0;
}
/*
컴파일 오류
*/

 

각각 0번째 인덱스의 값을 넣었을 때, arr1 은 정상적으로 H 가 들어갔지만, arr2 는 들어가지 않았다.

즉, char *형 변수는 문자열을 바꿀 수 없다.

그래서 상수형태의 문자열이라고도 한다.

'C' 카테고리의 다른 글

포인터( pointer ) - 2  (0) 2020.07.03
Comments