본문 바로가기
개발/C++ (98,03,11,14,17,20,23)

Modern C++ : pointer and address (98, 11)

by snowoods 2025. 8. 13.

Modern C++

포인터와 주소 (Pointer and Address)

 

개요

C++에서 포인터(pointer)와 주소(address)는 메모리를 직접 제어하고 데이터를 효율적으로 관리하기 위한 핵심 개념입니다. 모든 변수는 메모리 상의 특정 위치에 저장되며, 이 위치를 나타내는 값이 바로 '주소'입니다. '포인터'는 이러한 메모리 주소를 값으로 가지는 특별한 변수입니다.

이 문서를 통해 변수의 주소를 얻는 방법, 포인터를 선언하고 사용하는 방법, 그리고 포인터를 통해 원본 변수의 값을 변경하는 '역참조(dereferencing)'에 대해 알아봅니다.

 

C++ 버전별 주요 키워드 도입 시기

  • C++98/03: 포인터의 기본적인 개념(*, &)이 이미 존재했습니다.
  • C++11
    • nullptr: 0이나 NULL 대신 포인터가 아무것도 가리키지 않음을 명시적으로 나타내는 키워드가 도입되었습니다.
    • auto: 변수 타입을 컴파일러가 자동으로 추론하게 하여, std::uint32_t*와 같은 긴 포인터 타입을 간결하게 선언할 수 있습니다.
    • <cstdint>: std::uint32_t와 같이 이식성 높은 고정 너비 정수형을 제공합니다.

 

내용 설명

아래 예제는 변수 my_age를 선언하고, 포인터 pointer_to_some_data가 이 변수의 주소를 가리키도록 한 뒤, 포인터를 이용해 my_age의 값을 변경하는 과정을 보여줍니다.

  1. auto my_age = std::uint32_t{30U};
    • 32비트 부호 없는 정수형 변수 my_age를 선언하고 값 30으로 초기화합니다.
    • 이 변수는 메모리의 특정 주소(예: 0x...F940)에 할당됩니다.
  2. std::cout << &my_age << "\n";
    • 주소 연산자(&)를 사용해 my_age 변수가 저장된 메모리 주소를 출력합니다.
  3. std::uint32_t *pointer_to_some_data = &my_age;
    • std::uint32_t 타입의 데이터를 가리키는 포인터 변수 pointer_to_some_data를 선언합니다.
    • 이 포인터에 my_age의 주소(&my_age)를 할당합니다. 이제 포인터는 my_age를 가리킵니다.
    • 포인터 변수 자체도 다른 메모리 주소(예: 0x...F6E8)를 가집니다.
  4. *pointer_to_some_data = 31;
    • 역참조 연산자(*)를 사용해 pointer_to_some_data가 가리키는 메모리 주소에 접근합니다.
    • 해당 주소에 있는 값을 31로 변경합니다. 이는 my_age의 값을 직접 변경하는 것과 동일한 효과를 가집니다.

 

예제 코드

#include <cstdint>
#include <iostream>

int main()
{
    // 1. 'my_age' 변수를 선언하고 메모리에 30이라는 값을 저장합니다.
    //    이 변수는 고유한 메모리 주소를 갖습니다.
    auto my_age = std::uint32_t{30U}; // 예: 주소 0x...F940에 값 30 저장
    std::cout << "'my_age'의 메모리 주소: " << &my_age << "\n";
    std::cout << "'my_age'의 초기 값: " << my_age << "\n\n";

    // 2. 'my_age'의 주소를 저장할 포인터 변수를 선언합니다.
    //    이 포인터 변수 자체도 별도의 메모리 주소를 갖습니다.
    std::uint32_t *pointer_to_some_data = &my_age; // 예: 주소 0x...F6E8에 'my_age'의 주소(0x...F940)를 저장
    std::cout << "'pointer_to_some_data' 포인터 변수 자체의 주소: " << &pointer_to_some_data << "\n";
    std::cout << "'pointer_to_some_data'가 가리키는 주소: " << pointer_to_some_data << "\n\n";

    // 3. 포인터를 통해 'my_age'의 값을 변경합니다 (역참조).
    *pointer_to_some_data = 31;
    std::cout << "포인터를 통해 변경된 'my_age'의 값: " << my_age << "\n";

    return 0;
}

 

실행 결과

// 실행 환경에 따라 실제 주소값은 매번 달라집니다.
'my_age'의 메모리 주소: 0x16d3b7314
'my_age'의 초기 값: 30

'pointer_to_some_data' 포인터 변수 자체의 주소: 0x16d3b7308
'pointer_to_some_data'가 가리키는 주소: 0x16d3b7314

포인터를 통해 변경된 'my_age'의 값: 31

 

활용팁

  • Null 포인터 확인: 포인터를 사용하기 전에는 항상 nullptr인지 확인하는 습관을 들이는 것이 안전합니다. if (ptr != nullptr) { ... }
  • Dangling 포인터 주의: 이미 해제된 메모리를 가리키는 '댕글링 포인터(dangling pointer)'를 역참조하면 정의되지 않은 동작(undefined behavior)이 발생하므로 매우 위험합니다.
  • 스마트 포인터 활용: C++11부터 도입된 스마트 포인터(std::unique_ptr, std::shared_ptr, std::weak_ptr)를 사용하면 메모리 누수와 같은 문제를 예방하고 메모리를 자동으로 관리할 수 있어 더 안전하고 현대적인 C++ 프로그래밍이 가능합니다.
  • 배열과 포인터: C-스타일 배열의 이름은 배열의 첫 번째 요소에 대한 포인터처럼 동작하여, 포인터 연산을 통해 배열 요소에 접근할 수 있습니다.