본글은 최근에 Pwnable 문제중 C언어, Python뿐만 아니라 C++로 이루이진 문제를 접하는 경우가 많아져서 Python, Java와 다른 C++만의 문법이나 차이점등을 정리하기 위해 작성한 글입니다.
잘못된 부분이 있다면 댓글로 알려주시기 바랍니다.
(예시 코드등은 TCP 스쿨의 자료를 참고했습니다.)
https://www.tcpschool.com/cpp/intro
1. 참조자(Reference)
C++에서는 특정 변수의 실제 이름 대신 사용할 수 있는 참조자라는 기능이 있습니다. 참조자는 크기가 같은 큰 구조체와 같은 데이터를 함수의 인수로 전달하거나 클래스 설계할때 자주 사용됩니다.
문법은 아래와 같습니다.
int 변수이름; // 변수의 선언
int& 참조자이름 = 변수이름; // 참조자 선언
이때 &연산자는 타입을 식별하기 위해 사용됩니다. 이렇게 선언된 참조자는 변수와 같은 메모리 위치를 참조하게 되어 증가 연산을 수행하는 경우 참조 변수만 변경 되는 것이 아닌 대상 변수도 함께 변경되는 것을 확인 할 수 있습니다.
int x = 10; // 변수의 선언
int& y = x; // 참조자 선언
cout << "x : " << x << ", y : " << y << endl;
y++; // 참조자를 이용한 증가 연산
cout << "x : " << x << ", y : " << y << endl;
cout << "x의 주소값 : " << &x << ", y의 주소값 : " << &y;
//실행 결과
x : 10, y : 10
x : 11, y : 11
x의 주소값 : 0x7ffe6c0a1234, y의 주소값 : 0x7ffe6c0a1234
이러한 참조자가 필요한 이유는 값을 인자로 전달하는 함수 호출방식 Call-by-value, 주소 값을 인자로 전달하는 함수의 호출방식 Call-by-reference를 비교해보면 쉽게 이해할 수 있습니다.
값을 인자로 전달하는 Call-by-value의 경우 외부의 값을 변경시킬수 없습니다.
int main(void)
{
int num1 = 3, num2 = 7;
cout << num1 << "," << num2 << endl;
Swap(num1, num2);
cout << num1 << "," << num2 << endl;
return 0;
}
void Swap(int x, int y) //외부 값 변경 불가
{
int temp = x;
x = y;
y = temp;
}
// 실행 결과
3,7
3,7
하지만 주소 값을 인자로 전달하는 Call-by-reference의 경우는 외부 값을 변경할 수 있게 됩니다.
int main(void)
{
int num1 = 3, num2 = 7;
cout << num1 << "," << num2 << endl;
Swap(num1, num2);
cout << num1 << "," << num2 << endl;
return 0;
}
void Swap(int &x, int &y) // 주소연산자 & 추가 외부 값 변경
{
int temp = x;
x = y;
y = temp;
}
// 실행 결과
3,7
7,3
두개의 차이를 알기 위해 메모리 측면에서 보면 main 코드는 먼저 num1과 num2 메모리 공간을 할당합니다. 그 위치에 각각 3, 7을 대입하고 Swap이라는 함수의 x, y라는 변수에 대한 메모리 공간을 할당한뒤 그 안에 값을 대입합니다. 이때, Call-by-value의 경우는 int x = num1, int y = num2라는 식과 동일한 동작을 하게 됩니다. 새로 할당된 x 와 y는 Swap함수 내에서 교환이 이루어지고 Swap함수가 종료되면 Swap의 지역변수이기 때문에 메모리 값을 반환하고 사라지게 됩니다.
이로인해 num1과 num2와는 다른 메모리 공간을 가지고 있는 x, y 이기 때문에 num1과 num2 값의 변화가 생기지 않습니다.
반대로 Call-by- reference는 int &x = num1, int &y = num2로 새로운 이름을 지어주는 것일뿐 num1과 num2의 동일한 메모리 주소를 가지고 있게 됩니다. 이로인해 x와 y의 Swap이 이루어지고 Swap이 종료되어 x,y의 이름이 사라지더라도 num1, num2의 값을 변경하는 결과를 낼 수 있게 됩니다.
C++에서는 이러한 참조자를 구조체와 같은 사용자 정의 타입을 다룰 때 매우 유용하게 사용하고 있습니다.
struct Book
{
string title;
string author;
int price;
};
void Display(const Book&);
int main(void)
{
Book web_book = {"HTML과 CSS", "홍길동", 28000};
Display(web_book);
return 0;
}
void Display(const Book& bk)
{
cout << "책의 제목은 " << bk.title << "이고, ";
cout << "저자는 " << bk.author << "이며, ";
cout << "가격은 " << bk.price << "원입니다.";
}
사용자가 정의한 Book이라는 구조체를 Display에 참조자 형태로 넣어 구조체에 있는 값들을 변경하여 사용하는 구조로 사용할 수 있습니다.
2. 소멸자(destructor)
C++ 에서 생성자가 객체 멤버의 초기화와 객체 사용을 위한 외부 환경까지 초기화하는 역할을 합니다.
그러면 반대로 객체의 수명이 끝나면 반대 역할을 수행할 멤버 함수도 필요하게 되는데 이러한 역을 하는 멤버 함수를 소멸자(destructor)라고 합니다. 소멸자는 객체의 수명이 끝나면 컴파일러에 의해 자동으로 호출되어 사용이 끝난 객체를 정리해줍니다.
소멸자의 특징으로는 첫째, 소멸자는 인수를 가지지 않습니다. 둘째, 소멸자는 반환값이 없지만 void형으로 선언하지 않습니다 셋째, 객체는 여러 개의 생성자를 가질수 있으나 소멸자는 단 하나만 가질 수 있습니다 넷째, 소멸자는 const, volatile 또는 static로 선언될 수 없지만, const, volatile도는 static로 선언된 객체의 소멸을 위해서 호출될 수 는 있습니다.
소멸자의 문법은 "~클래스명" 을 사용하며 다음과 같습니다.
~Book(); // 소멸자 원형
Book::~Book() { } // 소멸자 선언
//예제
Book::~Book()
{
// 프로그램이 종료될 때 컴파일러에 의해 자동으로 호출됨.
cout << "Book 객체의 소멸자가 호출되었습니다." << endl;
}
//실행결과
0
Book 객체의 소멸자가 호출되었습니다.
C++에서 소멸자는 컴파일러가 알아서 처리하게 되는데 해당 소멸자가 호출되는 시기가 메모리의 영역별로 다르게 호출됩니다.
메모리 영역 | 소멸자 호출 시기 |
데이터 영역 | 해당 프로그램이 종료될 때 |
스택 영역 | 해당 객체가 정의된 블록을 벗어날 때 |
힙 영역 | delete를 사용하여 해당 객체의 메모리를 반환할 때 |
임시 객체 | 임시 객체 사용을 마쳤을 때 |
다른 영역과 다르게 heap 영역은 delete를 사용해야만 소멸자가 호출되고 정리되기 때문에 heap영역을 할당했다면 해제를 잊지 않도록 주의해야 합니다.
Reference
1.https://www.tcpschool.com/cpp/cpp_cppFunction_reference
'Reference > Language_Study' 카테고리의 다른 글
[C++] 6. C++ STL(Standard Template Library) (0) | 2024.03.27 |
---|---|
[C++] 5. C++ 프렌드(friend), 스마트 포인터(smart pointer) (0) | 2024.03.25 |
[C++] 3. C++ new()와 delete() (0) | 2024.03.22 |
[C++] 2. C++ typeid, endl 과 \n 차이 (0) | 2024.03.19 |
[C++] 1. C++ nullptr, namespace (0) | 2024.03.18 |