본문 바로가기

C언어

[C언어] 포인터 - 개념, *연산자, 배열, 동적할당

1. 포인터 개념

포인터란? 어떠한 변수의 주소를 저장하는 자료형이다. 포인터의 크기는 운영체제의 bit 수에 따라 달라진다. 운영체제가 32bit라면 포인터는 4byte, 운영체제가 64bit라면 포인터는 8byte의 고정 크기를 가진다. 

 

2. 포인터 연산자

포인터는 별(*)을 붙여 사용한다. 

 

①(자료형)(*) (변수) ex) int* pa = &a;

주소를 저장하는 변수의 선언 방법이다. *은 자료형 쪽에 붙일 수도 있고 변수 쪽에 붙일 수도 있다. 어디에 *을 붙이는지는 개발자의 취향 차이.

 

②(*)(변수) ex) *pa = 8;

해당 주소에 들어있는 원본 값에 접근하는 방법이다. 변수 앞에 별을 붙이면 주소를 저장하는 변수 pa의 원본 값 a에 접근할 수 있다. (아래 예제 코드 참고)

#include <stdio.h>

int main(void) {

    int a = 5;
    printf("a = %d\n", a); //a = 5

    int* pa = &a; // int *pa 와 같음 //pa에 a의 주소를 담는다. 
    printf("pa = %d\n", pa); // pa = -909511276
    
    *pa = 8; //주소값의 원본값에 접근
    printf("a = %d", a); // a = 8

    return 0;
}

pa의 값은 a의 주소 값으로 실행할 때마다 값이 바뀔 수 있다. 

 

3. 포인터와 배열

배열의 주소값을 넘겨줄 때에는 포인터를 사용하지 않는다. 포인터를 사용한다 해도 2차, 3차 혹은 그 이상의 배열이 아닌 이상 굳이 사용하지 않는다. 그 이유는 무엇일까? 배열은 그 자체로 주소 값이 되고 메모리 공간에 연속적으로 할당되기 때문이다. 

 

배열이 포인터가 되는 이유 => 접은 글 참조

더보기

사실 잘 이해가 가지 않았다. 배열이 어떻게 주소 값이 되는 것일까?

이를 이해하기 위해선 배열이 어떤 식으로 만들어지는지 짚고 넘어갈 필요가 있었다.

 

다음은 배열의 이름은 배열의 첫 번째 값과 같다는 증명이다.

#include <stdio.h>

int main(void) {
    int arr[3] = { 0, 1, 2 };

    printf("배열의 이름: %p\n", arr); //배열의 이름: 000000A783B9FC28
    
    printf("arr[0] = %p\n", &arr[0]); //arr[0] = 000000A783B9FC28
    printf("arr[1] = %p\n", &arr[1]); //arr[1] = 000000A783B9FC2C
    printf("arr[2] = %p\n", &arr[2]); //arr[2] = 000000A783B9FC30

    return 0;
}

%p라는 변환 문자를 사용해서 주소 값을 출력해보았다.

arr이라는 배열의 이름과 배열의 첫 번째 요소의 값이 같다는 사실을 확인할 수 있다. 배열이 그 자체로 주소 값이 되는 것이다. 또한 배열은 연속적으로 할당되기 때문에 각 요소마다 4 만큼씩 차이가 난다는 사실도 확인할 수 있었다. 

 

포인터 변수를 활용한 배열의 입력과 출력 코드이다. 배열이 연속적으로 메모리에 할당된다는 특성을 이용해 포인터 변수를 증가시키면서 배열에 할당하였다.

#include <stdio.h>

int main(void) {
    int arr[10];
    int* parr = arr; //배열 자체가 주소값을 나타낸다. 
	
    //배열 입력
    for (int i = 0; i < 10; i++) {
        scanf("%d", parr); //parr은 배열의 주소값
        parr++; //주소값을 한 칸 옮김. parr = parr + 4 => arr[1]
    }
	
    //배열출력
    for (int i = 0; i < 10; i++) {
        printf("%d ", arr[i]);
    }

    return 0;
}

 

4. 배열의 동적 할당 - malloc()

malloc()은 동적으로 메모리를 할당하는 함수이다. 동적으로 할당한다는 의미는 먼저 정적으로 할당한다는 의미를 알면 쉽게 이해할 수 있다. 지금까지 배열을 사용한다면 array [10]와 같이 배열의 크기를 정해줘야 했다. 배열의 크기를 정하지 않으면 배열을 사용할 수 없었다. 그렇다면 입력한 수만큼 배열의 크기를 지정하려면 어떻게 해야 할까? 이때 사용하는 것이 malloc함수, 즉 동적 할당이다. 동적 할당은 입력한 수만큼 배열의 크기를 지정하는 것처럼 조건에 따라 배열의 크기가 달라지게 한다.

 

동적으로 할당된 배열은 heap 영역에 할당된다. 

heap영역은 메모리 구조를 4가지 영역으로 나누었을 때 해당되는 하나의 영역으로, 동적으로 할당되는 데이터가 저장된다. 

메모리 구조. 메모리는 코드 영역, 데이터 영역, 힙 영역, 스택 영역으로 나누어져 있다.

 

동적 할당하는 배열 선언:

/* 배열을 동적으로 할당하는 법 */
int* ptr = (int*)malloc(sizeof(int));
// 변수명 = 자료형       할당할 크기
/* 포인터 변수에는 4byte만큼 할당이 됨 */

 

다음은 동적 할당의 예시이다. 배열의 크기를 입력받고 그 배열의 크기만큼 값을 입력하면 배열이 출력되는 코드이다. 

#include <stdio.h>
#include <stdlib.h> //malloc함수가 들어있음

int main(void) {

    int n;
    scanf("%d", &n);

    int* parr = (int*)malloc(n * sizeof(int)); //n의 크기를 가진 int형 배열을 생성
    int* pstrt = parr;

    for (int i = 0; i < n; i++) {
        scanf("%d", parr);
        parr++;
    }
    printf("\n");
    parr = pstrt;
    for (int i = 0; i < n; i++) {
        printf("%d ", *parr);
        parr++;
    }
    printf("\n");

    return 0;
}