본문 바로가기
Computer Science

[C언어] 포인터 기본 개념 알아보기

by DuncanKim 2022. 6. 27.
728x90

[C언어] 포인터 기본 개념 알아보기

 

 

c는 매뉴얼이 많다. 자바가 일반 자동변속기 승합차라면, c는 90년대 대형버스라고 할 수 있다.

많은 것들을 내가 스스로 조작할 수 있다. 변속이라던지, 가속이라던지, 문을 열고 닫는다던지, 저단 기어를 넣어서 느리지만 힘을 가지고 갈 지 등등, 많은 것들을 개발자가 조종할 수 있다. 그래서 Low 레벨 언어라고도 한다.

 

포인터는 메모리 주소와 연관되어 있는 문법이다.

메모리는 이전 글에서 살펴봤듯, 힙과 스택 영역에 위치한다.

이전글 : 

2022.06.27 - [프로그래밍 언어/C\C++] - [C언어] C언어의 메모리 관리

 

 

그 스택 영역은 고유한 주소값을 가지고 있고, 포인터를 쓰면 변수가 저장된 메모리 주소를 불러올 수 있다. 메모리 주소를 불러온 것을 가지고, 그 변수의 값을 바꿔줄 수도 있다. C언어에서 배열을 출력할 때, 배열은 포인터 변수라서 다른 방식으로 출력을 걸어주어야 한다. 

 

이렇듯 포인터는 C언어 계열 언어를 함에 있어서 그 도구의 작동원리에 대해 더 깊게 이해하는 것에 도움을 준다. 아래에서는 포인터로 변수의 값을 바꿔보는 기본적인 것을 해볼 것이다.

 

 

1. 포인터 기본 사용 방법

 

포인터는 아래와 같이 선언한다.

int main(){
int* p;
    *p = 100;
}

 

변수의 타입명 바로 뒤에 '*' 를 붙이고 변수명을 쓰고, 거기에 바로 = 100을 해도 되고,

*p = 100을 따로 적어서 포인터 변수 p에 100을 담을 수도 있다.

 

<실습. i = 50;을 하지 않고 변수 안에 담긴 값 변경하기>

 

#include <stdio.h>

int main(void) {
        int i = 30;
        printf("i의 주소 : %ld\n", (long)&i);
        printf("i의 사이즈 : %ld\n", sizeof(i));
        printf("&i의 사이즈 %ld\n", sizeof(&i));
        int* p = &i;
        printf("p의 값 : %ld\n", (long)p);
        printf("p의 사이즈 : %ld\n", sizeof(p));
        printf("p의 주소 : %ld\n", (long)&p);      
        
        *p = 50;
        printf("*p의 주소 : %ld\n", (long)&*p);
        printf("*p의 크기 : %ld", sizeof(*p));

}       
//결과
/*
i의 주소 : 6130872988
i의 사이즈 : 4
&i의 사이즈 8
p의 값 : 6130872988
p의 사이즈 : 8
p의 주소 : 6130872976
*p의 주소 : 6130872988
*p의 크기 : 4
*/

 

포인터를 사용해서 각각의 값을 출력해보았다. 위에서 보듯, 포인터 변수에는 int i라는 61억 xxx라는 int i의 주소값이 담겨있고, 다시 포인터 변수 p의 주소값을 추적해보면 int i의 주소값과는 다른 것을 볼 수 있다.

각각 메모리에서 다른 곳에 저장되어 있으며, 포인터는 그것을 가리키는 것밖에 하지 않는 것이다.

 

다시 말해, 포인터 변수는 "쟤는 저기 있어요~!"라는 것을 나에게 알려주기 위해 자기 자신 안에 특정한 변수의 주소값을 저장하고 있는 것이다.

 

맨 아래에 있는 *p의 경우, 포인터 변수 p의 값을 50으로 한다는 이야기인데, p는 i의 주소값을 가지고 있으니, p가 가지고 있는 주소안에 있는 값을 50으로 바꾸어라. 라고 명령을 한 것과 같다.

 

주소값은 다음과 같은 것을 알려준다.

i의 주소는 6130872988이다. 또한 정수형의 크기는 4바이트이기 때문에, i의 사이즈는 4가 나오게 된다. 

int i의 값은 ~~~988, 989, 990, 991에 담겨 있는 것이다.

 

 

그런데, 포인터 변수 p의 주소는 왜 ~~~976일까? 왜 앞에 있는 변수 i와 메모리 크기 차이가 12일까?

조심해야 하는데, 스택이라고 해서, 차곡차곡 쌓이는게 아니고, IDE 마다 현출해주는 것이 달라서 그렇다.

 

Local 변수를 컴파일러가 할당할 때 빈공간이 생기는 이유가 "정의되지 않은 동작"은 아닙니다. 정확히 말하자면 C/C++ 표준은 로컬 변수를 컴파일러가 어떻게 배치해야 할지에 대해 아무런 규칙을 정해놓고 있지 않습니다. 어떻게 하든지 컴파일러 마음대로입니다.

출처 : https://kldp.org/node/157621

"컴파일러 마음대로라서!"

 

실제로 간단한 실험을 해보았더니 다음과 같았다.

 

i, j, k의 주소는 각각 4씩 차이가 나는 것을 알 수 있지만, 포인터 변수의 경우, 메모리 주소 크기 차이가 12가 날 때도 있고, 8이 날 때도 있다. 포인터 변수의 크기는 8이다.

 

메모리 크기 차이가 12만큼 난다고 해서, p1의 크기가 12는 아닌 것이다. 그 빈 공간은 컴파일러가 부여하는 것이며, 어떻게 부여하는 지는 모른다. 무튼 그 사이에 공간이 4만큼 빈 공간이 생기기 때문에 이러한 차이가 발생하는 것이다.

 

 

여기서 또 헷갈리는게 있는데, *p의 주소값은 왜 또 6130872988일까. 그것은 바로 가리키고 있는 곳의 주소를 반환했기 때문이다. 즉 i의 주소값을 반환한 것이다. *p의 크기도 4인 것을 볼 수 있는데, 이 경우 포인터 변수의 크기(8바이트)를 반환한 것이 아니라, p가 가리키고 있는 곳의 사이즈 즉, i의 크기를 반환했기 때문에 4가 나온 것이다.

 

아무튼, 설명이 길어지는데,

*는 가리키는 곳,
&는 주소

를 반환한다는 것을 알고 여러 군데에 실험적으로 바꿔가면서 활용을 해보면 이해를 할 수 있다.

 

 

2. 다른 타입의 포인터 변수

 

int i = 20;
int* pi = &i;    // (O)

char a = 'a';
char* pa = &a;   // (O)

char b = 'b';
int* pb = &b;    // (X)

 

포인터 변수의 크기들은 모두 8바이트이다. 그렇다면, char 형으로 선언된 타입의 변수를 int* p = b로 해서 포인터를 선언할 수 있을까?

불가능하다. 왜냐하면, char 타입의 포인터 변수는 그 변수가 저장된 곳으로 이동하여 1바이트를 읽어오는 반면, int 타입의 경우 4바이트를 읽어오기 때문에, 구조상 불가능하다.

 

 

 

3. 메모리를 활용한 포인터 변수 접근 방법

 

#include<stdio.h>

int main(void) {
    int a = 10;
    int b = 20;

    int* p;

    p = &a;

    *p = 100;

    p = &a - 1;  // a주소를 가지고 b주소에 접근, p--도 가능;
    *p = 200;

    printf("a : %d\n", a);
    printf("b : %d\n", b);

    return 0;
}

 

그렇다면, 포인터 변수에도 사칙연산이 통할까? 예를 들어 주소값을 1 낮춘다더지 해서 다른 값을 집어넣을 수도 있지 않은가?

 

가능은 하다. 위의 코드를 보면 -1을 해서 b주소에 접근을 할 수 있다. 여기에서 p의 경우, 포인터의 값을 가지고 있기 때문에, -1을 하면, int 타입의 크기만큼인 '4'만큼 주소값을 빼준다. 그렇게 되면, &a에 1은 뺀다면, b는 나중에 선언되었기 때문에, 나중에 선언된 값을 가리키는 주소값으로 p의 값이 변경된다.

그런다음, *p = 200; 을 하여, p가 가지고 있는 주소로 가서 변수를 200으로 바꾸면, b가 200이 되는 것을 볼 수 있다.

 

 

4. 함수의 매개변수로 사용가능한 포인터

 

// 문제 : 원본값을 훼손하는 change 함수를 만들어주세요.

#include <stdio.h>

void change(int* num) {
  *num = 50;
}

int main(void) {
  int x = 20;
  printf("change 함수 호출하기 전의 x : %d\n", x);
  change(&x);
  printf("change 함수 호출한 후의 x : %d\n", x);
  return 0;
}

 

매개변수로 포인터 변수를 받아서 그 값을 변경하는 것도 가능하다. 단, 매개변수를 주소값으로 환원해서 받아야 할 것이다.

그렇게 해야 함수 내에서 * 를 활용해서 값을 변경할 수 있으니까.

728x90

댓글