Machineboy空

포인터(pointer)와 이터레이터(Iterator), 동적할당/ 정적할당 본문

언어

포인터(pointer)와 이터레이터(Iterator), 동적할당/ 정적할당

안녕도라 2024. 1. 11. 16:16

포인터를 직접 사용해야 하는 언어들

즉, 메모리 관리를 직접할 수 있는 언어들.



메모리 관리를 가비지 컬렉터(garbage collector)가 아닌 사용자가 직접해주어야 하는 언어.

*가비지 컬렉터(garbage collector): 메모리 영역 중 더 이상 쓰이지 않는 영역을 자동으로 찾아내 해제하는 것

 

C, C++ 등은 가비지 컬렉터가 없고, 개발자가 직접 필요한 메모리를 예약하고 해제.

 

이에 사용되는 수단 중 하나가 포인터(pointer).

비유하자면 기다란 집게 같은 것.

 

Unmanaged 언어 정도의 low-level 언어들에서 지원된다.

 

C#같은 언어에서도 포인터를 쓰는 방법이 있지만 권장되지 않는 이유

= 메모리 주소에 직접 접근하는 것은 보안상 해킹 위험 등이 발생할 수 있기 때문


메모리와 포인터

 

메모리는 메모리 셀의 연속과 같으며, 각 셀의 크기는 1byte이고 고유한 주소가 존재한다.

포인터메모리 주소를 담는 데이터 타입(자료형)이다.

 

메모리 주소는 변수가 사용하는 메모리 주소의 첫번째로,

데이터 타입에 따라 메모리 주소로 부터 n칸 이런 식.

 

포인터의 역참조(*, dereference)를 통해 그 값에 직접 접근할 수 있다.

 

*char (1byte = 1칸)

*int (4byte = 4칸)

*double (8byte = 8칸)

 

* pointer의 크기는 어떤 타입이든 OS가 32bit라면 4byte , 64bit라면 8byte로 고정

(집 주소의 크기는 집의 크기와 관련이 없다)

int i = 0을 할당했다고 할 때

1 int i int 타입의 변수를 저장하기 위해 4 byte의 메모리 영역을 예약한다.
2 int i = 0; 예약한 메모리 영역 ( 0X0000 ~ 0X0003)에 int i의 값(0)이 저장된다.
3 cout << &i << '\n' 변수의 메모리 주소: 변수가 사용하는 메모리 주소의 첫번째 , 16진수 표기
*OS나, 실행시간에 따라 주소 할당이 달라진다.

&연산자(ampersand) : 변수의 메모리 주소 얻을 수 있다.
4 int *a = &i; 데이터 타입으로서의 포인터: 변수의 메모리 주소를 담는 타입

*:asterisk operator
* 포인터 자료형 선언
5 cout <<*a <<'\n' * 역참조(dereference) 연산자 : a에 담긴 값을 참조할 수 있다.

 


 

포인터와 배열

 

배열의 이름을 주소값으로 쓴다?

= 배열은 어차피 나란히 나열되어 있으니 배열 전체 대신, 배열의 첫번째 원소의 주소 값으로 정의

 

* Array to pointer decay 현상

#include <bits/stdc++.h>
using namespace std;

int a[3] = {1,2,3};

int main(){
	int * c = a;
    
    cout << c << "\n";			// 배열 a의 첫번째 원소 1을 가리키는 포인터
    cout << &a[0] << "\n";		// 배열 a의 첫번째 원소 1의 메모리 주소
    cout << c+1 << "\n";		// 배열 a의 두번째 원소 2를 가리키는 포인터
    cout << &a[1] << "\n";		// 배열 a의 두번째 원소 2의 메모리 주소
    
    return 0;
}

 

 

즉, 배열의 이름을 *c에 할당함으로서,

a[3]에서 배열의 크기 정보 3이 사라지고 첫번째 원소의 주소가 바인딩되는 현상.

 


포인터와 함수

즉 데이터를 복사하지 않고 함수 매개변수로 사용할 때 (Call by Reference)

 

일반적으로 함수를 사용할 때, 복사된 값이 사용된다.

이것은 복사된 크기만큼 중복된 값이 차지된다는 이야기.

 

참조값을 활용해 원본에 접근, 수정하고자할 때 포인터를 parameter, argument로 사용 가능.


포인터와 구조체, 클래스

#include <iostream>

#define SIZE 20

using namespace std;
// 문자열, 구조체에서의 pointer 사용

struct MyStruct
{
    char name[20];
    int age;
};

int main(){

// 문자열에서 사용하는 법
    char animal[SIZE];                  
    char* ps;

    cout << "동물 이름을 입력하십시오\n";
    cin >> animal;

    ps = new char[strlen(animal) + 1];  //* strlen : 변수의 크기 >> 실행시간 동안 얼마나 긴 것을 입력하든 유연하게 한 칸 더 넉넉한 크기를 부여
    strcpy(ps, animal);                 //* strcpy : 복사

    cout << "입력하신 동물 이름을 복사하였습니다" << endl;
    cout << "입력하신 동물 이름은 " << animal << "이고, 그 주소는 " << (int*)animal << " 입니다." << endl;
    cout << "복사된 동물 이름은 " << ps << "이고, 그 주소는 " << (int*)ps << " 입니다." << endl;    //  복사된 값의 주소값은 다르네?

// 구조체에서 사용하는 법 : Dynamic Struct 동적 구조체

    MyStruct* temp = new MyStruct;

    cout << "당신의 이름을 입력하십시오\n";
    cin >> temp ->name;                 // 동적 구조체 멤버 접근 법 1 (->)

    cout << "당신의 나이를 입력하십시오\n";
    cin >> (*temp).age;                 // 동적 구조체 멤버 접근 법 2 

    cout << "안녕하세요! " << temp ->name << "씨!\n";
    cout << "당신은 " << temp->age << "살 이군요!";

    return 0;
}

포인터의 쓸모

객체지향 프로그래밍 언어란?

 

컴파일 시간이 아닌 실행 시간에 어떠한 결정을 내릴 수 있다.

 

컴파일 시간 즉, 읽어들이는 시간에 모든 것이 결정되는 것이 아니라

실행 시간 즉, 돌아가는 와중에 결정을 내릴 수 있음을 의미한다.

= 융통성이 있다.

 

ex) 배열을 생성할 때

절차적 프로그래밍 객체지향적 프로그래밍
배열의 크기가 미리 결정되어야 한다. 배열의 크기를 실행시간 동안 결정할 수 있다.
  포인터를 통한 주소 미리 지정을 통해, 프로그램 실행하는 동안 결정되지 않은 값을 대입할 수 있음.

프로세스(프로그램 실행 ) 메모리 구조와 정적 할당/동적 할당

운영 체제는 프로그램 실행할 때 어떤 구조를 기반으로 메모리에 할당하는지.

정적 할당
(Static Memory Allocation)
동적 할당
(Dynamic Memory Allocation)
컴파일 단계에서 메모리를 할당하는 것 런타임 단계에서 메모리를 할당받는 것
운영 체제는 프로그램 실행할 때 어떤 구조를 기반으로 메모리에 할당하는지.


- 데이터 영역 (BSS 영역+ Data 영역)
- 코드 영역
운영 체제는 프로그램 실행할 때 어떤 구조를 기반으로 메모리에 할당하는지.

stack, heap
BSS Segment (block stated symbol)

전역변수,static,const로 선언된 변수 중 
0으로 초기화 or 초기화가 어떠한 값으로도 되어 있지 않은 변수들.
Stack

지역변수, 매개변수, 실행되는 함수에 의해
늘어나거나 줄어드는 메모리 영역

함수가 호출될 때마다
호출될 때의 환경 등 특정 정보가 stack에 계속해서 저장.

재귀함수 호출 시, 새로운 스택 프레임이 매번 사용되기 때문에
재귀함수 내의 지역변수로 선언하게 되면 해당 변수는 독립적으로 작용.
다른 함수에 있는 변수에 영향을 미치지 않는다.
Data Segment

전역변수,static,const로 선언된 변수 중 
0이 아닌 값으로 초기화된 변수들
Heap

동적으로 할당되는 변수들.

malloc(),free() 함수로 관리 가능.

vector와 같은 동적으로 관리되는 자료구조의 경우
Heap영역을 사용한다.
Code/Text Segment

프로그램의 코드

이터레이터(Iterator)

컨테이너에 저장되어 있는 요소(원소)의 주소를 가리키는 개체.

 

포인터를 일반화한 것.

(= 그냥 컨테이너 종류에 상관없이 몇번째 요소를 가리키는 화살표 정도의 의미로 해석하면되는 듯)

 

vector, map 등 각각 다르게 구현된 컨테이너들을 일반화된 이터레이터를 통해 쉽게 순회할 수 있다.

 

주소값을 바로 반환하지 못하고

&*를 통해 한단계 더 거쳐서 주소값을 반환한다.

 

*이더레이터(Iterator)의 대표적 함수

begin(), end(), advance(iterator, cnt)

#include <bits/stdc++.h>
using namespace std;

vector<int> v;

int main(){

    // vector공간에 1~5까지 집어넣었다.
    for(int i = 1; i <=5; i++) v.push_back(i);              

    // 
    for(int i = 0; i < 5; i++){
        cout<< i << "번째 요소 : " << *(v.begin()+i) << "\n";           //v.begin()주소값 반환 >> *(역참조 연산자)를 통해 값에 접근
        cout<< &*(v.begin()+ i) << '\n';                              //&(주소연산자)를 통해 주소 출력
    }

    //
    for(auto it = v.begin(); it != v.end(); it++){                  // auto의 데이터형은 *(포인터: 주소값을 담는 데이터형)이 할당될 것.                     
        cout << *it << ' ';                                         // v를 순회하면서 v의 원소를 하나씩 출력
    }
    cout << '\n';

    //
    for(vector<int>::iterator it = v.begin(); it != v.end(); it++){ // auto가 아니라 원래는 이렇게 선언되어야 함.
        cout << *it << ' ';
    }

    //
    auto it = v.begin();
    advance(it,3);          // * iterator를 원하는 곳으로 이동하는 함수

    cout << '\n';
    cout << *it << '\n';

    return 0;
}

 


포인터(pointer) vs 이터레이터(Iterator)

이터레이터(Iterator) 포인터(Pointer)
어떤 컨테이너(배열, 맵 등)의 범위 안에서 일부 요소를 가리키며
해당 요소들을 순회할 수 있는 개체
변수의 메모리 주소를 저장하는 개체
컨테이너의 개체를 참조하는 것이기 때문에 이 자체를 제거할 수 없다. delete를 통해 포인터를 제거할 수 있다.
(피셜)참조값 그 자체 (피셜)&값, 즉 주소값
요소의 타입과는 상관없이 컨테이너에 저장된 데이터를 순회하는 과정을 담당!  

 

이터레이터 = 일반화된 포인터 이게 무슨 말?

 

*일반화(generalization) :

객체지향에선 상속에 해당하는 말.

여러 사례들의 공통되는 속성들을 일반적인 개념으로 추상화의 한 형태

 

즉, 각각의 요소들을 쉽게 탐색할 수 있게 '일반화'한 장치.

'언어' 카테고리의 다른 글

<UX/UI의 10가지 심리학 법칙> 존 야블론스키  (4) 2024.07.14
Main 메소드 - 진입점 (Entry Point)  (0) 2024.01.09