C 언어 - 포인터 / 포인터 변수

컴퓨터/C

728x90
반응형

본문을 읽기 전 보고 오는 것은 권장합니다.

 

C 언어 - 메모리주소

프로그래밍과 메모리 프로그래밍과 메모리는 아주 밀접한 관련이 있습니다. 프로그래밍을 한다는 것은 메모리에 각종 데이터를 저장하고 그 데이터를 CPU나 각종 연산장치를 통해 계산하는 것

blog-of-gon.tistory.com

 

포인터

C 언어에서는 메모리주소로 접근하여 그 메모리 공간의 데이터를 이용할 수 있는 방법을 제공해 줍니다. 

이것이 바로 C언어의 꽃이라고 하는 포인터라는 개념입니다. 사실 사용되고 있는 프로그래밍 언어에 포인터의 개념은 다 들어있습니다. 다만, 우리가 직접 사용을 하느냐 또는 언어에서 제공을 하느냐의 차이일 뿐입니다.

그러면 C언어에서 이 메모리 주소에 접근하는 포인터에 대하여 알아보도록 하겠습니다.

 

포인터 변수

메모리의 주소를 저장하는 변수를 포인터 변수라고 합니다.  조금 생소한 개념일 수 있지만 정리하자면 아래와 같습니다.

  • C언어에서 변수를 선언
    • 메모리 주소의 공간을 할당하여 이름(변수명)을 지정하고 그 안에 데이터(자료형)를 넣는  것이다.
  • 컴퓨터의 입장에서는 변수의 메모리 주소를 알고 있어야 그 변수가 지정된 메모리 공간에 접근할 수 있다.
  • 포인터 변수의 선언
    • 메모리 주소의 공간을 할당하여 이름(변수명)을 지정하고 그 안에 데이터(자료형의 메모리의 주소)를 넣는 것이다. 
  • 포인터 변수는 다른 변수의 메모리 주소를 저장하여 그 메모리 주소 안의 값을 제어할 수 있다.

포인터 변수를 사용하는 이유

변수와 메모리 주소 그리고 포인터 변수에 대하여 이해를 했다면 이제 왜 포인터 변수를 사용하는지 알아봐야 됩니다.

 

가장 대표적인 장점은 바로 지역변수와 함수의 한계를 극복할 수 있습니다.

지역변수는 중괄호 { } 안에서만 사용할 수 있고 함수의 매개변수로 넘어가게 되면 값이 복사된 형태로 함수안에서 새로운 지역변수가 생기는 형태가 됩니다.  그리고 함수는 하나의 반환만을 할수 있습니다.  

#include <stdio.h>
void func(int a, int b)
{
    a += 10;
    b += 10;
}
int main()
{
    int a = 1;
    int b = 2;
    printf("함수를 실행 하기 전 변수 a 의 값 : %d\n", a);
    printf("함수를 실행 하기 전 변수 b 의 값 : %d\n", b);
    func(a, b);
    printf("함수를 실행 후 변수 a 의 값 : %d\n", a);
    printf("함수를 실행 후 변수 b 의 값 : %d\n", b);
    return 0;
}

main문의 지역변수 a, b의 값을 함수 func의 매개변수 a, b로 넣어주어 함수 안에서 변경해도 함수 안의 지역변수(매개변수) a, b의 값이 변경되는 것이기 때문에 main함수의 지역변수 a, b는 값의 변화가 없습니다.

 

하지만 포인터 변수를 사용하면 이런 한계점을 극복이 가능해집니다.

포인터 변수는 메모리의 주소를 저장하고 있고 그 메모리 주소로 접근하여 그 메모리 안의 데이터를 변경할 수 있습니다.

따라서 다른 함수 안의 매개변수로 메모리의 주소 값을 복사하여 가지고 가서 그 메모리 주소 안의 데이터를 변경한다면 다른 지역에 있는 지역변수의 값도 제어가 가능하게 됩니다.

 

포인터 변수의 선언 및 사용

선언 과 초기화
    자료형* 변수명 (=&변수명);
초기화와 사용
    변수명 = &변수명;
     *변수명 = 메모리 주소에 넣을 데이터

포인터 변수는 2가지 일을 할 수 있습니다. 

  • 포인터 변수에 다른 변수의 메모리 주소를 저장한다.
  • 저장된 메모리 주소에 접근하여 그 메모리 주소의 데이터를 변경할 수 있다.

사용방법에 대하여 자세히 정리하자면 다음과 같습니다.

  • 포인터 변수에 저장할 메모리 변수의 자료형과 포인터 변수임을 알리는 * 그리고 포인터 변수 명으로 선언한다.
  • 포인터 변수에 주소 연산자 &를 사용하여 저장할 변수의 주소를 넣어준다 (&변수명)
    • 선언과 동시에 다른 변수의 메모리 주소를 넣을 수 있다.
    • 선언 후 다른 변수의 메모리 주소를 넣으려면 변수명 = &변수명(변수의 메모리 주소)을 쓴다.
    • 포인터에 저장된 메모리 주소의 정보를 변경 또는 확인하려면 변수명 앞에 *을 표시한다.

포인터 변수 사용해보기

#include<stdio.h>
int main()
{
    int a = 1; // 변수 a 선언
    int* p =&a; // 포인터 변수 p 선언 및 변수 a의 주소 저장
    p = &a; // 포인터 변수 p에 변수 a의 주소 저장 (윗줄과 동일)
    printf("변수 a의 값 : %d\n", a);
    printf("포인터 p에 저장된 메모리주소안의 값 : %d\n", *p);
    *p = 10; // 포인터 변수 p에 저장된 메모리주소 안의 값을 10으로 변경
    printf("포인터 변수로 제어 후 변수 a의 값 : %d\n", a);
    printf("포인터 변수로 제어 후 포인터 p에 저장된 메모리주소안의 값 : %d\n", *p);
    a = 100;
    printf("변수 a를 변경 후 변수 a의 값 : %d\n", a);
    printf("변수 a를 변경 후 포인터 p에 저장된 메모리주소안의 값 : %d\n", *p);
    return 0;
}

  • 포인터 변수 p에 변수 a의 주소를 저장했습니다.
  • 포인터 변수에 저장된 메모리 주소 안의 데이터 값과 변수 a의 값은 동일합니다.

포인터 변수 메모리 주소 확인해보기

#include<stdio.h>
int main()
{
    int a = 1; // 변수 a 선언
    int* p =&a; // 포인터 변수 p 선언 및 변수 a의 주소 저장
    p = &a; // 포인터 변수 p에 변수 a의 주소 저장 (윗줄과 동일)
    printf("변수 a의 메모리 주소 : %p\n", &a);
    printf("포인터 변수에 저장된 메모리 주소 : %p\n", p);
    printf("포인터 변수의 메모리 주소 : %p\n", &p);
}

  • 포인터 변수 안에 저장된 메모리 값을 보기 위해서는 printf % p 형식 지정자를 사용합니다. 
  • 결과를 보면 변수 a와 포인터 변수에 저장된 메모리 주소는 동일하다는 것을 알 수 있습니다.
  • 따라서 포인터 변수를 통해 변수 a를 제어할 수 있게 되는 것입니다.
  • 포인터 변수의 메모리 주소는 포인터 변수가 저장된 메모리 주소입니다.

함수의 이동일 때 지역변수의 개념 확인해 보기

#include <stdio.h>
#include <stdio.h>
void func(int a, int b)
{
    printf("func 함수 지역변수 a의 메모리 주소 : %p\n", &a);
    printf("func 함수 지역변수 b의 메모리 주소 : %p\n", &b);
    a += 10;
    b += 10;
}
int main()
{
    int a = 1;
    int b = 2;
    printf("main 함수 지역변수 a의 메모리 주소 : %p\n", &a);
    printf("main 함수 지역변수 b의 메모리 주소 : %p\n", &b);
    func(a, b);
    return 0;
}

  • 결과와 같이 지역변수의 개념을 사용하기 때문에 main과 func에 있는 변수 a, b는 서로 다른 것이라는 것을 확인했습니다. 따라서 두 개의 변수 a, b를 일반 변수를 사용하면 제어하기란 쉽지 않다는 것을 확인했습니다.

포인터 변수를 사용하여 함수에 넘겨보자

#include <stdio.h>
void func(int *a, int *b)
{
    printf("func 함수 포인터a에 저장된 메모리 주소 : %p\n", a); // int* a = &a가 매개변수에서 된 상태
    printf("func 함수 포인터b에 저장된 메모리 주소 : %p\n", b); // int* b = &b가 매개변수에서 된 상태
}
int main()
{
    int a = 1;
    int b = 2;
    printf("main 함수 지역변수 a의 메모리 주소 : %p\n", &a);
    printf("main 함수 지역변수 b의 메모리 주소 : %p\n", &b);
    func(&a, &b);
    return 0;
}

  • 포인터 변수를 매개변수로 사용했습니다.
  • main함수의 지역변수 a와 b의 메모리 주소를 함수의 매개변수로 전달해 줬습니다.
  • 따라서 함수의 매개변수 (포인터 변수)에 main함수 안에 지역변수 a, b의 메모리를 복사한 func지역에 만들어진 포인터 변수입니다.

그리고 위에서 포인터 변수는 어떤 변수의 메모리 주소를 저장하고, 저장된 메모리 주소 안의 데이터를 접근하여 사용 변경할 수 있습니다. 그럼 최종 예제로 지역변수의 한계를 극복해보도록 하겠습니다.

 

함수에서 지역변수의 한계 극복해보기

#include <stdio.h>
void func(int *a, int *b)
{
    *a = 100;
    *b = 200;
}
int main()
{
    int a = 1;
    int b = 2;
    printf("함수 실행 전 a의 값 : %d\n", a);
    printf("함수 실행 전 b의 값 : %d\n", b);
    func(&a, &b);
    printf("함수 실행 후(포인터 매개변수 이용) a의 값 : %d\n", a);
    printf("함수 실행 후(포인터 매개변수 이용) b의 값 : %d\n", b);
    return 0;
}

  • 함수에 포인터 형태의 매개변수를 선언하고
  • main함수에서 지역변수들의 메모리 주소를 넘겨준다.
  • 함수 안에서 포인터 변수로 선언된 매개변수 a, b에 저장된 메모리 주소 안의 데이터를 변경한다.
  • main함수의 지역변수 a, b와 함수 안의 포인터 변수로 선언된 매개변수는 동일한 메모리 주소임으로 함수의 반환이 아닌 메모리 주소에 직접 접근하여 데이터를 컨트롤할 수 있다.

 

728x90
반응형

'컴퓨터 > C' 카테고리의 다른 글

C 언어 - 오버플로우와 언더플로우  (0) 2021.06.16
C 언어 - 다차원 배열  (0) 2021.06.14
C 언어 - 메모리주소  (0) 2021.06.12
C 언어 - 배열과 문자열  (0) 2021.06.09
C 언어 - 지역변수와 전역변수  (0) 2021.06.08

Commnet

G91개발일지

Gon91(지구일)

91년생 공학엔지니어의 개발일지

TODAY :

YESTER DAY :

TOTAL :