[include(틀:다른 뜻1, from=컴파일, other1=일본의 게임 회사, rd1=컴파일(게임 회사), other2=일본의 만화, rd2=컴파일러(만화))] [include(틀:컴퓨터공학)] [include(틀:이론 컴퓨터 과학)] [목차] == 개요 == {{{+2 Compiler}}} 컴파일이란 어떤 언어의 코드 전체를 다른 언어로 바꿔주는 과정이다. 그리고 이것을 자동으로 수행해주는 소프트웨어를 컴파일러. 코드가 실행되기 전 컴파일러를 거쳐서 기계어로 모두 변환되어 실행되는 프로그래밍 언어를 컴파일 언어라고 한다. [[인터프리터]] 언어와 대조된다. == 상세 == 컴파일러를 엄밀히 말하자면, 어떤 [[프로그래밍 언어]]로 쓰여진 소스 파일을 다른 프로그래밍 언어로 바꾸어주는 번역기인 셈이다. 어떤 언어 A를 B로 바꾸면 그게 컴파일러다. [[Scheme]]을 [[C언어]]로 번역한다든지, 심지어 기계어를 [[C언어]]로 [[리버스 엔지니어링|번역하더라도]](!) 컴파일러라고 칭할 수 있다. 하지만 대개의 경우 고수준 언어를 [[기계어]]로 번역하는 프로그램을 일컫는다. 이 두 개념을 구분하고자 할 경우 전자를 트랜스컴파일러(transcompiler)나 소스간 컴파일러(source-to-source compiler), 후자를 디컴파일러(decompiler)라고 많이 부른다. [[어셈블리어]]를 기계어로 번역하는 프로그램 역시 어셈블리어의 특수성 때문에 따로 [[어셈블러]]라 한다. 초기엔 프로그램을 작성하기 위해서는 컴퓨터 위에서 바로 돌아가는 [[기계어]]를 통하여 [[프로그래밍]]을 했다. 그러나 이런 과정은 생산성, 기기 간 호환성,[* 예를 들어서 [[ARM(CPU)|ARM]] 아키텍처 대상으로 작성된 프로그램은 [[x86]] 아키텍처에서 안돌아가는 식으로.] [[디버깅]] 등 모든 면에서 효율적이지 않다. 따라서 [[컴퓨터과학]]이 발전하면서 많은 부분을 추상화된 고수준 언어로 작성하고 이를 [[번역기]]를 통해 기계어로 번역하기 시작했는데, 이 번역기가 바로 컴파일러이다. 현재 많은 [[프로그램]]은 컴파일러를 통하여 전체를 기계어로 번역하여 실행하므로 프로그램 개발에 필수적인 툴 중에 하나다. 한 언어의 컴파일러를 자신의 언어로 재작성하는 것을 [[부트스트래핑]]이라고 한다. 재밌는 것은 컴파일러도 결국 하나의 언어로 짜여진 프로그램이라는 점. 따라서 바이너리 포맷의 파일을 쓸 수 있고 트리 자료구조를 생성할 수 있는 언어는 [[부트스트래핑]](bootstrapping)이 가능하다.[* 트리 자료구조를 사용하지 않는다면 [[어셈블러]]에 해당하며, 고수준 언어를 컴파일할 수는 없다.] 범용 목적 프로그래밍 언어는 당연히 이 조건을 만족하므로 부트스트래핑이 가능하다.[* 그렇지 않은 일부 특수 목적 언어는 영원히 다른 언어의 도움을 받아야 한다.] [[GCC]]라는 C 컴파일러는 컴파일에 [[C++]]을 사용한다. 컴파일러의 최초 버전만 다른 언어용의 컴파일러 또는 어셈블러의 도움을 받아 만들고 나면[* 또는 다른 아키텍처상에서 구동하는 크로스컴파일러로] 컴파일러가 자기 자신의 언어로 짜여질 수 있다. 일례로, GHC라는 [[Haskell]] 컴파일러는 최초에 Lazy ML이라는 다른 언어로 작성되었다가, 자기 자신의 언어인 [[Haskell]]로 재작성되었다. 이런 식으로 거슬러 올라가다 보면, 최초의 어셈블러는 기계어로 만들어졌고 최초의 고급 언어는 어셈블러로 만들어졌음을 알 수 있다. 원칙적으로 컴파일러는 프로그램을 기계어로 바꾸기만 할 뿐 이를 바로 실행이 가능하게 하지는 않는다. 여러 소스 파일에서 나온 결과물을 합치고 라이브러리도 포함시키는 등 별도의 작업을 거쳐야 실행이 가능해지는데 이를 수행하는 프로그램이 [[링커]]이다. 하지만 보통은 그냥 뭉뚱그려 컴파일러라 부르는 경우가 많다. 또한 요즘은 그냥 프로그램 하나만 돌리면 [[컴파일]]과 링킹을 한 번에 끝낼 수 있게 되어 있다. 물론 내부적으로는 컴파일러와 [[링커]]가 따로 있어서 이를 이용하는 경우가 많다. 00년대 초반, 그 이전에는 싱글/듀얼코어 프로세서가 대세라 컴파일 시간에도 상당히 많은 시간을 잡아먹었는데 이를 해결하기 위해 헤더 파일 포함을 이용해 목적 파일을 큰 파일 하나로 모으는 트릭을 사용하기도 했으나, 최근 사용되는 개발자 PC의 경우 쿼드코어는 저사양이고 보통 6~16 혹은 그 이상의 코어를 가진 프로세서를 사용하는데다, [[그리드 컴퓨팅|분산 빌드]]를 사용하기 때문에 이런 짓은 코드가 짧은 편이 아닌 이상 코드 관리가 힘들어지는 바보짓이 된다. 하나의 매우 매우 긴 소스 파일을 읽는 것보다 파일/기능별로 정리된 코드를 보는 것이 생산성에도 훨씬 좋다. [[Visual Studio]]를 비롯해 최근에 나오는 대부분의 컴파일러(IDE)는 멀티 프로세서 컴파일을 지원하기 때문에 코어수가 늘어날수록 병렬로 컴파일 후 링킹하는 것이 훨씬 빠르다. [[OpenCV]] 등 대형 오픈 소스 프로젝트를 멀티코어 빌드해보면 CPU를 풀로드로 갈굴 수 있다. 컴파일을 하는 대신, 소스를 한꺼번에 번역하지 않고 명령 하나하나를 실행할 때마다 해석하여 계산하는 방법도 있는데 이 해석기를 [[인터프리터]]라 해서 따로 분류한다. 컴파일러가 번역기라면 인터프리터는 통역기인 셈. 인터프리터 언어에 비해 컴파일 언어의 단점은 수정이 용이하지 않다는 점이다. 수정 사항이 발생하면 다시 컴파일을 해야 되는데, 작은 프로그램일 경우에는 문제가 되지 않지만 컴파일이 몇 시간씩 걸리는 덩치 큰 프로그램에서는 문제가 된다. 특히 수정 사항이 빈번하게 발생할 경우에는 큰 문제가 된다. 이 때문에 수정 사항이 빈번하게 발생할 것 같은 부분은 [[인터프리터]]를 쓰는 방법으로 따로 빼 두는 기법을 많이 사용한다. 다만 멀티스레딩 프로그래밍이 굉장히 어려운 편(인터프리터는 속도는 느리지만 수정이 간단하다는 장점이 있다). 범용 프로그램 언어가 아닌 인터프리터 언어를 '''스크립트''' 언어라고 한다. 보통 Web/매크로/게임 등 특정 분야에 특화/로컬화된 인터프리터 언어들을 이렇게 부른다. 스크립트의 경우는 [[Lua]] 등 표준화된 스크립트 언어를 사용하는 경우도 있지만 범용 기계어 컴파일러보다 기능범위/명령어 수가 적기 때문에 회사/단체에서 내부 스크립트를 제작해서 쓰기도 한다. == 종류 == 컴파일러는 다음과 같은 네 분류로 나뉜다. * 원시 코드를 바로 [[기계어]]로 변환하는 정적 컴파일(Static Compilation) * 원시 코드를 [[바이트코드]]로 변환하는 바이트코드 컴파일(Bytecode Compilation) * [[바이트코드]] 등의 중간 코드를 기계어로 변환하는 AOT 컴파일(Ahead-Of-Time Compilation) * 실행시 최초 한 번에 한해 컴파일을 거치는 [[JIT]] 컴파일(Just-In-Time Compilation) 요즘 컴파일러들은 기계가 프로그램을 빠르게 돌릴 수 있도록 적절한 최적화 작업을 하고 프로그래머가 실수할만한 부분을 경고하는데다 편집기와 연동하여 패턴 학습/코드 추천 등 갈수록 똑똑해지는 추세이다. [[프로그래머]]가 할 수 있는 어렵다고 알려진 작업이 컴파일러, [[운영체제|OS]], [[게임 엔진]], [[웹 브라우저]] 엔진, [[텐서플로|딥러닝 프레임워크]] 제작이다.[* [[웹 브라우저]] 엔진도 2010년대 오면서 OS급의 복잡성을 자랑하게 되었지만 엔진이 대부분 [[오픈 소스]]인 고로 자체개발이 목적이 아니면 그냥 끌리는 거 하나 퍼서 쓰면 된다(...).] 컴파일러는 [[컴퓨터과학]] 학부과정 커리큘럼의 3, 4학년에 있는 과목이다. 컴파일러를 제작하려면 [[컴퓨터구조]]와 소프트웨어를 전부 이해하고 있어야 하고 [[자료구조]]와 [[알고리즘]]에 대한 심도 깊은 이해와 함께 [[이산수학]]에도 능해야 한다. 컴파일러를 제작하고 [[LLVM]]같은 오픈 소스에 기여할 수 있을 정도의 실력이라면 글로벌 탑티어 IT 기업에 쉽게 들어갈 정도의 실력이 된다. 거기서 더 나간 스킬 트리로는 [[프로그램 최적화]]가 있는데 이건 컴파일러의 보조 스킬. --물론 [[BrainFuck]]류의 [[난해한 프로그래밍 언어]]의 컴파일러는 간단한 편-- -- 이런 종류의 컴파일러는 만들기 쉬운데 이걸로 고급 프로그래밍을 하기가 컴파일러 제작급 난이도-- 기계어를 다시 프로그래밍 언어로 바꾸는 디컴파일이라는 용어도 있다. 사실 컴파일 과정에서 소스 코드의 일부 정보를 지워버리기 때문에 완전한 디컴파일은 불가능하다. 대체로 [[Java]]/[[C\#]] 등 VM기반 언어는 디컴파일시 손실되는 정보가 적은 편[* 컴파일 프로필에 따라 완전한 소스 코드 복구까지 가능할 정도.]이고 [[C언어|C]]/[[C++]]의 경우 컴파일 프로필이 '릴리즈' 모드일 경우 거의 모든 인터페이스 정보가 손실된다. 이를 디컴파일하는 것은 사실상 어셈블리를 분석하여 그에 맞는 C/C++ 코드를 역산하는 [[리버스 엔지니어링]]을 인공지능으로 하는 것에 가깝다. 간혹 프로필을 '디버그'로 둔 채 그대로 릴리즈해버린 경우가 있는데 이 경우에는 디컴파일시 꽤 많은 정보가 보존되어 나온다. 하지만 대개 지역변수 이름 같이 지역성이 명백하고 기계에게 필요없는 정보는 날아가는 편이다. == 크로스 컴파일러 == 다른 CPU나 다른 운영체제에서 작동하는 실행코드를 만들어주는 컴파일러. 예를 들자면 [[ARM(CPU)|ARM]]에서 구동시킬 실행파일을 x86-64 [[Linux|리눅스]]/[[Microsoft Windows|윈도우]] 환경에서 컴파일 및 링크하는 것을 말한다. 보통 [[임베디드 시스템]]에서 작동시킬 실행파일을 만들 때 사용한다. 주된 이유는 속도와 생산성 때문. 컴파일 과정은 CPU 작업을 상당히 많이 요구하며, 임베디드 시스템은 CPU 성능이 떨어지고 메모리가 협소하기 때문에 고성능 컴퓨터에서 편리하게 코드를 편집하고 컴파일 작업을 진행하는 것이 훨씬 더 빠르고 생산성이 좋다. 486~586 수준의 임베디드 시스템 내에서 직접 컴파일하는 것도 가능하지만, 고성능 x86-64 머신을 사용하는 게 20배 이상 빠르다. 물론 [[슈퍼컴퓨터]]용 바이너리를 [[8086]]에 [[DOS]] 같은 구닥다리에서 컴파일해 만들어도 크로스 컴파일이다. [[안드로이드(운영체제)]]나 [[iPhone]]에서 작동하는 앱을 만들기 위해 [[Microsoft Windows|윈도우]]나 [[iMac]]에서 개발도구로 실행파일을 만들어내는 것은 모두 크로스 컴파일에 속한다. == intrinsic == C 등의 고급 언어에서 CPU의 인스트럭션 하나로 해결할 수 있는 복잡한 기능(?)을 사용할 경우 특정 함수를 호출하면 컴파일러가 함수를 호출하는 명령을 넣어주는 것이 아니라 그냥 그 자리에 어셈블리를 사용한 것과 동일하게 기계어 코드를 직접 넣어주는 함수(처럼 생긴 예약어)가 있는데 이것을 intrinsic, 혹은 built-in function이라고 한다([[인라인 함수]]와는 다르다). Compare-And-Exchange[* Compare-And-Swap이라고도 하며 줄여서 CAE 또는 CAS라고 부른다. 이는 멀티 쓰레딩이나 멀티 프로세싱에서 같은 메모리를 동시에 수정할 때 동기화 문제를 해결하기 위해 현대의 대부분의 CPU에서 지원하는 원자적인(atomic) 명령어이다.]의 예를 들어 a값이 m인 경우 n으로 바꾸는 코드는 x86 CPU에서 cmpxchg 인스트럭션 하나로 해결이 가능하다. 이러한 명령은 컴파일러가 지원하는 인트린식을 사용하면 된다. [[RISC]]에서 pseudoinstruction[* 어셈블리 명령어는 있지만 실제 바이너리를 까보면 어러 명령어의 집합으로 나오는 경우]을 사용하는 것과 반대의 개념으로 볼 수 있다. 컴파일러별로, 그리고 아키텍처별로 지원하는 인트린식이 다르니 사용을 원하면 컴파일러 매뉴얼을 참조하면 된다. {{{#!syntax cpp int a, b; ... b = CAS(&a, 10, 20); .... }}} * C 코드 {{{#!syntax cpp int CAS(int* pos, int oldval, int newval) { int oldpos = *pos; if(*pos == oldval) * pos = newval; return oldpos; } }}} * 어셈블리 코드(x86-64) {{{CAS: movl %edx, %eax lock cmpxchgl %esi, (%rdi) ret }}} * 어셈블리 코드(x86 32비트) (built-in function을 사용하지 않은 경우) {{{CAS: mov ecx, dword ptr[esp + 4] mov eax, dword ptr[ecx] push eax; mov ebx, dword ptr[esp + 12]; cmp ebx, eax; jne endpoint; mov dword ptr[ecx], dword ptr[esp + 16]; endpoint: pop eax; ret 16; }}} * 어셈블리 코드(x86) (built-in function을 사용한 경우) {{{CAS: mov ecx, dword ptr[esp + 4] mov eax, dword ptr[ecx] mov ebx, dword ptr[esp + 8]; mov edx, dword ptr[esp + 12]; cmpxchg ebx, edx; mov dword ptr[ecx], esp; ret 16; }}} * VC++ intrinsic {{{#!syntax cpp b = _InterlockedCompareExchange(&a, 20, 10); }}} * gcc built-in function {{{#!syntax cpp b = __sync_val_compare_and_swap(&a, 10, 20); }}}또 다른 예제로 popcount를 들 수 있다. 정수형의 값에서 1로 세트된 비트를 세는 것이다. {{{#!syntax cpp printf("%d %d\n", popcount(0x0000FFFF), popcount(0x00000001)); 16 1 }}} * C 코드 {{{#!syntax cpp int popcount(unsigned int a) { int i, cnt = 0; for(i = 0; i < sizeof(unsigned int) * 8; i++) { /* i번째 비트가 1이면 cnt 증가 */ if(((a >> i) & 1) == 1) cnt++; /* cnt += (a>>i) & 1; ^^; */ } return cnt; } }}} * gcc built-in function {{{#!syntax cpp int popcount(unsigned int a) { return __builtin_popcount(a); } }}} ~~참 쉽죠?~~ [[인텔]]의 x86 CPU는 [[삼각함수]]나 [[로그]] 등의 실수연산을 CPU가 바로 지원해주기 때문에 x86용 컴파일러는 대부분의 수학 함수를 인트린식으로 지원한다. == 목록 == * [[GCC]] * [[LLVM]]/[[Clang]] * [[Visual Studio|MSVC]] * [[안드로이드 런타임]] == 관련 사이트 == * [[https://godbolt.org/|Godbolt Compiler Explorer]]: 온라인으로 각종 언어의 컴파일러를 사용해 코드를 컴파일해볼 수 있는 사이트이다. * [[https://www.ryugod.com/|류갓닷컴]]: 온라인으로 170개가 넘는 언어의 컴파일러, 인터프리터를 리눅스 터미널과 함께 제공한다. == 컴파일러 개론 == 2021년 [[튜링상]]을 수상한 제프리 울만(Jeffrey D. Ullman), 알프레드 에이호(Alfred V. Aho)의 'Compilers: Principles, Techniques, and Tools' 이른바 드래곤책이 유명하다. 현재 2판까지 나왔다. [[전산학과]] 학부생들이 배운다. [[파일:Compilers.jpg]] * 컴파일러 구조(Compiler structure) * 어휘 분석(정규 표현식 및 유한 오토마타 포함)(Lexical analysis (including regular expressions and finite automata)) * 구문 분석(문맥 없는 문법,LL 파서,상향식 파서,LR 파서 포함)(Syntax analysis (including context-free grammars, LL parsers, bottom-up parsers, and LR parsers)) * 구문 지향 번역(Syntax-directed translation) * 유형 검사 (유형 변환 및 다형성 포함)(Type checking (including type conversions and polymorphism)) * 런타임 환경((매개변수 전달,기호 테이블 및 레지스터 할당 포함)(Run-time environment (including parameter passing, symbol tables and register allocation)) * 코드 생성(중간 코드 생성 포함)(Code generation (including intermediate code generation)) * 코드 최적화(Code optimization) * 직접적인 번역(Directed translation) * 새로운 데이터 흐름 분석(New data flow analyses) * 병렬 머신(Parallel machines) * 가비지 컬렉션(Garbage collection) * 새로운 사례 연구(New case studies) == 관련 문서 == * [[컴퓨터 관련 정보]] * [[컴퓨터 공학]] [include(틀:문서 가져옴, title=임베디드 시스템, version=89)] [include(틀:문서 가져옴, title=컴파일, version=89)] [[분류:컴퓨터 공학]][[분류:컴파일러]]