개요
- constexpr 의 작동방식을 정확히 파악해보고자 함
- 컴파일된 어셈블리어를 뜯어보고 확실하게 알고자 함
- Microsoft Visual Studio 2017
- 목적 : 문자열 길이를 저장하고자 하다가 생긴 문제를 해결
- 엔진 초기화가 되지 않은 상태에서
- 파일 범위의 변수에(std::string) 값을 넣다가 메모리를 건드림
- const char* 혹은 char[] 로 저장하고 compile-time 에 길이를 넣고싶음
constexpr 함수
- constexpr 함수가 compile-time 에 실행되는 케이스
- constexpr 변수에 값을 넣을 때
- template 에서 값을 필요로 할 때
- 그냥 실행하면 일반함수처럼 run-time 에 실행된다.
int constexpr C_length(const char* str)
{
return *str ? 1 + C_length(str + 1) : 0;
}
// compile-time 이든 run-time 이든 속도는 이게 더 빠를 듯?
// 하지만 const char* 와 char[] 가 다름에 주의하자
template <int N>
constexpr int T_length(char const (&)[N])
{
return N - 1;
}
template<int N>
constexpr int twice()
{
return N * 2;
}
void main()
{
// 그냥 함수호출하면 run-time 호출을 한다.
test(C_length("asdf"));
// push offset string "asdf"
// call C_length
// add esp, 4
// push eax
// call test
// add esp, 4
test(T_length("asdf"));
// push offset string "asdf"
// call T_length<5>
// add esp, 4
// push eax
// call test
// add esp, 4
// compile-time case 1 : constexpr 변수에 값을 넣을 때
constexpr int a0 = C_length("asdf");
// mov dword ptr [a0],4
constexpr int a1 = T_length("asdf");
// mov dword ptr [a1],4
test(a0);
test(a1);
// push 4
// call test
// add esp, 4
// const 변수에 넣으면 어떻게 될까?
// 이상한 최적화??? 를 수행하여
// 변수선언 시에는 run-time 에 함수호출
// 변수사용 시에는 compile-time 에 치환
const int a2 = C_length("asdf");
// push offset string "asdf"
// call C_length
// add esp, 4
// mov dword ptr[a2], eax
const int a3 = T_length("asdf");
// push offset string "asdf"
// call T_length<5>
// add esp, 4
// mov dword ptr[a3], eax
test(a2);
test(a3);
// push 4
// call test
// add esp, 4
// compile-time case 2 : template 에서 사용될 때
twice<C_length("asdf")>();
// call twice<4>
constexpr int a4 = twice<C_length("asdf")>();
// mov dword ptr[a4], 8
// const 는 위에서 봤던 것처럼 된다.
const int a5 = twice<C_length("asdf")>();
// call twice<4>
// mov dword ptr[a5], eax
test(a4);
test(a5);
// push 8
// call test
// add esp, 4
}
- 연쇄적으로 호출해 보면 어떻게 될까?
- 그냥 하면 무식하게 run-time 함수콜 3번
- constexpr 변수에 넣으면 compile-time 에 넣음
void main()
{
test(test(C_length("asdf")));
// push offset string "asdf"
// call C_length
// add esp, 4
// push eax
// call test
// add esp, 4
// push eax
// call test
// add esp, 4
constexpr int ret1 = C_length("asdf");
// mov dword ptr[ret1], 4
constexpr int ret2 = test(ret1);
// mov dword ptr[ret2], 5
constexpr int ret3 = test(ret2);
// mov dword ptr[ret3], 6
// !!! 하악 !!!
constexpr int ret4 = test(test(C_length("asdf")));
// mov dword ptr[ret4], 6
// template 에서 필요로 하는 부분까지만 compile-time 에 계산하고
// 정작 template 함수 자체는 run-time 에 ㅋㅋ
twice<test(C_length("asdf"))>();
// call twice<5>
}
멤버변수를 constexpr 로 선언
- constexpr 은 static 에서만 사용 가능
- cosntexpr 선언시 바로 값을 넣어줘야 함. compile-time 이니까
- 선언시 초기값을 넣어준 static const 는 static constexpr 과 동작이 같다.
struct Value
{
const int m_value;
const int m_value2;
const int m_value3;
const int m_value4 = 256;
//constexpr int x_value = 1024; ERROR, static 아님
//static constexpr int sce_value; ERROR, 초기값 없음
static constexpr int sce_value = C_length("asdf") * 25; // 100
static const int sc_value = C_length("asdf") * 50; // 200
static const int sc_value2;
};
//constexpr int Value::sce_value = 100; ERROR, 선언에 있어야 함
// run-time CRT 초기화
const int Value::sc_value2 = C_length("asdf") * 50; // 200
// push offset string "asdf"
// call C_length
// add esp, 4
// imul eax, eax, 32h
// mov dword ptr[Value::sc_value2], eax
void main()
{
// compile-time
constexpr Value v{ C_length("as"), T_length("asd") };
// mov dword ptr[v], 2
// mov dword ptr[ebp-10h], 3
// mov dword ptr[ebp-0Ch], 0
// mov dword ptr[ebp-8], 100h
// 멤버변수는 값을 메모리에서 가져옴
test(v.m_value);
// mov eax, dword ptr[v]
// push eax
// call test
// add esp, 4
test(v.m_value2);
// mov eax, dword ptr[ebp-10h]
// push eax
// call test
// add esp, 4
// 기본타입을 constexpr 로 선언하면 변수선언과 사용 둘다 compile-time 에 치환
constexpr int value = v.m_value;
// mov dword ptr[value], 2
test(value);
// push 2
// call test
// add esp, 4
// static constexpr 변수 는 compile-time 에 치환
test(v.sce_value);
test(Value::sce_value);
// push 64h
// call test
// add esp, 4
// static const 변수도 compile-time 치환이 잘 된다.
test(v.sc_value);
test(v.sc_value2);
test(Value::sc_value);
test(Value::sc_value2);
// push 0C8h
// call test
// add esp, 4
}
실전투입
- static 변수로 하려면 Print 함수 안에서 구별할 수가 없네?
- template 으로 하면 너무 많이 생기는데… 싫다.
- 상속받게 만들면?
- virutal 로 인한 run-time 오버헤드
- 게다가 쓸데없는 객체생성비용 발생
struct Base
{
virtual int getLength() = 0;
};
struct AttrID : Base
{
virtual int getLength() override { return size; }
constexpr static const char* str = "id";
constexpr static int size = 2;
};
struct AttrName
{
constexpr static const char* str = "name";
constexpr static int size = C_length("name");
};
struct AttrRequest
{
constexpr static const char* str = "request";
constexpr static int size = C_length(str);
};
void PrintAll()
{
cout << AttrID::str << endl;
cout << AttrID::size << endl;
cout << AttrName::str << endl;
cout << AttrName::size << endl;
cout << AttrRequest::str << endl;
cout << AttrRequest::size << endl;
}
template <typename T>
void PrintTemplate()
{
cout << T::str << endl;
cout << T::size << endl;
}
template <typename T>
void PrintParameter(const T& t)
{
cout << T::str << endl;
cout << T::size << endl;
}
template <typename T>
void PrintParameter2(T&& t)
{
cout << T::str << endl;
cout << T::size << endl;
}
void PrintBase(Base* pB)
{
int x = pB->getLength();
}
void PrintDouble(const char* str, const int size)
{
cout << str << endl;
cout << size << endl;
}
void main()
{
PrintAll();
PrintTemplate<AttrID>();
PrintParameter(AttrName());
PrintParameter2(AttrRequest());
AttrID d;
PrintBase(&d);
PrintDouble(AttrID::str, AttrID::size);
PrintDouble(AttrName::str, AttrName::size);
PrintDouble(AttrRequest::str, AttrRequest::size);
}
- PrintAttr 에 파라미터 한개만 전달하고 싶음
struct AttrValue
{
const char* str;
int size;
};
AttrValue ID = { "id", T_length("id") };
AttrValue NAME { "name", C_length("name") };
AttrValue REQUEST { "request", T_length("request") };
void PrintAttr(const AttrValue& av)
{
cout << av.str << endl;
cout << av.size << endl;
}
void main()
{
PrintAttr(ID);
PrintAttr(NAME);
PrintAttr(REQUEST);
}
- 최종결론?
- PrintAttr 방식을 생각했었는데
- PrintDouble 이 걍 나은 듯 하기도 하고 고민중-_-