Machineboy空
포인터(pointer)와 이터레이터(Iterator), 동적할당/ 정적할당 본문
포인터를 직접 사용해야 하는 언어들
즉, 메모리 관리를 직접할 수 있는 언어들.
메모리 관리를 가비지 컬렉터(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로 고정
(집 주소의 크기는 집의 크기와 관련이 없다)
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 |