프로그래밍/C, C++

# C언어 포인터(Pointer) 완벽 가이드

ridev0901 2024. 12. 3. 09:21
728x90
300x250

C언어 포인터란 무엇인가?

 

포인터(pointer)는 메모리 주소를 저장하는 변수입니다. 보통 우리가 사용하는 변수는 값(value)을 저장하지만, 포인터는 그 값이 저장된 메모리의 주소를 가리키는 역할을 합니다.
 

포인터를 자세히 알아보기 전에!!
포인터랑 떼려야 뗄수 없는 배열 포스팅 내용 먼저 복습하고 가는 것도 좋겠습니다~.
 
2024.11.28 - [프로그래밍/C, C++] - C언어 배열 완벽 가이드

 

C언어 배열 완벽 가이드

C언어 배열 완벽가이드: 초보자를 위한 친절한 설명과 예제 왜 배열을 사용할까요?C언어에서 배열은 같은 종류의 데이터를 연속적으로 저장하기 위한 공간입니다. 마치 서랍장에 같은 종류의

ridev0901.tistory.com

 
C언어에서 포인터는 메모리 주소를 저장하는 변수입니다. 즉, 포인터는 다른 변수의 주소를 참조함으로써, 메모리 관리와 데이터 접근을 보다 유연하게 할 수 있게 해줍니다. 포인터의 필요성은 크게 두 가지로 요약될 수 있습니다. 첫째, 효율적인 메모리 사용입니다. 포인터를 사용하면 큰 데이터 구조를 복사하는 대신 그 주소만을 전달하여 메모리 사용을 최적화할 수 있습니다. 둘째, 복잡한 데이터 구조(예: 연결 리스트, 트리 등)를 구현할 수 있는 기반을 제공합니다. 이러한 이유로 포인터는 C언어의 강력한 기능 중 하나로 자리 잡고 있습니다.
 

일반 변수와 포인터 변수의 차이

  • 일반 변수는 값 자체를 저장합니다.
  • 포인터 변수는 해당 값이 저장된 메모리의 주소를 저장합니다.
#include <stdio.h>

int main() {
    int num = 10;
    int *ptr = &num; // ptr은 num 변수의 주소를 저장

    printf("num의 값: %d\n", num);
    printf("ptr이 가리키는 값: %d\n", *ptr);
    printf("ptr이 저장하고 있는 주소: %p\n", ptr);

    return 0;
}





  • & 연산자: 변수의 주소를 반환합니다.
  • * 연산자: 포인터가 가리키는 값을 반환합니다. (역참조 연산자)

이 코드를 실행하면 다음과 같은 결과가 나옵니다.

 

num의 값: 10
ptr이 가리키는 값: 10
ptr이 저장하고 있는 주소: 0x7fffd7f8482c
300x250

포인터 사용 상황과 방법

1. 동적 메모리 할당

C언어에서는 `malloc()`, `calloc()`, `realloc()`, `free()` 함수를 사용하여 동적으로 메모리를 할당하고 해제할 수 있습니다. 이때 포인터를 사용하여 메모리 주소를 저장하고 관리합니다.

c

#include <stdio.h>
#include <stdlib.h>

int main() {
    int *arr;
    int n = 5;

    // 동적 메모리 할당
    arr = (int *)malloc(n * sizeof(int));
    if (arr == NULL) {
        printf("메모리 할당 실패\n");
        return 1;
    }

    // 배열에 값 대입
    for (int i = 0; i < n; i++) {
        arr[i] = i + 1;
    }

    // 값 출력
    for (int i = 0; i < n; i++) {
        printf("%d ", arr[i]);
    }

    // 메모리 해제
    free(arr);
    return 0;
}



2. 배열과 포인터

포인터는 배열과 밀접하게 연관되어 있습니다. 배열의 이름은 해당 배열의 첫 번째 요소의 주소를 나타내므로, 포인터를 통해 배열의 요소에 직접 접근할 수 있습니다.
 

c

#include <stdio.h>

int main() {
    int arr[] = {1, 2, 3, 4, 5};
    int *ptr = arr; // 배열의 첫 번째 요소 주소를 포인터에 할당

    for (int i = 0; i < 5; i++) {
        printf("%d ", *(ptr + i)); // 포인터 산술을 사용하여 배열 요소 접근
    }
    return 0;
}




3. 함수와 포인터

포인터를 사용하면 함수에 인자를 전달할 때 직접 메모리를 수정할 수 있습니다. 이는 값에 의한 호출(call by value) 대신 참조에 의한 호출(call by reference)을 가능하게 합니다.

c

#include <stdio.h>

void updateValue(int *p) {
    *p = 10; // 포인터를 통해 값 변경
}

int main() {
    int num = 5;
    printf("변경 전: %d\n", num);
    updateValue(&num); // 변수의 주소를 전달
    printf("변경 후: %d\n", num);
    return 0;
}

 
 

728x90

주의해야 할 점

  • NULL 포인터: 포인터가 어떤 값도 가리키지 않을 때 NULL 포인터라고 합니다. NULL 포인터를 역참조하면 프로그램이 비정상적으로 종료될 수 있습니다.
  • 메모리 누수: 동적 메모리를 할당하고 해제하지 않으면 메모리 누수가 발생할 수 있습니다.
  • 포인터 연산: 포인터에 정수를 더하거나 빼는 연산을 통해 배열 요소에 순차적으로 접근할 수 있지만, 잘못된 연산은 예기치 못한 결과를 초래할 수 있습니다.



포인터의 장단점과 다른 언어와의 비교

장점

1. 효율적인 메모리 관리: 포인터를 통해 메모리를 직접 관리하고 최적화할 수 있습니다.
2. 유연한 데이터 구조: 연결 리스트, 트리 등 다양한 데이터 구조를 쉽게 구현할 수 있습니다.
3. 성능 향상: 데이터 복사 없이 주소를 전달하여 성능을 향상시킬 수 있습니다.
 


단점

1. 복잡성: 포인터의 개념이 복잡하고, 잘못 사용 시 오류(예: 메모리 누수, 세그멘테이션 오류 등)를 발생시킬 수 있습니다.
2. 디버깅 어려움: 포인터 관련 버그는 찾기 어려운 경우가 많아 디버깅이 복잡할 수 있습니다.

 

다른 언어와의 비교

- C++: C언어의 포인터 개념을 계승하면서 스마트 포인터(예: `std::unique_ptr`, `std::shared_ptr`)를 도입하여 메모리 관리의 안전성을 높였습니다.
- Python: 포인터의 개념이 없지만, 모든 객체가 참조로 전달되어 유사한 방식으로 작동합니다. 그러나 메모리 관리의 복잡성을 숨기므로 사용이 더 간편합니다.
- Java: 포인터를 지원하지 않지만, 객체를 참조로 전달하는 방식으로 메모리 관리를 단순화했습니다. 개발자는 메모리 주소를 직접 다루지 않아도 됩니다.

포인터는 C언어의 강력한 기능으로, 프로그래머가 메모리와 데이터에 대한 세밀한 제어를 가능하게 합니다. 그러나 그만큼 신중하게 사용해야 하며, 특히 메모리 관리에 대한 이해가 필수적입니다.

728x90
300x250