본글은 최근에 Pwnable 문제중 C언어, Python뿐만 아니라 C++로 이루이진 문제를 접하는 경우가 많아져서 Python, Java와 다른 C++만의 문법이나 차이점등을 정리하기 위해 작성한 글입니다.
잘못된 부분이 있다면 댓글로 알려주시기 바랍니다.
(예시 코드등은 TCP 스쿨의 자료를 참고했습니다.)
https://www.tcpschool.com/cpp/intro
1. 프렌드(friend)
객체의 private 멤버는 해당 객체의 public 멤버 함수로만 접근이 가능합니다. 하지만 경우에 따라서는 해당 객체의 멤버 함수가 아닌 함수도 private멤버에 접근해야만 할 경우가 발생합니다. 이런 경우 private 멤버에 접근하기 위해 public 멤버를 새롭게 추가하는 것으로 해결할 수 있는데 이는 매우 비효울적입니다. C++ 에서는 이러한 경우 비효율적인 부분을 해결하고자 프렌드(friend)라는 접근 제어 키워드가 추가되었습니다.
프렌드는 지정한 대상에 한해 해당 객체의 모든 멤버에 접근 할 수 있는 권한을 부여해줍니다. friend 키워드는 전역함수, 클래스, 멤버 함수 세가지 형태로 사용이 가능하며 다음과 같이 사용합니다.
friend 클래스이름 함수이름(매개변수목록);
// 예시
class A {
friend class B;
...
}
friend 키워드는 함수 원형에서만 사용해야하며 함수의 정의에서는 사용하지 않습니다. 하지만, friend 선언은 예시처럼 위치하는 영역은 private 여도, public내에 있어도 위치에 따른 차이는 전혀 없습니다.
다음으로 private에 있는 값에 접근하는 예시 코드를 보면
#include <iostream>
using namespace std;
class B;
class A{
private:
int adata;
public:
A(int _data)
{
adata = _data;
}
void ShowData(B b);
friend class B;
};
class B{
private:
int bdata;
public:
B(int _data)
{
bdata = _data;
}
void ShowData(A a)
{
cout << "a.data: " << a.adata << endl;
}
friend class A;
};
void A::ShowData(B b)
{
cout << "b.data: " << b.bdata << endl;
}
int main()
{
A a(10);
B b(20);
a.ShowData(b);
b.ShowData(a);
return 0;
}
// 실행결과
b.data:20
a.data:10
A클래스와 B클래스 public 마지막 하단에 friend를 선언한 상황입니다. 이로인해 class A에서 class B의 멤버로 class B에서 class A의 멤버로 접근이 가능하게 됩니다. 이러한 구조로 a.ShowData로 B클래스의 멤버에 들어있는 값 20을, b.ShowData로 A클래스 멤버에 들어있을 값 10을 출력하게 됩니다.
friend 선언은 이렇듯 기본적으로 접근하지 못하도록 private 지정해놓은 멤버에 접근할 수 있는 키워드이기 때문에 많이 사용하게 되면 공격자에게 매우 좋은 타겟이 될 수 있습니다. 그러므로 사용하기 위해서는 많은 사항을 고려한 후 사용하거나 자제해야 합니다.
friend라는 키워드를 처음 알게 되었는데 취약점을 분석할때 매우 취약한 타겟이라는 생각이 들고 다른 공격과 함께 연계가 될것 같다는 예상이 되어 friend에 대해서는 관련 워게임 CTF등을 접할경우 함께 더 자세하게 정리하도록 하겠습니다.
2. 스마트 포인터
이전에 C++ 에서 malloc, calloc대신 사용하는 키워드로 new를 정리한적이 있습니다. 당시 new 키워드를 사용하여 할당받은 메모리는 해제하기 위해서는 delete를 사용해 메모리를 해제해야 한다고 했습니다.
하지만, 개발자의 실수로 해제하지 않는 경우가 있을 수 있어 메모리 누수로부터 프로그램의 안전성을 보장하기 위해 C++11부터는 사용이 끝난 메모리를 자동으로 해제해주는 스마트 포인터를 제공하고 있습니다
스마트 포인터는 new 키워드를 사용해 기본 포인터가 실제 메모리를 가르키도록 초기화한 후 기본포인터를 스마트 포인터에 대입하여 사용합니다. 이렇게 정의된 스마트 포인터의 수명이 다하면 소멸자는 delete를 사용하여 할당된 메모리를 자동으로 해제하는 구조로 따로 메모리 해제할 필요가 없어집니다.
스마트 포인터의 종류로는 unique_ptr, shared_ptr, weak_ptr이 있습니다.
1) unique_ptr
unique_ptr은 하나의 스마트 포인터만이 특정 개체를 소유할 수 있도록 객체에 소유권 개념을 추가한 스마트 포인터입니다. 이 스마트 포인터는 객체의 소유권을 가지고 있을 때만, 소멸자가 해당 객체를 삭제 할 수 있도록 합니다.
이때, unique_ptr 인스턴스는 move() 멤버 함수로 소유권 이전은 가능하지만, 복사할 수 는 없고 소유권이 이전되면 객체를 소유하지 않도록 재설정 됩니다.
기본 구조는 다음과 같습니다.
unique_ptr<int> ptr01(new int(5)); // int형 unique_ptr인 ptr01을 선언하고 초기화함.
auto ptr02 = move(ptr01); // ptr01에서 ptr02로 소유권을 이전함.
// unique_ptr<int> ptr03 = ptr01; // 대입 연산자를 이용한 복사는 오류를 발생시킴.
#include <iostream>
#include <memory>
using namespace std;
class Person
{
private:
string name_;
int age_;
public:
Person(const string& name, int age); // 기초 클래스 생성자의 선언
~Person() { cout << "소멸자가 호출되었습니다." << endl; }
void ShowPersonInfo();
};
int main(void)
{
unique_ptr<Person> hong = make_unique<Person>("길동", 29);
hong->ShowPersonInfo();
return 0;
}
Person::Person(const string& name, int age) // 기초 클래스 생성자의 정의
{
name_ = name;
age_ = age;
cout << "생성자가 호출되었습니다." << endl;
}
void Person::ShowPersonInfo() { cout << name_ << "의 나이는 " << age_ << "살입니다." << endl; }
//실행 결과
생성자가 호출되었습니다.
길동의 나이는 29살입니다.
소멸자가 호출되었습니다.
예시 코드를 보면 Person 객체를 가르키는 unique_ptr를 make_unique로 생성하고 있는데 이는 C++14부터 추가된 것으로 unique_ptr 인스턴스를 안전하게 생성할 수 있는 것으로 인자로 전달받은 타입의 객체를 생성하고 생성된 객체를 가르키는
unique_ptr을 반환하는 함수입니다.
위 처럼 Person 객체를 가르키는 unique_ptr 인스턴스 hong은 일반 포인터와 달리 사용이 종료된 후에 delete 키워드를 사용해 메모리를 해제할 필요가 없어집니다.
2) shared_ptr
shared_ptr은 하나의 특정 객체를 참조하는 스마트 포인터가 몇개인지를 참조하는 스마트 포인터입니다.
참조하고 있는 스마트 포인터의 갯수를 참조횟수라고 하는데, 참조횟수는 객체에 새로운 shared_ptr이 추가될때마다 1씩 증가하고 수명이 다할때 1씩 감소합니다. 그러므로 참조 횟수가 0이되면 delete를 사용해 자동으로 메모리를 해제 합니다.
shared_ptr<Person> hong = make_shared<Person>("길동", 29);
cout << "현재 소유자 수 : " << hong.use_count() << endl; // 1
auto han = hong;
cout << "현재 소유자 수 : " << hong.use_count() << endl; // 2
han.reset(); // shared_ptr인 han을 해제함.
cout << "현재 소유자 수 : " << hong.use_count() << endl; // 1
예시 코드를 보면 make_shared()함수를 통해 hong 을 생성하고 있습니다. 생성한 당시에는 소유자가 1이지만, han을 통해 참조하게 되면 소유자 수가 2로 늘어나는 것을 확인 할 수 있습니다. 참조한 han을 해제하게 되면 소유자 수가 줄어드는 것을 확인 할 수 있습니다.
3) weak_ptr
weak_ptr은 shared_ptr 인스턴스가 소유하는 객체에 대한 접근만 제공하고 소유자의 수에는 포함되지 않는 스마트 포인터입니다. 만약 shared_ptr 포인터끼리 서로 상대방을 가르키게되는 상황이 된다면 참조 횟수가 절대 0이되지 않고 해당 메모리는 영원히 해제가 되지 않습니다. 이러한 상황을 순환 참조라고 하는데 이렇게 되면 메모리 누수 방지를 위해 추가한 스마트 포인터가 메모리 누수의 원인이 되는 아이러니한 상황이 됩니다.
해서 이러한 경우 순환 참조를 방지하기 위해 사용되는 스마트 포인터로 참조 카운트에 영향을 주지 않는 포인터가 필요할때 사용됩니다.
Reference
1. https://www.tcpschool.com/cpp/cpp_template_smartPointer
'Reference > Language_Study' 카테고리의 다른 글
아스키 코드 표 (0) | 2024.05.27 |
---|---|
[C++] 6. C++ STL(Standard Template Library) (0) | 2024.03.27 |
[C++] 4. C++ 참조자(reference), 소멸자(destructor) (2) | 2024.03.25 |
[C++] 3. C++ new()와 delete() (0) | 2024.03.22 |
[C++] 2. C++ typeid, endl 과 \n 차이 (0) | 2024.03.19 |