👋 본격적으로 자료구조 공부를 하기 전, 필요한 개념들을 정리해보자!
1. Code Segment (.text)
2. Initialized Data Segment (.data)
3. Uninitialized Data Segment (.bss)
4. Heap
5. Stack
C언어 에서 어떠한 변수를 가리키고 싶을 때 포인터를 사용하였다. C++에서는 다른 변수를 가리키는 또 다른 방식을 제공하는데, 바로 참조자(레퍼런스, reference)이다.
int a = 3;
int& ref_a = a;
ref_a = 7;
std::cout << a << "\n";
std::cout << ref_a << "\n";
위 코드를 실행시키면 a와 ref_a 모두 7을 출력하는 것을 알 수 있다.
a의 참조자인 ref_a에게 수행하는 모든 작업은 a에 작업하는 것과 동일하다고 생각하면 된다.
참조자를 선언하는 것은 컴파일러에게 ref_a란 a의 또 다른 이름이라고 알려주는 것이다.
type& 참조자 변수명 = 참조하고 싶은 변수명;
→ 가리키고자 하는 타입 뒤에 &를 붙여 참조자를 선언한다.
→ 포인터 타입의 참조자는 int*& 처럼 쓰면 된다.
레퍼런스는 반드시 선언과 동시에 초기값을 설정해야 한다.
ex. int& ref_a; → 불가능!
레퍼런스가 가리키는 변수는 바뀔 수 없다.
ex. int& ref = a;
int b = 3;
ref = b; → 레퍼런스가 가리키는 대상은 여전히 a, 해당 코드는 그저 a = b와 같은 의미이다.
레퍼런스는 메모리 상에 존재하지 않을 수 있다.
포인터의 경우 가리키는 변수의 자료형에 따른 크기만큼 메모리를 차지하게 된다. 하지만 레퍼런스의 경우, 레퍼런스에 행해지는 모든 수행은 가리키는 원래의 변수에 하는 것과 같기 때문에 메모리 상에 존재할 필요가 없어진다. 하지만, 항상 메모리 상에 존재하지 않는 것은 아니다.
레퍼런스는 상수 리터럴을 참조할 수 없다.
ex. int& ref = 5; → 불가능!
const int& ref = 5; → const 레퍼런스 형태로 선언하면 참조 가능
int a, b;
int& arr[2] = {a, b};
→ 레퍼런스의 레퍼런스, 레퍼런스의 배열, 레퍼런스의 포인터는 존재할 수 없다. (C++ 표준안 8.3.2/4)
C++ 문법 상 배열의 이름은 첫 번째 원소의 주소값으로 변환될 수 있어야 한다.
이때문에 arr[1]과 같은 문장이 *(arr+1)로 바뀌어서 처리될 수 있는 것이다.
주소값이 존재한다 = 해당 원소가 메모리 상에 존재한다.
BUT 레퍼런스는 특별한 경우가 아닌 이상 메모리 상에 존재하지 않는다. 모순 발생 !
따라서 레퍼런스의 배열은 불가능 (언어 차원에서 금지됨)
int arr[3] = {1, 2, 3};
int(&ref)[3] = arr;
ref[0] = 2; //arr[0]의 레퍼런스
ref[1] = 3; //arr[1]의 레퍼런스
ref[2] = 1; //arr[2]의 레퍼런스
배열의 레퍼런스의 경우 반드시 배열의 크기를 명시해야 한다.
참조하고 싶은 배열의 크기와 동일해야한다.
int& function( ) {
int a = 2;
return a;
}
int main( ) {
int b = function( );
return 0;
}
위 코드를 실행하게 되면 segmentation fault 오류가 발생한다.
function( )의 리턴값은 int&로 레퍼런스이고 a를 참조한다. 하지만 a는 지역변수로 함수의 리턴과 함께 사라져버린다. 따라서 function이 레퍼런스를 리턴하면서 참조하던 변수가 사라졌기 때문에 오류가 발생하게 되는 것이다. 이와 같은 현상을 Dangling reference라고 부른다. Dangling 이란 달랑달랑 거리는 것을 뜻하는데, 레퍼런스가 참조해야 할 변수가 사라져 레퍼런스만 덩그러니 남은 상황과 유사하다.
→ 함수의 리턴값이 레퍼런스일때, 지역변수를 리턴하지 않도록 주의해야 한다.
int& function(int& a) {
a = 5;
return a;
}
int main( ) {
int b = 2;
int c = function(b);
return 0;
}
위 코드에서 function 함수는 인자로 받은 레퍼런스를 리턴값으로 한다.
function(b)를 실행하게 되면 a는 main의 b를 참조하게 되고 function의 리턴값은 아직 살아있는 변수인 b를 참조하게 된다. 따라서 c는 b의 값인 5를 대입하게 된다.
❓ 레퍼런스를 리턴했을 때의 장점?
레퍼런스를 리턴하게 되는 경우, 레퍼런스가 참조하는 타입의 크기와 상관 없이 주소값 복사 한 번으로 전달이 끝나게 되어 매우 효율적이다!
int function( ) {
int a = 5;
return a;
}
int main( ) {
int &b = function( );
return 0;
}
위와 같이 함수의 리턴값을 레퍼런스로 받는 경우 컴파일 오류가 발생한다. 앞서 배운 내용을 생각하면 당연한 것인데, 함수의 리턴값은 함수 종료시 사라지기 때문에 레퍼런스가 참조해야 할 변수가 사라지는 Dangling reference가 발생하게 되는 것이다.
하지만 C++에 한 가지 예외 규칙이 있다.
const int&b = function( ); 처럼 상수 레퍼런스로 함수의 리턴값을 받는 경우, 해당 리턴값의 생명이 연장되어 정상적으로 작동한다. 리턴값의 생명은 레퍼런스가 사라질 때까지 유효하다.
C에 malloc 과 free가 존재한다면, C++에는 new와 delete가 있다!
new는 malloc과 같이 메모리를 할당해주는 역할을 하고, delete는 free와 같이 동적할당 된 메모리를 해제해주는 역할을 한다.
int* p = new int;
*p = 7;
std::cout << *p;
delete p;
type* 포인터 변수명 = new type;
→ type의 크기에 해당하는 공간을 할당하여 그 주소값을 포인터 변수에 저장
delete 포인터변수명;
→ Heap에 동적할당 된 메모리 해제
메모리를 설정하고 해제하는 방법은 위와 같고, 동적으로 생성한 객체에 접근하는 방법은 C와 동일하다.
type* 포인터변수명 = new type [크기];
→ 배열 형태의 메모리 할당 방법
delete[ ] 포인터변수명;
→ 배열 형태의 동적할당 메모리 해제
[이미지 출처] https://open4tech.com/memory-layout-embedded-c-programs/
[내용 참조] https://modoocode.com/141