[C++ 기초부터 심화까지 Chapter 03. Pointer and memory]

2025. 5. 7. 20:10·개발 언어/C++

03-1 포인터와 메모리

  • C++에서 가장 강력한 도구로 대표적으로 포인터가 뽑힌다.
  • 포인터를 이해하기 위해서는 데이터가 메모리에 저장되는 구조를 알아야한다.
  • 포인터는 데이터가 저장된 메모리 주소를 저장하는 변수이다.
  • 포인터 변수의 크기는 데이터 형식과는 관련이 없음. 모든 포인터 변수의 크기는 동일
  • 포인터 변수를 선언할 때 데이터 형식을 지정하는 이유는 해당 포인터가 가리키는 데이터의 형식을 명시하기 위함.
  • 다중 포인터도 가능!

포인터와 연산자

char char_value = 'A'; 
char *char_pointer = &char_value; # &(주소 연산자: 피연산자의 주소를 불러옴)`

cout << "char\_pointer:" << \*char\_pointer << endl; # \*(역참조 연산자)

배열과 포인터

  • 배열 선언 --> 자료형 배열_이름[크기] = {값1, 값2, 값3, ...., 값n}
  • 원소 접근 --> 배열_이름[인덱스] # 항상 0부터 시작함. 가장 마지막 원소는 "배열_이름[n-1]

포인터 연산으로 배열의 원소에 접근하기

  • 배열의 인덱스로 접근 and 포인터 연산으로 각 원소에 접근
  • -> &lotto[0] == lotto + 0과 동일함 --> 동일한 주소 반환
  • -> &배열_변수[인덱스] == 배열_변수 + 인덱스
  • -> ** 값을 불러오려면 *(역참조 연산자) 사용해야함!
  • -> ex) *(lotto + 7)

동적 메모리 할당

  • 동적 메모리를 할당하기 위해서는 new 키워드를 사용
  • 자료형 *변수_이름 = new 자료형;
  • 필요없는 시점에 delete 키워드로 반드시 해제해야함(직접).
  • delete 변수_이름;
  • 동적 할당 메모리 해제 이유: 함수의 매개변수나 지역변수처럼 대부분의 일반 변수는 Stack에 메모리가 할당되고 함수의 호출과 함께 할당되며 반환되면 자동으로 소멸함. 하지만 동적 할당 변수는 Heap이라는 메모리 영역에 존재하며 계속 유지됨. 메모리 영역도 훨씬 큼
  • segmentation fault
  • 프로그램이 허용되지 않은 메모리 영역에 접근을 시도하거나, 허용되지 않은 방법으로 메모리 영역제 접근을 시도할 때 발생
  • 포인터를 역참조하기 전에 포인터가 유효한 메모리를 가리키는지 확인이 필수
  • 유효하지 않은 메모리를 가리키는 포인터를 역참조하려고 하면 위 오류나 런타임 오류가 발생할 수 있음

03-2 함수와 구조체

  • function은 특정 작업을 수행하는 코드 집합
  • 기본적인 구성
  • int(반환 형식) func(함수이름) (int _arg1, int _arg2) (매개변수) { 함수 몸체 }

함수 선언의 4가지 필수 요소

  1. 반환 형식: 함수가 반환할 값의 자료형. 반환할 값이 없을 때는 void로 표기
  2. 함수 이름: 함수를 호출할 때 사용할 이름. 함수의 이름은 문자나 _(밑줄)로 시작하며 숫자X, 공백 X
  3. 매개 변수: 함수가 호출될 때 전달받은 값을 저장하는 변수로, 함수 내부에서만 사용할 수 있는 지역 변수. 전달 받을 값이 없을 때는 비워두거나, void로 매개변수가 없음을 표기할 수 있음.
  4. 함수 몸체: 함수의 기능을 정의하는 부분

function.cpp 작성 중 size 관련 궁금증 답변

🔍 핵심 차이점

  1. main 함수에서의
  2. sizeof(score) int score[5] = {90, 75, 80, 100, 65}; score_size = sizeof(score) / sizeof(int);
  • score는 이 시점에서는 정확히 크기가 정해진 배열이야. 즉, int[5]형이야.
  • sizeof(score)는 배열 전체의 바이트 크기를 줘. int가 4바이트라면, 5 * 4 = 20바이트.
  • sizeof(int)는 당연히 4바이트.
  • 따라서 score_size = 20 / 4 = 5.
  1. average 함수에서의
  2. sizeof(_array) int average(int _array[], int _count) { score_size = sizeof(_array) / sizeof(int); // ... }
  • 여기서 _array는 배열처럼 보이지만 실제로는 포인터야!
  • C++에서는 배열을 함수의 인자로 전달할 때 배열이 포인터로 decay(변형) 돼서 전달돼. 즉, _array는 사실상 int* _array로 해석돼.
  • sizeof(_array)는 포인터의 크기를 반환해. 일반적으로 8바이트 (64비트 시스템 기준).
  • sizeof(int)는 여전히 4바이트.
  • 결과적으로 score_size = 8 / 4 = 2가 되는 것처럼 보일 수도 있어.
  • 하지만 정확한 값은 시스템 아키텍처에 따라 다르고, 어쨌든 중요한 건 sizeof(_array)는 배열 전체 크기가 아니라 포인터 크기라는 점이야.

✅ 요약

  • main() 함수 내 --> 배열 전체 크기 --> score는 int[5] 타입
  • average() 함수 내 --> 포인터 크기 --> _array는 int*로 해석됨 (배열 → 포인터 decay)
## 동일 표현
int average(int _array[], int _count)


int average(int* _array, int _count)

즉, _array는 score 배열의 첫 번째 요소의 주소를 가리키는 포인터.
함수 안에서 _array[i]라고 쓰는 건, 포인터 연산을 이용한 배열 접근.

동일 표현

sum += _array[i];
sum += *(_array + i);

구조체

  • 단일 변수만 취급하고 return 하니까 답답하죠?
  • 그래서 구조체가 있다.
  • 구조체는 구조체 변수 선언을 해야지 사용할 수 있음
  • Ex) 사람의 정보 구조체
    struct Person
    {
      std::string name;
      int age;
      float height;
      float weight;
    };

Person adult;  
adult.name = "Siwon"  
adult.age = 26;  
adult.height = 173;  
adult.weight = 73;

<Person adult;>

03-3 정적 변수와 상수 변수

static과 const는 c++ 언어에서 자주 혼동하는 키워드이다.

정적 변수 선언하기 - static

지역 변수와 전역 변수의 차이를 간단하게 복습

  • 지역 변수는 선언된 지점에서 생성되고 해당 블록이 끝나면 소멸(auto duration)
  • --> 지역 변수(local variable): 함수 내부에 선언된 변수로, 해당 블록 내에서만 효력이 있음
  • --> 전역 변수(global variable): 전역 범위에 선언된 변수로, 해당 파일 전체에서 효력이 있다.

static의 기능

  • 지역 변수에 static 키워드를 사용하면 auto duration에서 static duration으로 변수의 유효 범위가 바뀜
  • 즉 static 키워드는 지역 변수를 정적 변수로 바꾼다.
  • 이렇게 선언된 정적 변수는 선언된 블록이 끝나더라도 값을 유지함.
  • static_variable_1.cpp 참고

static으로 정적 변수를 선언할 때는 반드시 초기화 해줘야한다. --> static int ID = 0; 이런식으로
안하면 자동으로 0으로 초기화함.

  • ID 같은 걸 차례대로 생성할 때도, 유용하게 사용됨

상수 변수 선언하기 - const

constant(상수)란 변하지 않는 값임.

  • 변수에 const 키워드를 사용하면, 값을 변경할 수 없게 됨.
void topic_callback(const std_msgs::msg::String & msg) const;
  • callback 함수가 읽기 전용이라는 것을 의미, 내부에 멤버 변수들을 절대 바꿀 수 없음.
    int getValue() const {
      return this->value; // O 가능
      this->value = 10;   // X 오류! const 함수에서는 값 못 바꿈
    }
  • const 변수를 사용할 때는 반드시 초기화를 해야하며, 초기화를 하지 않으면 컴파일 오류가 발생한다.
  • 그리고 초기화 이후에 새로운 값을 넣으려고 해도 컴파일 오류가 발생한다. ROS 코드에서 저렇게 하는 이유도 읽기 전용 코드이며 안에서 멤버 변수들을 절대 바꾸지 않도록 방지하는 역할임.

포인터 변수의 상수화

  • 일반 변수를 상수화하는 건 컴파일 오류만 주의하면 되지만, 포인터 변수를 상수화할 때는 const 키워드의 위치에 따라 상수화할 대상이 달라지므로 구분해서 사용해야함.

const int *ptr = &a --> *ptr 상수화 = 포인터 변수가 가리키는 값을 상수화

  • ptr은 일반 포인터
  • 가리키는 값(*ptr)이 상수
  • 따라서 *ptr = 2;는 금지지만(a를 바꾸는 것), ptr = &b;처럼 가리키는 대상은 변경 가능

int *const ptr = &a --> ptr 상수화 = 포인터 변수 자체를 상수화

  • 가리키는 값은 변경됨
  • ptr은 포인터인데, 포인터 자체가 const
  • 즉, ptr이 다른 주소를 가리키는 건 금지

const int *ptr = &a;는 “내가 (ptr로서) a를 볼 수는 있는데, 손대면 안 돼!” 이런 느낌

    int a = 0;
    int b = 1;
    const int *ptr = &a; // *ptr를 상수화 = 포인터 변수 자체를 상수화


    cout << "before ptr: " << *ptr << endl;


    // *ptr = 2; // 컴파일 에러

    ptr = &b; // ptr이 다른 걸 가리키는 건 됨


    a = 3; // 이건 또 됨
    // const가 제한하는 대상이 "포인터를 통해 값을 바꾸는 것"만 금지라는 사실
    cout << "after ptr: " << *ptr << endl;
    cout << "after a: " << a << endl; // a가 변경됨


    ------------------------------------
    int a = 0;
    int b = 1;
    int *const ptr = &a; // ptr 상수화 = 포인터 변수 자체를 상수화 --> 가리키는 값 a 는 변경 가능함


    cout << "before ptr: " << *ptr << endl;


    *ptr = 2; // 가리키는 값 a를 변경
    // ptr = &b; // 컴파일 에러


    cout << "after ptr: " << *ptr << endl;
    cout << "after a: " << a << endl; // a가 변경됨
    return 0;

03-4 레퍼런스 변수

C++ 언어에서는 포인터 대신 사용할 수 있는 레퍼런스라는 변수 형식을 제공한다.

  • 포인터를 사용하면 최적화와 성능을 향상시킬 수 있지만 어려워서 잘못 사용할 수도 있음.
  • 레퍼런스를 통해서 포인터의 이점을 누릴 수도 있음.

레퍼런스 사용하기

C++에서 제공하는 3가지 변수 형식

  • 일반 변수: 값을 저장하는 변수
  • 포인터 변수: 메모리 주소를 저장하는 변수
  • 레퍼런스: 변수에 또 다른 이름, 별칭을 부여

레퍼런스 변수 선언

  • 자료형 &레퍼런스_변수_이름 = 대상_변수_이름;
  • 레퍼런스로 사용할 때는 메모리 주소가 아닌 원본 변수를 참조하겠다는 의미
  • 함수의 매개변수를 레퍼런스로 선언하고 호출하면 호출할 때 넘긴 변수에 각각 별칭(또 다른 이름)이 부여된다고 이해하면 됨. 즉, 실제 변수는 하나지만 이름이 2개가 되는 것.
  • int &ref_a = a; int &ref_b = b;
  • 레퍼런스는 포인터를 비교적 안전하게 사용할 수 있도록 만든 도구
  • 포인터처럼 원본 값에 접근할 수는 있지만, 원본 자체나 공간의 크기, 메모리 주소등은 변경하지 못하게 막은것

 

※ 이 글은 직접 구매한『Do it! C++ 완전 정복』(문종채, 조규남 저, 성안당)을 참고하여 개인적으로 학습한 내용을 정리한 것입니다. 본문에 사용된 내용 및 예제 코드는 책의 내용을 기반으로 하되, 이해를 돕기 위한 개인적인 해석과 실습 결과를 포함하고 있습니다.

 

참고한 예제 코드: [GitHub - mystous/DoItCPP](https://github.com/mystous/DoItCPP) 
예제 코드의 일부는 위 오픈소스 저장소를 참고하거나 수정하여 활용했으며, 해당 저장소는 학습용으로 공개되어 있습니다.

'개발 언어 > C++' 카테고리의 다른 글

[C++ 기초부터 심화까지 Chapter 06. 객체지향과 클래스]  (0) 2025.05.08
[C++ 기초부터 심화까지 Chapter 05. 예외 처리 구문]  (0) 2025.05.07
[C++ 기초부터 심화까지 Chapter 04. 실행 흐름 제어]  (0) 2025.05.07
C++ 공부에 관하여  (0) 2025.05.07
'개발 언어/C++' 카테고리의 다른 글
  • [C++ 기초부터 심화까지 Chapter 06. 객체지향과 클래스]
  • [C++ 기초부터 심화까지 Chapter 05. 예외 처리 구문]
  • [C++ 기초부터 심화까지 Chapter 04. 실행 흐름 제어]
  • C++ 공부에 관하여
lidarmansiwon
lidarmansiwon
lidarmansiwon 님의 블로그 입니다.
  • lidarmansiwon
    라이다맨 시원의 연구개발 라이프
    lidarmansiwon
  • 전체
    오늘
    어제
    • 분류 전체보기 (12)
      • 이론 정리 (2)
        • Thor I. Fossen 리뷰 (1)
      • Ubuntu 및 Linux (0)
        • Trouble shooting (0)
      • 개발 언어 (5)
        • C++ (5)
        • Python (0)
      • 논문 리뷰 (3)
      • ROS2 (2)
  • 블로그 메뉴

    • Github
    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    motioncontrol
    슬라이딩 모드 컨트롤
    usv formation
    do it! c++ 완전 정복
    해양공학
    fossen
    singlethreadedexecutor
    이접안
    C++
    multithreadedexecutor
    자율선박
    ROS2
    maritimerobotics
    Sliding mode control
    marinecraft
    cpp
    c++ 기초부터 심화까지
    usv formation path planning based on behavior trees and fast marching method
    navigationcontrol
    실행 흐름 제어
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
lidarmansiwon
[C++ 기초부터 심화까지 Chapter 03. Pointer and memory]
상단으로

티스토리툴바