본글은 최근에 Pwnable 문제중 C언어, Python뿐만 아니라
C++로 이루이진 문제를 접하는 경우가 많아져서 Python, Java와 다른 C++만의 문법이나
차이점등을 정리하기 위해 작성한 글입니다.
틀린부분이 있다면 댓글로 알려주시기 바랍니다.
(예시 코드등은 TCP 스쿨의 자료를 참고했습니다.)
https://www.tcpschool.com/cpp/intro
C++ type 확인
C++ 에서도 Java나 Python 처럼 객체의 타입에 관한 정보를 확인 해야 할 때가 있다.
이때 C++에서 사용되는 문법이 typeid 이다.
typeid(표현식)
이 typeid는 다른 문법에서 처럼 타입을 확인할 수 있게 해주는 기능만 있는 것이 아닌
name 이라는 멤버 함수를 추가로 확인이 가능한데
이는 클래스의 이름을 문자열로 치환해 반환해준다.
(단, 컴파일러 제작사마다 다르기때문에 동일한 문자열을 반환할것이라고 추측해서는 안됨)
예시 코드를 보면
#include <iostream>
using namespace std;
int main(void)
{
cout << typeid(int).name() << endl; // 기본 자료형
cout << typeid(type_info).name() << endl; // 클래스
cout << typeid(main).name() << endl; // 함수
cout << typeid(cout).name() << endl; // 객체
}
// return 결과
int
class type_info
int __cdecl(void)
class std::basic_ostream<char,struct std::char_traits >
int, type_info, main, cout의 name을 확인하는데
int의 경우에는 기본 자료형이기때문에 int를
클래스의 경우 해당 클래스의 이름과 class라는 타입을
cout는 헤더파일 iostream의 정보를 가져오는 것과 class타입임을 확인 할 수 있다.
이러한 정보는 코드를 작성하다 형변환이 필요할때 해당 클래스가 맞는지 확인하기
편리하고 부모자식간의 형변환이 가능하기에 해당 정보가 필요하다.
class A {
public:
virtual void vf() {}
};
class B : public A {
public:
bool b;
};
class C : public A {
public:
int c;
};
void memberPrint(A* a)
{
if (typeid(B) == typeid(*a)) { //B 클래스인지 검사
B* b = (B*)a; //자식클래스로 강제 형변환 이지만 안전이 보장됨
cout << typeid(b->b).name() << endl;
}
else if (typeid(C) == typeid(*a)) { //C 클래스인지 검사
C* b = (C*)a; //자식클래스로 강제 형변환 이지만 안전이 보장됨
cout << typeid(b->c).name() << endl;
}
}
int main() {
A* a[] = { &B(), &C() };
memberPrint(a[0]);
memberPrint(a[1]);
}
위의 코드처럼 typeid를 조건문으로 넣어 해당 클래스를 확인후
형변환을 하는 코드를 구성할 수 있어 type을 확인할 수 있는 typeid()는 유용하게 사용할 수 있다.
C++ endl 과 \n 차이
C나 Python에서는 \n 은 줄바꿈으로써 print(), printf()등에서 줄을 바꿀때 사용한다.
C++에서 또한 \n을 사용할 수 있지만,
대부분의 C++ 코드에서 문자열의 끝에는 std::endl을 사용하는 것이 보편적이다.
C에서 이미 익숙하고 python으로 까지 포괄적으로 사용되는 \n 대신
왜 endl을 사용하는지 이유를 알아보았다.
한마디로 정리하면 입출력버퍼 때문에 endl을 사용한다.
위의 이미지를 보면 버퍼를 사용하는 입력의 경우 입력값을 버퍼에 쌓아두고
이를 한번에 전송한다.
이때 전송하는 조건을 보면 개행문자가 나타나거나, 버퍼가 가득차는 경우이다.
C++ 에서는 이 개행문자의 역할을 endl이 하는 것이다.
(이외에도 fflush()를 사용하는 경우가 있는데
pwnable CTF 문제에서 해당 함수에 관한 취약점 문제를 풀어본적이 있다.)
endl을 사용하는 경우와 사용하지 않는 경우 코드를 사용하면 이해가 훨씬 간단하다.
int main(void)
{
std::cout << "hello" << std::endl;
sleep(5);
return (0);
}
int main(void)
{
std::cout << "hello";
sleep(5);
return (0);
}
두 코드를 놓고 개발자의 입장에서 생각해보면
개발자가 원하는 코드의 흐름은
hello를 출력한뒤 5초를 기다리는 것이다.
첫번째 코드인 endl을 사용하는 경우에는 hello가 출력한뒤 5초후 종료되어 정상적으로 작동한다.
반면에 endl을 사용하지 않은 코드의 경우 5초가 흐른뒤 hello를 출력하고 종료된다.
이는 hello문자열이 버퍼에 들어갔으나 개행문자가 없었고,
버퍼또한 꽉차지 않았기 때문에 발생하는 문제이다.
\n가 hello 중간에 들어가도 마찬가지 이다.
\n은 개행문자가 아닌 줄바꿈의 역할을 하는 문자이기에
버퍼를 비우지 않는다.
그러나 버퍼를 비우는 것이 해당 문자열의 끝으로 보고 줄바꿈이 진행되기에
endl을 줄바꿈으로 사용할 수 있다고도 한다.
하지만, 백준등에서 타임아웃등의 문제로 빠른 처리를 요구하는 경우에는 버퍼를
비우지 않고 줄바꿈을 진행하는 \n이 훨씬 빠르기 때문에
즉시 출력해야하는 경우가 아니라면 \n을 사용하는 것으로 시간을 줄이는 것이 가능하다.
정리하면, 두개 모두 각 환경에 따라 필요할 수가 있으니
두 개의 차이점을 숙지하고 구분해 사용할 수 있어야 한다.
Reference
1. https://velog.io/@kdi2514/Modern-c-typeid
'Reference > Language_Study' 카테고리의 다른 글
[C++] 4. C++ 참조자(reference), 소멸자(destructor) (2) | 2024.03.25 |
---|---|
[C++] 3. C++ new()와 delete() (0) | 2024.03.22 |
[C++] 1. C++ nullptr, namespace (0) | 2024.03.18 |
명품 JAVA 에센셜 6장 실습문제 (0) | 2022.06.14 |
상속 문제(Unit) (0) | 2022.06.07 |