전체 글(29)
-
The Complete Guide to return x; - Implicit Move
이 포스트는 [The Complete Guide to return x]를 기반으로 재구성 되었습니다. ㅡ 이전 포스트에서 return x; 명령어가 실행될 때 발생하는 컴파일러 최적화인 Copy Elision 및 NRVO에 대해 알아보았다. 이 포스트에서는 C++의 버전이 올라감에 따라 NRVO에 생긴 문제를 알아보고, 이 문제를 해결하기 위해 변경된 표준에 대해 알아볼 것이다. C++11 C++11에 Move Semantics이 도입된 후로 NRVO에 문제가 생겼다. 다음 코드를 보자. unique_ptr f() { unique_ptr x = ~~~; return x; } f 함수는 로컬 변수 x의 할당을 제어할 수 있고, return expression은 변수 x의 이름인 id-expression 이..
2022.03.13 -
The Complete Guide to return x; - Copy Elision & NRVO
이 포스트는 [The Complete Guide to return x]를 기반으로 재구성 되었습니다. ㅡ 이 포스트에서는 return x; 명령어가 실행될 때 발생할 수 있는 컴파일러 최적화인 Copy Elision 및 NRVO에 대해 알아 볼 것이다. int f() { int i = 42; return i; } void test() { int j = f(); } 위 코드를 보면 test 함수에서 f 함수를 호출하여 반환 값을 통해 int j 변수를 초기화 하고 있다. x86-64 호출규약(calling convention)에서는 함수 f에서 return i; 명령어가 실행되면 함수 f의 스택 프레임에 있는 변수 i의 값을 함수 test의 스택 프레임에 있는 변수 j로 전달하기 위해 eax 레지스터에 변..
2022.03.12 -
Placement New로 인해 발생할 수 있는 Undefined Behavior 및 솔루션
C/C++에서는 갖가지 상황에서 Undefined Behavior가 발생할 수 있다. 그 중 대다수에 포인터가 연관되어 있는데, 오늘은 특정 상황에서 Undefined Behavior를 우회하는 방법을 살펴 볼 것이다. 1. Reusing dynamic storage occupied by a const complete object 상수로 선언된 변수의 값을 수정할 수 없다는 것은 알고 있을 것이다. 하지만, 놀랍게도 다음과 같이 placement new를 통해 상수의 값을 강제로 덮어씌울 수 있다. struct X { int n; }; const X *p = new const X{3}; const int a = p->n; new (const_cast(p)) const X{5}; const int b = ..
2022.03.09 -
Placement New를 통해 Trivial 객체를 올바르게 복사하는 방법
[원문 링크] 위 링크에서는 다음 질문을 하고있다. T가 trivial 타입이면 memcpy를 통해 오브젝트를 복사할 수 있으며, trivial 타입의 default initialization은 오브젝트의 representation을 변경하지 않으므로, 아래의 코드는 복사된 오브젝트를 올바르게 초기화 하는가? template T* copy_trivial(T orig) requires std::is_trivial_v { void* buf = std::aligned_alloc(alignof(T), sizeof(T)); // Note the order of these statements std::memcpy(buf, &orig, sizeof(T)); return new(buf) T; } 위 코드는 다음과 같이..
2022.03.03 -
Compiler Explorer를 통한 어셈블리 분석
이 포스트에서는 간단한 예제로 Compiler Explorer에서 생성된 어셈블리를 분석해본다. 생성되는 어셈블리는 각 컴파일러 마다 다르며, 함수로 인자가 넘겨지는 방식이나 스택에 메모리가 얼마나 할당되는지 등은 함수 호출규약(Calling Convention)에 따라 다르므로, 여기서는 생성된 어셈블리에서의 각 숫자에 의미를 부여하는 것 보다는 어셈블리가 어떻게 구성되는지를 보도록 한다. 이 포스트에서 컴파일러는 clang(x86-64)을 사용하며, 예제로 사용할 소스코드는 다음과 같다. 먼저 main() 함수의 어셈블리를 보자. 빨간색으로 표시된 영역(19~21)을 함수의 프롤로그(prologue)라고 하며, 함수를 시작하기에 앞서 필요한 레지스터와 로컬 변수들 사용을 위해 필요한 스택 영역을 설정..
2022.03.01