LLVM이란?

LLVM/주절주절 2019. 9. 3. 01:52
LLVM은 오픈소스 컴파일러 개발용 종합 패키지다.

GCC 등의 기존 컴파일러들은 컴파일 과정들인, 코드의 파싱이나 파스트리, 기계어 생성, 코드 최적화 등이 명확한 구분 없이 모놀리하게 설계되어있다.
이 말은 뭐냐하면, 유지보수하기가 더럽다는 말이다.
게다가 특정 과정 등은 독립적으로 관리해야 더 효율적으로 작동할 수 있는데... 어휘분석과 구문분석 등이 이에 속한다.

여튼 LLVM은 이러한 고질적인 문제들을 해결하기 위해서 탄생했다.
LLVM은 C++로 작성되어 유지보수가 보다 편리하고, 컴파일 과정들을 각각 독립적인 모듈로 제공한다.
그래서 이전에는 컴파일러를 제작하려면 각각의 플랫폼에 맞는 기계어로 치환하도록 일일이 손을 댔어야 했는데 LLVM은 기계어 생성이나 코드 최적화 등의 작업을 알아서 잘 처리해준다.

이런 까닭에 근래에 개발된 컴파일러들은 다수가 이 LLVM으로 제작되었다. Rust, Swift 등등..
게다가 실제 성능도 아주 빼어난 편이다.
LLVM으로 작성된 C/C++ 컴파일러인 Clang은 GCC와 동등하거나, 때로는 앞서는 실행/컴파일 성능을 보여준다고 한다. Rust의 컴파일러도 대체로 C/C++와 동등한 성능을 보인다.

그리고 이름의 뜻은 저수준 가상 머신(Low Level Virtual Machine)인데 특별한 뜻이 있는 건 아니다.
의미부여하지 말라고 공식문서에서도 말한다.


Reference
"LLVM, [위키백과]"
"메이유르 판디&슈오르 사르다 『LLVM Cookbook』, PACKT"

'LLVM > 주절주절' 카테고리의 다른 글

[LLVM-IR] Hello World 찍기  (0) 2019.09.03

설정

트랙백

댓글

[LLVM-IR] Hello World 찍기

LLVM/주절주절 2019. 9. 3. 01:51
일단 세팅을 먼저 해야 한다.
http://releases.llvm.org/download.html

여기서

32비트나 64비트로 인스톨러를 깔아서 실행- 예스맨이 되면 된다.

그럼 해당 비트의 프로그램 파일즈 디렉터리에 LLVM 항목이 생길 것이다.
거기 안에 보면 또 bin 디렉터리가 있는데. 여기에 주요 실행 파일들이 몰려있다.

그리고 저 clang이란 놈이 바로 c/c++ 컴파일러이자 llvm-ir 컴파일러이기도 한 놈이다.
앞으로 우린 저걸 쓸 것이다.

그리고 편하게 쓰기 위해서라도 이 경로를 패스에 저장해주면 된다.
환경변수 설정은 이 글을 볼 정도의 개발자라면 다 알 것이므로 방법은 생략한다.

그리고 c/c++ 라이브러리를 패스에 추가해줘야 한다.
콘솔 입출력 같은 플랫폼 종속적인 행위를 하려면 c/c++의 모듈을 쓸수밖에 없다..

그런데 윈도에서는 llvm에서 제공하는 라이브러리나 gcc 라이브러리는 뭔가 잘 돌아가질 않아서,
msvc의 라이브러리 디렉터리를 써야 한다.
라이브러리의 경로는 대충 이렇다. 더럽게 복잡하다.

저걸 따로 정리해서 include의 경로는 INCLUDE라는 이름의 환경 변수에 넣고,
lib 디렉터리 안에 있는 lib파일들도 따로 모아놓고, 그 경로를 LIB라는 이름의 환경 변수에 넣어주면,
clang이 컴파일할때 갖다 쓴다.


자 그럼 이제 코드를 돌려보자.
코드는 이렇다. 어셈과 유사한 부분이 많다.

ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
; 이게 주석임

; 문자열 상수를 전역상수처럼 선언함
@.str = private unnamed_addr constant [13 x i8] c"hello world\0A\00"

; puts 함수의 외부 선언
declare i32 @puts(i8* nocapture) nounwind

; main 함수 정의
define i32 @main()
{   
     ; i32()*
     ; [13 x i8]를 i8 *로 변환함...
    %cast210 = getelementptr [13 x i8],[13 x i8]* @.str, i64 0, i64 0
   
    ; puts 함수를 호출해서 stdout에 문자열을 출력함
    call i32 @puts(i8* %cast210)

    ret i32 0
}

; 이름 붙인 메타데이터
!0 = !{i32 42, null, !"string"}
!foo = !{!0} 
ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ

이걸 써서 .ll 확장자로 저장한 다음에

clang에 넣고 돌리면 된다.

'LLVM > 주절주절' 카테고리의 다른 글

LLVM이란?  (0) 2019.09.03

설정

트랙백

댓글

[C++] trivial 타입

카테고리 없음 2019. 9. 3. 01:44
trvial이란 사전적 의미로는 [하찮은] 등의 의미를 지니는 단어이다.
햐지만 c++에서는 수학적 의미에서의 trivial(단순한 구조의 객체)을 뜻한다.
trivial 타입이란 생성자나 소멸자, 복사자, 이동자 등의 기본 특수 멤버들이 아무것도 하지 않는 타입이다.

이게 왜 필요한가 하면.. 가끔 생성자같은게 호출되지 않는 기존 C 기능과의 호환을 위해서다.
trivial한 타입만이 생성자 등을 호출하지 않는 malloc이나 memcpy같은 기능에 사용될 수 있다.

이에 대한 판별 기능은 <type_traits> 헤더에 전부 모여있다.

이 경우엔 기본생성자 등이 아무 일도 하지 않는다.

이 경우에도 아무 일도 하지 않는다.

생성자를 따로 구현했다. 실상은 아무것도 하지 않지만 뭔가 구현을 했기 때문에 trivial이 아니게 된다.

소멸자를 이렇게 해놔도 trivial이 아니게 된다. 다른 부분에서 뭔가 구현이 될 수 있기 때문이다.

메서드의 유무는 중요하지 않다.


하지만 가상메서드가 들어간다면 trivial은 깨진다. 다형성을 지원하기 위해 메모리 상에 가상테이블이 적재되고, 이에 대한 포인터가 내장되기 때문이다.
자연히 C와의 연계가 깨진다.


멤버변수의 유무 자체는 상관없다.

하지만 이러면 암묵적으로 생성자에서 하는 일이 생겨서 trivial이 깨져버린다.

아무튼 이런식으로 해서,
is_trivial은
1.모든 특수 메서드가 default, 혹은 명시되지 않았으며, 아무런 일을 하지 않고,
2.하나의 가상메서드도 소유하지 않았음을 요구한다.
이걸 만족할때만 true를 반환해준다.

세세하게 따지자면 몹시 많다.
여기에 이 관련 요구사항들이 다 들어있다.

https://en.cppreference.com/w/cpp/header/type_traits

https://narss.tistory.com/m/entry/trivial

설정

트랙백

댓글

[C++] R-Value Reference와 Move Semantic

언어/C++ 2019. 9. 3. 01:36
R-Value Reference(이하 오른값 참조)는 C++11부터 추가된 아주 복잡하면서도 멋진 녀석이다.

이게 대체 뭐하는 놈인고 하면, 아주 원론적인 부분까지 접해들어갈 필요가 있다.
아래 코드를 봐보자.

int num;
num = 33;

L: 여기서 num은 왼쪽에 있다. 그리고 이름이 있다. 그리고 표현식이 종료되어도 존재한다. 또한 메모리 셀이므로 주소연산(&)을 사용할 수 있다. 이것이 왼값이다.
R: 오른쪽에는 33이 있다. 이건 이름이 없다. 그냥 값이 num으로 전달될 뿐이고, 표현식이 끝나면 증발한다. 또한 상수이므로 주소연산을 사용할 수 없다. 이것이 오른값이다.

const string& s... 같은,
이제까지 우리가 사용했던 참조는 왼값참조였다.
기존에 존재하던 변수(왼값)를 참조했으니까.

하지만 이제 우리가 접근해볼 오른값 참조는 저런 이름없는 임시 값들마저 참조할 수 있다.
표기법은 아래와 같이, &대신에 &&를 사용한다.
int && ref = 333; //오른값 333을 참조

왼값도 std::move를 사용하면 오른값 참조에 묶을수 있다.
std::string s = "text";
std::string&& ref = std::move(s);
//이름이 move지만 진짜 이동하는건 아니고 &&참조로 캐스팅만 해줌

오른값 참조자도 기존의 왼값 참조자처럼 값에 접근해서 이래저래 쓸수 있다.

근데 이게 다라면 이걸 구분해서 쓸 이유가 전혀 없다. 그냥 왼값참조 써도 똑같이 되잖아?


그렇다. 이 오른값 참조로 값이 다른 변수에 전달되고, 그 변수의 타입에 해당하는 이동 생성자(혹은 대입자)가 있다면, 무브 시맨틱(Move Semantic)이라는 것이 발생한다.
말 그대로 이동(move)을 수행한다.

컴퓨터에서 무거운 영상 파일들을 옮겨본 적이 있을것이다.
파일이 너무 무겁다면 그 파일의 복사하고 붙여넣는(Ctrl+c,v) 속도도 엄청하게 느려진다.
하지만 파일을 잘라내고 붙일(Ctrl+x,v) 때는 별다른 시간이 소요되지 않고 뚝딱 완료된다. 이것이 파일이 한 군데만 존재할 것을 보장하는 데서 발생하는 일종의 이동이다.
앞서 언급한 무브시맨틱도 이와 같은 원리라 보면 된다.

예시를 한번 보자.
//가정1: s 안에 담긴 문자열은 크기가 크다.
std::string s = "...";
//이 시점에서 s는 더이상 필요가 없다.
//s2에서 옮겨서 s2로만 쓰면 된다.
std::string s2 = s;

이러면 s2의 복사생성자가 호출되면서 s의 내부값을 일일이 복사해서 가져간다.
기존 s의 크기가 클수록 소요되는 시간도 선형적으로 증가할 것이다.

하지만 이렇게 한다면
std::string s2 = std::move(s);
//s를 std::string&&으로 캐스팅
//s2의 이동생성자 호출

s의 길이와 상관없이 이 작업은 아주 작은 상수시간에 종료된다.
그리고 기존의 s는 텅 비게된다.

그리고 무브시맨틱은 연산중에 발생한 임시객체를 다룰때도 효용을 보인다.
std::string s = "hello";
std::string s2 = " world";
std::string s3 = s+s2;
옛날 방식대로 생각해보자. 위 코드의 세번째줄에서 s와 s2가 더해져서 둘이 이어진 임시객체 std::string("hello world")가 생성된다. 여기까진 좋다. 그럼
std::string s3 = std::string("hello world");
의 형태가 된다.
그럼 저 임시객체의 값을 복사해가기 위해  복사생성자가 호출되고 저 값들을 전부 복사해갈 것이다.
당장 저 길이라면 상관없지만, 임시객체가 저것보다 훨씬 크다면 이건 문제가 된다.

하지만 이제 우리에겐 무브 시맨틱이 있다.
s+s2의 결과물은 오른값이다.
그러므로 이동생성자가 호출돼서 s+s2의 결과값이 낭비없이 s3에게 쏙 넘어간다.


그리고 이런 성능의 향상 빼고도, 이 무브시맨틱은 하나의 장점이 또 있다.

무브시맨틱은 말 그대로 이동이다.
값이 아래에 있던 것이 위로 가면, 그 값은 위에만 존재한다. 아래에는 존재하지 않는다.
그러므로 이동의 동작은 의미론적으로 유일성(unique)을 보장한다. 복사와 다르게.

그래서 스마트포인터 std::unique_ptr 같은 경우는 이런 이동만을 행할 수 있다. 복사는 금지되어있다.
이 외에도 복사는 금지하고 이동만을 허가하는 클래스들이 꽤 있다.

auto p = std::make_unique<T>(...);
std::unique_ptr<T> p2 = p; //에러! 복사 불가
std::unique_ptr<T> p3 = std::move(p); //p->p3 이동. p는 null이 됨


아 그리고 오른값참조가 등장하면서 원래 4개였던 클래스의 기본 함수들이, 6개로 늘어났다.
클래스명이 T일 경우, 기본 함수들은 아래와 같다.

T()=default; //기본 생성자
~T() = default; //소멸자
T(const T&) = default; //복사생성자
T(T&&) = default; //이동생성자
T& operator=(const T&) = default; //복사대입자
T& operator=(T&&) = default; //이동대입자

이동생성자와 이동대입자는 구현할 필요가 없다. 복사생성자와 같은 얕은복사의 문제는 없기 때문에 그냥 =default만 해줘도 된다.(컴파일러에게 자동구현시키기)
그래도 만약 구현하고 싶다면, 아래처럼 하면 된다.
//가정: 타입 T에는 멤버변수로 a와 b를 가진다.
//가정: a와 b는 전부 이동이 가능하다.
T(T&& other) :
a(std::move(other.a)),
b(std::move(other.b)
{}
T& operator=(T&& other)
{
    this->a=std::move(other.a);
    this->b=std::move(other.b);
    return *this;
}

아무튼 오른값 참조는 굉장히 유용하다.
문제는 자잘한 규칙과 함정이 많고, 복잡하다는 것인데

가장 헷갈릴만한 것은, 오른값 참조자는 오른값이 아니라는 것이다. 오른값 참조자 자체는 이름도 있고, 주소연산도 가할 수 있으며, 표현식이 종료되어도 생존한다.
때문에 아래와 같은 경우는 이동이 발생하지 않는다.
std::string s = "...";
std::string&& s2 = std::move(s); //s->s2 이동
std::string s3 = s2; //s2->s3 이동 실패. 그냥 복사 생성자 호출

이동의 과정은 순수하게 오른값으로만 전달되어야 한다.
아래와 같이 고치면 이동이 수행된다.
std::string s = "...";
std::string&& s2 = std::move(s); //s->s2 이동
std::string s3 = std::move(s2); //s2->s3 이동

그리고 오른값 참조자는 const를 붙이면 안된다.
에러는 나지 않지만 이동이 실패한다.


더 자세한 사항들은 모던 이펙티브 C++를 참조하길 바란다.


'언어 > C++' 카테고리의 다른 글

[C++] Q: 왜 동적배열이 vector가 된건가요?  (0) 2019.09.03
[C++] C++17 optional  (0) 2018.12.30
[C++] C++17 variant  (0) 2018.12.30

설정

트랙백

댓글

[C++] Q: 왜 동적배열이 vector가 된건가요?

언어/C++ 2019. 9. 3. 01:27
https://stackoverflow.com/questions/581426/why-is-a-c-vector-called-a-vector


Q: 수학적 의미의 벡터는 대략 알긴 하는데요.
c++의 벡터에 대한 뜻은 알지 못해서 이렇게 질문을 올립니다.


A: vector의 수학적 정의는 집합 Sn의 멤버면서 특정 집합 S에 들어있는 값의 정렬된 시퀀스입니다.
이게 c++의 벡터가 저장하는거죠


A2: 이건 STL의 설계자인 Alex Stepanov가 built-in 배열과 구분하기 위해 vector라고 부른 데서 유래합니다.
근데 수학적 의미의 vector는 길이가 고정된 시퀀스죠... c++의 vector는 길이가 변하는 시퀀스고요.
알렉스도 자신의 실수를 인정했습니다.

게다가 C++0X(C++11)에서는 수학적 의미의 vector와 같은 기능을 하는 클래스를 'array'로 정의해버렸습니다.
엉망진창이네요.

알렉스의 교훈: 이름은 언제나 신중하게 결정하자

'언어 > C++' 카테고리의 다른 글

[C++] R-Value Reference와 Move Semantic  (0) 2019.09.03
[C++] C++17 optional  (0) 2018.12.30
[C++] C++17 variant  (0) 2018.12.30

설정

트랙백

댓글

[C++] C++17 optional

언어/C++ 2018. 12. 30. 22:29

C++17부터 옵셔널 라이브러리가 추가되었다.


이 옵셔널 타입은 원래 함수형 언어에서 나온 개념인데, 개념적으로는 모나드라고 부른다.


예외처리 방법 중 하나며, 안전성이 높아서 명령형 언어들에서도 채용을 많이 하는 편이다.

C#의 Nullable이 그렇고, Swift의 옵셔널, 코틀린의 ??, 러스트의 Result 등이 있다.


단어 자체의 뜻을 보면 '선택적인'이라는 뜻인데. 값이 선택적으로 있을수도 없을수도 있다는 뜻이다.


좀 다르긴 하지만 포인터 타입도 이런 면에서 옵셔널의 특성을 가진다고 할 수도 있다. 

널 포인터를 가지면 값이 없는 것이고 뭐라도 갖고있으면 주소가 있는거니까 값이 있는 것이다.


옵셔널은 이러한 특성을 포인터뿐만 아니라 모든 타입에 적용시킬 수가 있다.

게다가 값이 들어있는지 아닌지 명시적으로 확인을 하게 해서 포인터에 비하면 아주 안전하다.

그대신 귀찮은 면이 있긴 하지만 어느정도 감수할만한 부분이라 생각된다.



옵셔널로 선언된 객체는 그 자체로는 사용할 수 없다.

값이 들어있는 상태인지 아닌지 모르기 때문이다.



옵셔널의 값을 사용하려면 value 메서드를 쓰면 된다. 

다만 값이 없을 때 쓰면 예외가 뜨니까 has_value나 operator bool로 값 여부를 체크하고 사용해야 한다.

"John"이라는 값이 멀쩡히 들어있으니 아주 잘 수행된다. 


값이 없으니 "??"가 나온다.


포인터에서 빈 주소를 표현하는 용도로 nullptr가 예약된것처럼

빈 옵셔널을 표현하는 객체로 nullopt가 있다.


근데 값을 사용할때마다 조건문을 달아주는건 꽤 귀찮은 작업이다.

위에서 했던건 value_or 메서드 하나로 퉁칠 수 있다.

이건 값이 들어있으면 값 그냥 반환하고, 값이 없으면 인자로 받은걸 반환한다.


'언어 > C++' 카테고리의 다른 글

[C++] R-Value Reference와 Move Semantic  (0) 2019.09.03
[C++] Q: 왜 동적배열이 vector가 된건가요?  (0) 2019.09.03
[C++] C++17 variant  (0) 2018.12.30

설정

트랙백

댓글

[C++] C++17 variant

언어/C++ 2018. 12. 30. 22:21

variant는 진화된 공용체다.


기존의 union은 문제가 많았다.


예를 들어 기존의 union을 쓸 때, 

double 버전으로 값을 넣고 int 버전으로 사용을 한다 해도 아무런 문제 없이 작동이 된다.


하지만 출력값을 보면 알겠지만 값이 정상적이지 않다.


C의 공용체는 아무런 타입 검사를 수행하지 않는다. 이 때문에 C의 공용체는 타입-safe하지 못하다고 한다.


아무튼 저런 맛이 간 출력값이 나온 이유는 아무런 조치 없이 메모리에 존재하는 비트값을 그대로 읽어왔기 때문이다. 정수 타입과 실수 타입의 메모리 구조는 완전히 다르다. 게다가 엔디언과 바이트 크기까지 고려하면 같은 계열의 타입이라도 괴리가 발생할 수가 있다.


물론 이런걸 이용해서 편법적으로 사용할 수도 있지만, 

실제로 이런 식의 접근은, 99% 사용자의 실수에서 발생한다. 그리고 실수는 결함으로 이어진다.

union은 이처럼 안전성에 문제가 많다.


게다가 기존의 union은 생성자/소멸자/가상메서드가 없는 이른바 POD 타입만을 멤버로 둘 수가 있다는 심각한 기능적 결함이 있었다.

다시말해 C++스타일의 타입은 제대로 담아둘수 없었다는 것이다.



C++17에서는 이런 문제들을 해결한 variant라는 멋진 녀석을 내놓았다.


variant는 타입-safe한 공용체 템플릿이다.

당연히 C++ 스타일의 클래스도 멤버로 둘 수 있다.


기존의 공용체와 다른 특징으로, 이녀석은 반드시 초기화를 해야만 접근 타입을 바꿀 수가 있다.

해당 타입으로의 초기화를 수행하지 않고 접근을 한다면 예외가 발생한다.

아마 이런 방식을 통해서 타입체킹도 엄격하게 하고 생성자/소멸자도 써먹을 수 있게 한 것 같다.



일단 아래 코드는 아주 정상적으로 작동하는 코드다.

int 타입으로 값 대입 한번 해주고, 접근해서 출력하고.

double로 초기화해주고, 접근해서 출력한다.




하지만 초기화를 수행하지 않으면 이렇게 예외가 나온다.

이렇게



그리고 애초에 없는 타입이나 인덱스는 당연히 컴파일부터가 안 된다.



암튼 그렇다.


자세한 건 레퍼런스를 참조

https://en.cppreference.com/w/cpp/utility/variant

'언어 > C++' 카테고리의 다른 글

[C++] R-Value Reference와 Move Semantic  (0) 2019.09.03
[C++] Q: 왜 동적배열이 vector가 된건가요?  (0) 2019.09.03
[C++] C++17 optional  (0) 2018.12.30

설정

트랙백

댓글

SFML 튜토리얼 목차

SFML 오피셜 튜토리얼

[시작]

비주얼 스튜디오
-> http://insalat.tistory.com/14

코드블럭
-> 안함

리눅스
-> 안함

x코드
-> 안함

CMake
-> 안함


[시스템 모듈]

시간 다루기
-> http://insalat.tistory.com/28

스레드
->

사용자 데이터 스트림
->


[윈도우 모듈]

윈도우 열고 관리하기
-> http://insalat.tistory.com/15

이벤트
-> http://insalat.tistory.com/16

키보드와 마우스, 조이스틱
-> http://insalat.tistory.com/17


[그래픽 모듈]

2D 사물 그리기 
-> http://insalat.tistory.com/18

스프라이트와 텍스쳐
-> http://insalat.tistory.com/19

텍스트와 폰트
-> http://insalat.tistory.com/20

도형
-> http://insalat.tistory.com/21

vertex 배열
-> http://insalat.tistory.com/22

엔티티 변환
-> http://insalat.tistory.com/23

셰이더로 효과 넣기
-> http://insalat.tistory.com/24

View로 2D 카메라 제어하기
-> http://insalat.tistory.com/25


[오디오 모듈]

사운드 재생
-> http://insalat.tistory.com/26

오디오 기록
-> http://insalat.tistory.com/27

커스텀 오디오 스트림
->

3D에서 어쩌고
->


[네트워크 모듈]

소켓을 사용한 통신
->

패킷의 사용과 확장
->

HTTP로 웹 리퀘스트
->

FTP?
->



설정

트랙백

댓글