Computer Science
전공이 Software engineering이면서 Blog에 관련된 이야기를 하지 않으려고 외면해 왔다. 관심을 가지고 하는 일이며, 하루의 대부분을 차지하는 일인데 애써 거부하는 것이 웃기지 않은 가? 매우 뛰어나신 분들은 항상 겸손히 조용히 계시니 없는 지식이라도 조금 풀어놓고자 한다. 빈수레가 요란하다고 하지 않았던 가?
64-bit Data Models
Visual Studio와 GCC의 C compiler에게 매우 감사했었던 사실은
32-bit 시절 모두 ILP32를 사용했다라는 것이다.
ILP32를 간단히 설명하자면 int, long, pointer는 32-bit 에 해당한다는 이야기이다.
C90에서 Data type들을 정의할 때
'sizeof(char) <= sizeof(short) <= sizeof(int) <= sizeof(long) = sizeof(size_t)'
와 같은 식으로 정의했기 때문에 이러한 문제가 발생할 수 밖에 없다.
따라서 LP32, ILP32, LP64, ILP64, LLP64[1]와 같은 것이 존재한다.
지금까지 int가 system paradigm이 넘어갈 때 마다 바뀌어서 ILP64로 변경되는 것 아닌 가 했으나, 64-bit로 넘어오면서 Visual Studio는 LLP64, GCC는 LP64로 동작하고 있고 덕분에 int는 여전 4-byte에 머물러 있다.
다만 여기서 문제가 되는 것은 64-bit programming 시에 발생하게 되는데, 기존의 source code를 32-bit에서 64-bit로 변경시 Data type 에 대한 문제가 발생한다. pointer size가 변경되어버린 것이 첫번째이며 64bit를 사용하기 위한 방법[2]은 Visual studio와 GCC가 다르기 때문에 양쪽 모두 돌아가는 program을 작성해야하는 경우 매우 번거로운 일이 되어버린다. 물론 일반적인 program의 경우에는 별탈 없이 변경할 수 있으리라 생각되지만 분명 까다롭게 처리해야하는 부분이 있으리라 생각된다.
하고 싶은 이야기는 오랜만에 windows에서 programming을 하는 중에 발생한 문제. DWORD나 WORD같은 것을 꼭 사용해야하는 것인가? 가능하면 INT32, INT64와 같은 표현법을 사용해주면 참 고마울 것 같다. Linux에선 int32_t, int64_t를 사용해주면 고마울 것이고 말이다.
여담으로 덧붙이는 말은 Visual Studio 6.0 같은 것은 이제 사용하지 말자. 기본 Charset이 Unicode가 아닌 ASCII에 맞추어진 Tool을 사용하면 2-byte문자를 사용하는 국가에서 해당 Charset의 Windows를 사용하지 않으면 문자가 정상적으로 출력되지 않을 것 아닌가.. 특히 대한민국의 한글은 2-byte문자가 아닌 가? Unicode로 program을 작성해주면 참 고마울 것 같다.
[1] LLP64는 long, long long, pointer를 지칭하는 것으로 Visual Studio의 경우에 존재한다.
[2] GCC에서는 int64_t, uint64_t가 있고 Windows에서는 INT64, UINT64가 있다. (물론 long long도 가능하다.)
LLVM, Low Level Virtual Machine
LLVM은 compiler infrastructure이다. C++로 작성되었으며 Compile-time, Link-time, Run-time, Idle-time 등의 각 시점에서 모든 프로그래밍 언어에 대응하여 최적화 할 수 있도록 설계되었다.
Compiler는 Front-end와 Back-end, 두 부분으로 나눌 수 있는데 Front-end에서는 Lexical analysis, Syntax analysis의 과정을 통해 최종적으로 Intermediate code를 생성해낸다. 그리고 생성된 Intermediate code를 넘겨받은 Back-end는 Code optimization를 거쳐서 Target code를 생성해낸다. 따라서 Front-end와 Back-end는 code를 만들어낸다는 동일한 기능을 하고 있으나 Back-end의 경우 특정 Machine에 맞추어 설계되며 Front-end는 특정 언어에 맞추어 설계된다. 이렇게 설계함으로써 동일한 언어의 컴파일러를 타 기종의 Machine에 적용하기 위해서 Back-end만 수정하면 된다.
LLVM은 언어에 관계없이 동일한 Intermediate Representation(IR)[1]를 생성해 낸다. 아니 생성해내는 것이 아니라 IR은 이미 정의 되어 있거나 정의할 수 있으며 Front-end에서 해당 언어를 LLVM IR을 생성해내야 비로서 LLVM을 사용할 수 있게 된다.(Front-end는 LLVM core에 포함하고 있지 않다.) LLVM은 IR을 가지고 있으며 Back-end라고 생각할 수 있다. LLVM IR은 기계어에 가깝게 생성되지만 어느 Machine에도 종속되어 있지 않기 때문이다. 따라서 LLVM, Low Level Virtual Machine이라고 부를 수 있다.
LLVM에서는 C/C++/Objective C compiler로서 Clang을 제공하고 있다. LLVM의 설명으로는 Objective C를 compiler할 때 GCC보다 3배 빠르다고 한다. 이러한 성과에 대한 이유는 다음과 같이 유추해볼 수도 있다. 애당초 LLVM은 University of Illinois에서 시작된 것으로 Chris Lattner라는 사람이 LLVM의 primary author이다. 그런데 Christ Lattner가 Apple Inc.에 입사 후 open source project임에도 불구하고 Apple Inc. 에서 지원하기 시작했다. 그리고 현재 Apple의 주요 Toolchain이 LLVM으로 바뀌고 있다. 현재 당신이 Mac user이며 Xcode Version이 3.2 이상 이라면 Terminal을 열어서 다음과 같이 명령을 수행해보면 Apple에서 사용하고 있다는 사실을 알게 될 것이다.
s:~ __yuki_n$ llvm-gcc --version i686-apple-darwin11-llvm-gcc-4.2 (GCC) 4.2.1 (Based on Apple Inc. build 5658) ( LLVM build 2335.15.00) Copyright (C) 2007 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. s:~ __yuki_n$ clang --version Apple clang version 2.1 (tags/Apple/clang-163.7.1) (based on LLVM 3.0svn) Target: x86_64-apple-darwin11.2.0 Thread model: posix s:~ __yuki_n$
추가로 IR을 확인해 보기위해 아래의 c program(hello world)를 작성한 후 IR으로는 어떻게 표현되는지 확인해보자.
#include <stdio.h>
int main() {
printf("Hello world\n");
return 0;
}
위 프로그램은 매우 짧지만, LLVM에서 이야기하는 Source code formatting을 따른 것이다.[2]s:~ __yuki_n$ clang -S -emit-llvm hello.c이렇게 compile하고나면 IR을 확인할 수 있다.
hello.s
; ModuleID = 'hello.c'
target datalayout = "e-p:64:64:64-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:64:64-f3
2:32:32-f64:64:64-v64:64:64-v128:128:128-a0:0:64-s0:64:64-f80:128:128-n8:16:32:6
4"
target triple = "x86_64-apple-macosx10.7.2"
@.str = private unnamed_addr constant [13 x i8] c"Hello world\0A\00"
define i32 @main() nounwind ssp {
%1 = alloca i32, align 4
store i32 0, i32* %1
%2 = call i32 (i8*, ...)* @printf(i8* getelementptr inbounds ([13 x i8]* @.str
, i32 0, i32 0))
ret i32 0
}
declare i32 @printf(i8*, ...)
[1] LLVM에서는 Intermediate Code를 Intermediate representation이라고 하고 있다.
[2] LLVM에서는 '{'를 다음 줄이 아닌 같은 줄에 쓰고 Indentation은 '\t'이 아닌 ' '을 사용하라고 이야기하고 있다. 공백이 2칸이나 4칸이냐에 대한 문제는 규정하고 있지 않으나 2칸을 사용하는 것은 지극히 개인적인 의견이다. 자세한 이야기는 다음 link들을 참조할 수 있다. http://erin.is/lTx3j4Qt / http://erin.is/lTx3j4QX
C++에서 new가 memory allocation에 실패한다면?
이 질문을 보고 단박에 무슨 이야기를 하려는 지 알았다면 더 이상 이 글을 읽을 필요는 없다. 이번 기회를 통해서 또 한번 이 이야기를 되뇌인다. '언제나, 누구에게나 배울 것은 있다.'
C언어를 무진장 좋아한다. 어린시절 기억을 되돌려보면, Visual Basic, Qbasic 정도를 다룰 수 있었다가 C를 만나면서 만들어낼 수 있는 Program의 영역이 넓어졌다는 사실을 깨달았던 그 언젠가가 아직도 잊혀지지 않고 머리 속에 남아 있기 때문이다. 특히 C를 만나면서 Linux를 만났고 Emacs를 만났고 VI를 만났다. 그랬었기에 아직도 computer에서 떨어지지 못하고 있는 거다. 무언가 해낼 수 있다는 자신감을 심어줬기 때문에......
그 만큼 각별했던 C를 만난 후 5년 후에나 만났던 C++은 친숙했다. 다만 참 마음에 들지 않는 것이 있었으니, 바로 class exception의 존재이며 try, catch의 존재였다. exception 처리를 code에 하는 것이 아니라 별도로 하게 하는 이유는 프로그램의 흐름에서 예외에 대한 생각을 하지 않게 하여 수월하게 프로그램을 작성한다는데 있다고 믿는다.[1] 그런데 그 당시 exception은 거추장 스럽게만 느껴졌고, C++ 을 이용하면서 try, catch를 배우는 그 당시에만 사용해보고 이 때껏 사용하지 않았다. 물론 try, catch를 이용하여 exception을 처리하는 source code를 보기도 했지만 code를 읽어내려가는데 아무런 문제도 없었으니 제대로 사용해볼 생각은 더더욱 없었으리라. 그래서 이 때껏 모르고 있었던 것이다.
Type *ptr= new Type;
if(ptr==NULL) {
cerr << "Fail to allocate memroy";
return;
}
작성한 code의 일부분인데, 이것을 보고 어떤 문제가 있는지 이해했는 가? C++에서 new로 memory allocation을 요구했을 때 실패하면 NULL을 return하는 것이 아니라 기본적으로 bad_alloc exception을 발생시킨다. 물론 new를 통해서 memory allocation을 실패를 만나기가 쉽지 않다. 그렇기 때문에 더더욱 이 부분에 대한 이해가 늦었고 지금처럼 무식하게 메모리가 바닥날 때가지 메모리를 할당받아야하는 경우를 만나지 못했다면 끝끝내 알지 못했을 것이다.[2]만약 NULL을 return 받고 싶다면 다음과 같이 해야한다.
Type *ptr= new(std::nothrow) Type;
[1] 이 이야기를 해 놓고도 계속 의구심이 남는다. 이 글을 읽고 있는 당신이 정확한 이유를 안다면 무식한 한 사람을 깨우쳐 준다는 생각으로 덧글이나 메일을 남겨주시면 너무나 감사할 것이다.
[2] 혹여 이 부분에 대한 Test가 필요하다면, 현재 컴퓨터의 모든 상황을 정리한 후 테스트 하는 것이 좋다