블로그 이미지
대갈장군

calendar

1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30

Notice

2017. 5. 31. 23:43 프로그래밍/C#

https://docs.microsoft.com/en-us/dotnet/csharp/async


만약 입력과 출력에 관계된 일련의 처리 (데이터 베이스 처리나 네트워크를 통한 데이터 처리와 같은 일들)를 해야 할 경우에 이른바 asynchronous programming이 필요한데 이것은 내가 사용해 본 Node JS의 기본적인 처리 방법과 같다. 


요청이 들어오면 콜백 함수를 정해두고 일이 마무리 되면 콜백을 호출하는 일련의 처리 방식을 비동기 (asynchronous) 적이다 라고 하는 것.


C#은 기본적으로 제공해주는 비동기 함수 호출을 위해 아주 유용한 라이브러리 함수가 제공되니 그것이 바로 await / async 이구나.


비동기 프로그래밍의 핵심은 Task와 Task<T> 객체인데 이들은 비동기 operation들을 모델링한다. 이 객체들은 async와 await 키워드에 의해서 지원되는데 다음과 같은 경우를 생각해 볼 수 있다.


입출력에 관계된 코드의 경우, await operation은 Task나 Task<T>를 async 함수 내부에서 리턴하게 된다.


CPU에 관계된 코드의 경우 (CPU를 background로 사용하는 코드) await는 Task.Run 함수를 사용해서 background thread를 시작하게 된다.


이 await 키워드가 바로 마법을 행하는 놈인데 이놈은 실행되는 시점에 즉시 자신을 호출한 호출자에게 컨트롤을 '양보' (yield)한다. 다른 말로 하자면 버튼을 누르는 그 즉시 버튼을 컨트롤 하는 스레드에게 우선순위를 양보함으로써 UI가 사용자에 입력에 즉각적으로 반응하도록 한다는 것. 


이제 예를 들어보자. 가장 단순하게 인터넷에서 어떤 페이지의 버튼을 눌렀을 때, async와 await를 이용해서 비동기 프로그래밍을 구현한 것이다.


C#
private readonly HttpClient _httpClient = new HttpClient();

downloadButton.Clicked += async (o, e) =>
{
    // This line will yield control to the UI as the request
    // from the web service is happening.
    //
    // The UI thread is now free to perform other work.
    var stringData = await _httpClient.GetStringAsync(URL);
    DoSomethingWithData(stringData);
};


매우 간단하게 비동기 프로그래밍을 구현했다. 다만, 위 코드에서 보다시피 GetStringAsync라고 하는 비동기 전용 함수가 미리 구현되어 있어서 사용한다는 점. 그리고 버튼의 클릭 이벤트 핸들러 추가시 async 키워드를 사용했다는 점 주의.


다음으로는 CPU에 바인딩 된 경우에 대해서 알아보면, 많은 일을 처리해야 하는 경우에 사용할 수 있는 것이 바로 비동기 프로그래밍이다.


아래의 코드가 바로 그 예제이며 버튼이 눌러 졌을 때 비동기적으로 background thread를 Task.Run()을 통해서 호출하고 사용자는 스무스하게 플레이 할 수 있도록 UI에게 컨트롤을 양보한다.


C#
private DamageResult CalculateDamageDone()
{
    // Code omitted:
    //
    // Does an expensive calculation and returns
    // the result of that calculation.
}


calculateButton.Clicked += async (o, e) =>
{
    // This line will yield control to the UI CalculateDamageDone()
    // performs its work.  The UI thread is free to perform other work.
    var damageResult = await Task.Run(() => CalculateDamageDone());
    DisplayDamage(damageResult);
};



여기서 몇가지 중요한 점이 있는데 await 키워드는 반드시 async로 정의된 함수 안에서만 쓸수 있다는 점. 상식적으로 생각해서 컴파일러에게 이 함수는 비동기 함수를 포함하고 있으니 너가 적절한 조치를 취해야 한다는 것을 미리 알려줘야 한다는 점에서 이런 룰이 적용된 것으로 보인다. 물론, 암묵적인 변환을 할수 있을지는 몰라도, 아마도 명시적인 것이 더 적합할 듯.


또 한가지 더 중요한 점으로 지적된 것이, 비동기 프로그래밍을 하는 것이 성능에 있어서 분명한 이득이 될 때 사용하라는 점. 비동기 프로그래밍은 공짜로 주어지는 것이 아니기 때문에 확실히 성능상의 이득이 있을때 적절히 사용하라는 점. 오버헤드가 있다는 말.


추가적인 예제가 아래와 같이 나와 있다. 특정 웹사이트에 접속해서 특정 단어가 몇개나 들어가 있는지 검사해서 숫자를 결과를 리턴하는 것인데 await와 async로 만들었다.


C#
private readonly HttpClient _httpClient = new HttpClient();

[HttpGet]
[Route("DotNetCount")]
public async Task<int> GetDotNetCountAsync()
{
    // Suspends GetDotNetCountAsync() to allow the caller (the web server)
    // to accept another request, rather than blocking on this one.
    var html = await _httpClient.DownloadStringAsync("http://dotnetfoundation.org");

    return Regex.Matches(html, ".NET").Count;
}


드디어 Task<T>가 그 모습을 드러냈는데 T가 바로 리턴 값의 타입이다. 아래 코드는 UWP의 경우의 코드이다.


C#
private readonly HttpClient _httpClient = new HttpClient();

private async void SeeTheDotNets_Click(object sender, RoutedEventArgs e)
{
    // Capture the task handle here so we can await the background task later.
    var getDotNetFoundationHtmlTask = _httpClient.GetStringAsync("http://www.dotnetfoundation.org");

    // Any other work on the UI thread can be done here, such as enabling a Progress Bar.
    // This is important to do here, before the "await" call, so that the user
    // sees the progress bar before execution of this method is yielded.
    NetworkProgressBar.IsEnabled = true;
    NetworkProgressBar.Visibility = Visibility.Visible;

    // The await operator suspends SeeTheDotNets_Click, returning control to its caller.
    // This is what allows the app to be responsive and not hang on the UI thread.
    var html = await getDotNetFoundationHtmlTask;
    int count = Regex.Matches(html, ".NET").Count;

    DotNetCountLabel.Text = $"Number of .NETs on dotnetfoundation.org: {count}";

    NetworkProgressBar.IsEnabled = false;
    NetworkProgressBar.Visbility = Visibility.Collapsed;
}


뭐 비슷한데 가운데 추가적으로 프로그래스 바를 보여주는 옵션이 있네. 굳이 UWP의 경우를 따로 보여줘야 했나 싶다만, 아마도 UWP를 대세로 밀고 있는 듯 한 느낌적인 느낌?


그리고, 추가적으로 여러개의 task가 완료되기를 기다릴수도 있는데 바로 Task.WhenAll과 Task.WhenAny와 같은 것을 통해서 여러개의 비동기 background job들을 관리 가능하다.


C#
public async Task<User> GetUser(int userId)
{
    // Code omitted:
    //
    // Given a user Id {userId}, retrieves a User object corresponding
    // to the entry in the database with {userId} as its Id.
}

public static Task<IEnumerable<User>> GetUsers(IEnumerable<int> userIds)
{
    var getUserTasks = new List<Task<User>>();

    foreach (int userId in userIds)
    {
        getUserTasks.Add(GetUser(id));
    }

    return await Task.WhenAll(getUserTasks);
}


Task<User> 타입을 가지는 List를 만들어서 (getUserTasks) 각각의 userID를 리스트에 추가한 다음 한꺼번에 await를 통해서 호출하고 동시에 모든 작업이 완료되면 비동기적으로 그 결과를 리턴하게 된다. 


같은 일을 LINQ를 이용하는 경우에는 다음과 같다. 


C#
public async Task<User> GetUser(int userId)
{
    // Code omitted:
    //
    // Given a user Id {userId}, retrieves a User object corresponding
    // to the entry in the database with {userId} as its Id.
}

public static async Task<User[]> GetUsers(IEnumerable<int> userIds)
{
    var getUserTasks = userIds.Select(id => GetUser(id));
    return await Task.WhenAll(getUserTasks);
}


훨씬 간결한 형태의 코드가 가능하다. LINQ의 파워가 느껴진다... 포스가 느껴진다... 



마지막으로 중요한 몇가지 포인트들이 있는데,


1. async 함수는 반드시 최소한 한개의 await 키워드를 함수 내부에 가지고 있어야 한다. 없어도 컴파일은 되지만 효율이 겁나 떨어진다. 불필요한 async 남발은 피하라는 것.


2. async void는 반드시 event handler에만 사용하라. event의 경우 리턴값이 없으므로 Task나 Task<T>를 쓸 필요가 없고 그런 경우에는 async void를 쓰면 된다. 다만 이 경우에 컴파일러 입장에서는 몇몇 이유로 검증하는 것이 어렵기 때문에 최대한 async void를 사용하는 횟수를 줄여서 필요한 경우에만 쓰고 남발하지 말라는 것. 


3. LINQ와 async를 함께 쓰는 것은 매우 포스가 있지만 중요한 점은 LINQ 자체가 구현 되어 있는 방식이 async와 맞지 않는 부분도 있고 잘못 쓰게 되면 데드락이 걸리기 때문에 정확히 알고 있는 경우 아니면 되도록이면 함께 쓰지 말라.




'프로그래밍 > C#' 카테고리의 다른 글

C# 이벤트 - 객체 지향적 메시지  (0) 2010.06.25
C# - 제너릭 (Generic) 에 대한 질문  (0) 2010.06.18
Covariance and Contravariance  (0) 2010.06.18
델리게이트 - Delegate  (0) 2010.06.17
posted by 대갈장군
2016. 2. 8. 23:04 프로그래밍/C++

출처: http://duartes.org/gustavo/blog/post/getting-physical-with-memory/

저자: Gustavo Duarte


이 글도 상당히 도움이 되는 글인듯 하여 번역하여 옮겨둔다.


원래 복잡한 시스템을 이해하려면 가장 기초가 되는 원초적인 것을 들여다 볼 필요가 있다. 그런 점에서 우리는 프로세서와 버스 간의 인터페이스에 대해서 알아보자... 저자는 EE 전공자가 아니므로 EE 친구들이 걱정하는 그런 부분은 가뿐히 무시하겠다고... CS! CS! /yay


Physical Memory Access

위의 그림은 Core 2 디자인이다. 코어 2의 경우 775개의 핀을 가지고 있다. 그중 절반은 파워만 제공하고 데이터를 운송하지 않는다. 그럼 대략 387.5 개 인가? 작동하는 핀들을 그룹으로 묶어주면 사실 코어2의 디자인은 상당히 단순하다. 위의 그림을 보면 중요한 핀들을 보여주고 있는데 Address pin, Data pin, Request pin 들이다. 이 동작들은 FSB (Front Side Bus)라 불리는 곳에서 이른바 Transaction이라는 이름으로 시행되는데 FSB Transaction은 5개의 페이즈를 가지고 있단다. Arbitration, Request, Snoop, Response, Data 요렇게 다섯가지. Agent라 함은 프로세서에 Northbridge까지 포함한다. 한국말로 요원들이라고 해야 겠군... 이 요원들이 다섯가지 페이즈를 호출하여 사용하는데 각각 다른 형태의 임무를 수행한다는 군...


우리가 관심있는 분야는 오직 Request Phase다. Request Agent (주로 한개의 프로세서) 에 의해 2개의 packet이 출력으로 나오는 페이즈가 바로 Request Phase. 

FSB Request Phase, Packet A

위 그림처럼 35-3 (33 비트)의 길이를 가지는 필드를 먼저 보게 되는데 이것은 물리 주소를 알려주는 필드이다. 그리고 바로 뒤에 REQ 핀이 오는데 이 녀석의 각 비트 값에 따라 어떠한 형태의 접근 및 처리를 원하는지 알려준다. 이 첫번째 패킷이 나간 후에 바로 두 번째 패킷이 나가는데 아래 그림과 같다.

FSB Request Phase, Packet B

여기서 재미 있는 부분은 바로 Attribute Signals 필드인데 (31:24) 여기를 보면 메모리 캐싱 동작의 5가지 종류에 대해서 정의하고 있다. FSB에 이 정보를 제공함으로써 Request Agent는 다른 프로세서들에게 이 Transaction이 그들의 캐시에 영향을 주는지 알려주며 메모리 컨트롤러인 Northbridge에게 어떻게 동작해야 하는지도 알려준다. 프로세서는 커널에 의해 작성되는 page table을 참조하여 주어진 메모리 영역의 타입을 결정한다.


일반적으로 커널은 모든 RAM 영역을 write-back으로 간주하는데 그것이 최상의 퍼포먼스를 내기 때문이다. Write-back 모드에서는 메모리 액세스의 단위가 cache line 이 되고 이것은 64 바이트이다 (코어2에서). 만약 하나의 프로그램에서 1 바이트를 메모리에서 읽으면 프로세서는 그 1바이트를 담고 있는 전체 cache line 을 L2 와 L1 캐시에 로드한다. 또한 프로그램이 메모리에 쓰는 경우, 프로세서는 캐시에 있는 라인만 수정하고 main memory는 업데이트 하지 않는다. 나중에, 처리해야 하는 시점이 오면 그제서야 cache line 전체를 한방에 메인 메모리로 써버린다. 고로 대부분의 request는 Length field가 11 (64 bytes) 값을 가지고있다. 다음 그림을 보면 캐시에 없는 데이터를 어떻게 읽어들이는지 보여준다.

Memory Read Sequence Diagram

위 그림을 보아하니 대충 이해가 간다. 프로그램이 특정 주소를 달라고 요청하게 되면 우선 L1 캐시에 있는지 확인해보고 없으면 L2로 가고 거기도 없으면 FSB로 요청해 그 주소로 직접적인 액세싱을 하게 된다는 거. 길이는 64바이트로 고정되어 있군. 


추가적으로 인텔 컴퓨터의 일부 메모리 영역은 장치로 연결된 경우가 있다. 즉, 메모리 영역이 실질적인 RAM이 아니라 하드 드라이브의 일부이거나 네트워크 상의 공간일 수 있다는 것인데 이런경우에는 커널이 이런 주소 영역을 uncacheable 로 도장을 쾅 찍어둔다. 당연히 이런 주소 공간을 캐시에 담아둔다는 것은 말이 안된다. 이런 경우에는 길이가 64바이트가 아닐수 있으므로 Length field가 64 바이트가 아닐수 있다는 점..


글의 결론은

1. 퍼포먼스가 중요한 프로그램의 경우에는 같은 캐시 라인 안에 필요한 데이터를 팩킹해서 처리하도록 노력하는 것이 성능향상에 도움이 된다. 캐시에서 바로 가져오는 경우 성능의 향상이 어마어마 하다.

2. 하나의 cache line 안에 포함되는 메모리 엑세싱Atomic 이 보장된다. 원소성이라고 하는데 이것이 좋은 이유는 중간에 다른 스레드가 개입하여 중단하는 일이 없기 때문이다. 즉, 멀티 스레드에도 안전성을 제공한다는 거.

3. FSB는 모든 에이전트에 의해 공유된다. 모든 에이전트는 모든 transaction을 듣고 있어야 하는데 이런 것이 FSB에 교통혼잡을 유발하고 성능을 떨어뜨리는 이유가 되었다. 바로 Core i7에서는 이러한 문제를 해결하기 위해 프로세서가 바로 메모리로 접근할수 있도록 변경했다. 그것이 성능 향상에 큰 도움.






posted by 대갈장군
2016. 2. 6. 05:55 프로그래밍/C++
출처: http://duartes.org/gustavo/blog/post/anatomy-of-a-program-in-memory/

저자: Gustavo Duarte


요즘 메모리 관련 문제를 다루다 보니 점점 더 힙 속으로 빠져들고 있던 차에 좋은 글을 발견하고 내 개인 소장용으로 옮겨 볼까 한다.


일반적으로 멀티 테스킹 OS에서 각각의 프로세스는 그들 자신만의 메모리 샌드 박스에서 실행된다. 즉, 각각의 프로세스가 개별적인 주소 공간을 소유한다는 의미... 이 샌드 박스는 일반적으로 32비트 환경일 경우 Virtual Address Space라 부르고 약 4GB의 메모리 공간이 주어진다. (32비트니까~)


이 가상 메모리 주소 공간은 실질적인 (물질적인) 메모리와 이른바 Page Table이라는 놈을 통해 맵핑 되는데, 뭐, 쉽게 말해서 가상 주소는 말그대로 가상으로 주어진 공간이고 실제 메모리가 할당되려면 맵핑이 필요한데 이 주소록을 가지고 있는 녀석이 Page Table이라는 거지. 재미 있는 점은, 컴퓨터에 돌아가는 모든 프로세스는 이 룰에 따라야 한다는 말이다. 심지어 커널 자신 조차도... 쉽게 생각하면 커널은 예외일 것 같지만 그렇지 않다는 점을 글쓴이는 말하고 싶었던가 보다. 아무튼, 그래서 이 가상 주소 공간은 반드시 커널을 위해 일부를 쓴다는 점이다. (아래 그림처럼)

Kernel/User Memory Split

리눅스는 기본적으로 1GB만 커널이 사용하도록 기본 셋팅이 되지만 윈도우는 2GB나 커널을 위해 할당한다. 즉, 프로세스 자신은 남은 2GB만 사용가능하다는 거네... 이런 위도우 같은 경우를 봤나... 물론 윈도우도 3GB까지 확장하는 옵션이 있단다... 휴..


뭐 이쯤에서 눈치 챘겠지만 각 프로세스가 4GB씩이나 할당하면 나의 구린 컴퓨터는 고작 16GB 램인데 프로세스 4개 돌리면 램 바닥찍고 끝나는가 라는 의문이 스멀스멀 든다면 4 곱하기 4는 16이므로 정답..


물론, 각각의 프로세스가 4GB를 무조건 쓴다는 건 아니라는 점... 시작은 미미하게 해서 끝은 창대해 질수 있겠지만 일반적으로 시작할때는 최소 필요 메모리만 할당해서 최대 4GB까지 쓸수 있다는 점을 이론적으로 보여주는 것일 뿐, 실제로 프로세스가 무조건 4GB 할당하고 출발하는 건 아니라는 점.


다만, 프로그래머가 알아야 할 것은 실수로 커널 메모리 영역을 침범하면 운영체제가 "네 이놈! 여기가 어디라고 감히 들이대느냐!!" 하면서 프로그램을 종료시켜 버린다는 거... 또 한가지 중요한 점은, 커널 코드와 데이터는 항상 어디서나 접근 가능하고 인터럽트를 핸들링 하며 시스템 콜을 받아들일 준비가 되어 있는 반면, 사용자 주소 영역은 프로세스가 변경되면 그에 따라 변화한다는 점... (아래 그림)

Process Switch Effects on Virtual Memory

위 그림에서 사용자 주소 영역에 하늘색 표신 된 부분은 메모리가 할당되어 사용되어진 영역이고 흰색은 할당되지 않은 영역이다. 재미 있는 건, 저자가 Firefox는 메모리를 무지하게 잡아 먹는다면서 저렇게 Firefox의 메모리 할당을 크게 그렸다는 점... 크롬이 더 많이 먹는거 아닌가 몰라...

Flexible Process Address Space Layout In Linux

위 그림을 보면 대략적인 프로세스의 주소공간을 (4GB) 볼 수 있다. 일반적으로 모든게 제대로 잘 작동했을 경우 대부분의 프로그램은 위의 그림과 같은 구조의 메모리 공간을 가지고 시작하게 된다. 사실 이점에 대해서 저자는 보안 취약점이 될 수 있다고 지적하고 있다. 해커가 이런 메모리 구조를 미리 파악하고 대충 찍어서 맞추는 경우 보안상의 헛점이 드러날 수 있다는 점. 그래서 요즘은 메모리를 랜덤하게 유행한단다... 이 글이 써진 시점이 무려 2009년이니... 허허


위 그림에서 사용자 공간의 최상층에 위치하고 있는 것이 바로 Stack인데 이 녀석은 지역 변수를 저장하고 함수의 인자들을 저장하는 용도로 주로 사용된다. 함수를 호출하게 되면 스택영역에 Stack Frame이라는 놈을 넣게 되는데 이 함수가 리턴 될 때 이 Stack Frame도 파괴된다. 스택 자체가 LIFO 방식으로 단순하게 동작하므로 pushing과 popping이 매우 빠르고 효율적으로 수행 가능하다. 그리고 스택 영역을 자주 그리고 많이 사용하면 결국 CPU 캐시에 스택 메모리가 활성화 되어 있게 되므로 스피드를 더더욱 업업업~ 각각의 스레드는 자신만의 스택을 가진다.


만약 사용자가 스택 영역에 할당된 현재의 영역보다 더 큰 데이터를 집어 넣게 되면? 이렇게 되면 (리눅스의 경우) expand_stack()이라는 함수 (결국, acct_stack_growth() 함수 호출하는 녀석)를 호출해서 스택 영역의 확장이 가능한가 체크한다. 만약 스택 영억이 기본 최대 사이즈 (8MB)보다 작다면 스택 영역이 확장된다. 근데, 만약 이 8MB를 다 써버리면? 그때 바로 터지는 것이 Stack Overflow이고 프로그램은 Segmentation Fault를 받으면서 장렬히 전사한다. 또 한가지 흥미로운 점은 스택 영역은 한번 확장되면 스택이 줄어든다고 해서 확장된 영역을 줄이진 않는다. 이것이 마치 미국의 연방 정부의  예산과 같다고... 늘리기만 하고 줄이진 않는다는 이야기.. ㅋㅋ


자, 그 다음은 스택 아래에 위치한 Memory Mapping Segment라는 놈인데 이 놈은 파일의 내용을 직접적으로 메모리로 맵핑해준다. 이런 일을 해주는 대표적인 함수로는 mmap(), CreateFilemapping(), MapViewOfFile() 같은 함수들이다. 이러한 Memory Mapping은 고성능 파일 입출력이나 Dynamic library들을 로딩하기 위해서 사용되어 진다. 그리고 이른바 Anonymous Memory Mapping도 가능한데 이건 임의 메모리 맵핑이라 해야 하겠네.. 파일이나 라이브러리 같은걸 맵핑 하는게 아니라 malloc() 같은 함수가 제법 큰 메모리 공간을 할당하려고 할때 종종 이 구역의 메모리를 임의 메모리 맵핑이라는 형태로 사용하도록 해준다는 군. 메모리를 좀더 효율적이고 많이 사용하기 위해 그런듯...


그다음은 바로 힙! 엉덩이 아니지요.. Heap! 사실 프로그램 돌리다 보면 터지는 메모리 에러의 90% 이상이 힙에서 터진다고 봐도 과언이 아니다. 이 힙 영역은 프로그램이 런타임 (실행중) 중에 메모리를 임의의 크기로 할당할때 사용되는 영역이다. 이러한 동적 메모리 할당 때문에 힙 메모리 사용은 커널과 언어의 런타임이 서로 협상해서 만들어 진다. 대표적인 힙 할당 함수로 C에서는 malloc이 있고 C#이나 C++의 경우에는 new가 있다. 


만약 메모리 공간이 여유가 있다면 언어의 런타임에서 알아서 처리하고 끝낸다. 만약 현재의 할당된 힙으로 공간이 부족하다면 시스템 콜 (brk())을 통해 확장되는데 이것이 바로 커널의 개입이다. 사실 힙의 관리는 정말 복잡한데 머리 좋고 똑똑한 사람들이 최선을 다해 알아서 잘 관리하도록 만들어 놨다고... 뭐, 아무튼...


그리고 마지막으로 제일 아래 영역이 보이는데 BSS, data 그리고 program text다. BSS와 data store의 경우 static global variable들을 저장하는 용도로 사용된다. BSS는 할당되지 않은 (uninitialized) static variable들을 저장하고 data segment의 경우에는 initialized된 static variable들을 저장한다. 


뭐 몇가지 설명이 더 있기는 한데 그렇게 필요해 보이진 않아서 스킵~ 




posted by 대갈장군
2015. 2. 12. 04:06 프로그래밍/C

Source: http://phoxis.org/2011/04/27/c-language-constructors-and-destructors-with-gcc/


Constructors and Destructors are special functions. These are one of the features provided by an Object Oriented Programming language. Constructors and Destructors are defined inside an object class. When an object is instantiated, ie. defined of or dynamically allocated of that class type, the Constructor function of that class is executed automatically. There might be many constructors of which the correct implementation is automatically selected by the compiler. When this object is destroyed or deallocated, the Destructor function is automatically executed. For example when the scope of the object has finished or the object was dynamically allocated and now being freed. The Constructors and the Destructors are generally contains initialization and cleanup codes respectively required by an object to operate correctly. Because these functions are automatically invoked by the compiler therefore the programmer freed from the headache of calling them manually.

There is no such thing called ‘constructors’ and ‘destructors’ in C programming language or in structured languages, although there is no boundaries on defining such functions which act like them. You need to make functions which act like the constructors and destructors and then call them manually.

The GCC constructor and destructor attributes

GCC has attributes with which you can tell the compiler about how a lot of things should be handled by the compiler. Among such attributes the below function attributes are used to define constructors and destructors in C language. These would only work under GCC. As there is no objects and class approach in C the working of these functions are not like C++ or other OOP language constructor and destructors. With this feature, the functions defined as constructor function would be executed before the function main starts to execute, and the destructor would be executed after the main has finished execution. The GCC function attributes to define constructors and destructors are as follows:

1
2
3
4
__attribute__((constructor))
__attribute__((destructor))
__attribute__((constructor (PRIORITY)))
__attribute__((destructor (PRIORITY)))

For example, to declare a function named begin () as a constructor, and end () as a destructor, you need to tell gcc about these functions through the following declaration.

1
2
void begin (void) __attribute__((constructor));
void end (void) __attribute__((destructor));

An alternate way to flag a function as a C constructor or destructor can also be done at the time of the function definition.

1
2
3
4
5
6
7
8
__attribute__((constructor)) void begin (void)
{
 /* Function Body */
}
__attribute__((destructor)) void end (void)
{
 /* Function Body */
}

After declaring the functions as constructors and destructors as above, gcc will automatically call begin () before calling main () and call end () after leaving main or after the execution of exit () function. The following sample code demonstrates the feature.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>
 
void begin (void) __attribute__((constructor));
void end (void) __attribute__((destructor));
 
int main (void)
{
  printf ("\nInside main ()");
}
 
void begin (void)
{
  printf ("\nIn begin ()");
}
 
void end (void)
{
  printf ("\nIn end ()\n");
}

Execution of this code will come up with an output which clearly shows how the functions were executed.

1
2
3
In begin ()
Inside main ()
In end ()

Multiple Constructors and Destructors

Multiple constructors and destructors can be defined and can be automatically executed depending upon their priority. In this case the syntax is __attribute__((constructor (PRIORITY))) and __attribute__((destructor (PRIORITY))). In this case the function prototypes would look like.

1
2
3
4
5
6
7
8
void begin_0 (void) __attribute__((constructor (101)));
void end_0 (void) __attribute__((destructor (101)));
 
void begin_1 (void) __attribute__((constructor (102)));
void end_1 (void) __attribute__((destructor (102)));
 
void begin_2 (void) __attribute__((constructor (103)));
void end_2 (void) __attribute__((destructor (103)));

The constructors with lower priority value would be executed first. The destructors withhigher priority value would be executed first. So the constructors would be called in the sequence: begin_0begin_1 ()begin_2 () . and the destructors are called in the sequence end_2 ()end_1 ()end_0 (). Note the LIFO execution sequence of the constructors and destructors depending on the priority values.

The sample code below demonstrates this

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include <stdio.h>
 
void begin_0 (void) __attribute__((constructor (101)));
void end_0 (void) __attribute__((destructor (101)));
 
void begin_1 (void) __attribute__((constructor (102)));
void end_1 (void) __attribute__((destructor (102)));
 
void begin_2 (void) __attribute__((constructor (103)));
void end_2 (void) __attribute__((destructor (103)));
 
int main (void)
{
  printf ("\nInside main ()");
}
 
void begin_0 (void)
{
  printf ("\nIn begin_0 ()");
}
 
void end_0 (void)
{
  printf ("\nIn end_0 ()");
}
 
void begin_1 (void)
{
  printf ("\nIn begin_1 ()");
}
 
void end_1 (void)
{
  printf ("\nIn end_1 ()");
}
 
void begin_2 (void)
{
  printf ("\nIn begin_2 ()");
}
 
void end_2 (void)
{
  printf ("\nIn end_2 ()");
}

The output is as below:

1
2
3
4
5
6
7
In begin_0 ()
In begin_1 ()
In begin_2 ()
Inside main ()
In end_2 ()
In end_1 ()
In end_0 ()

Note that, when compiling with priority values between 0 and 100 (inclusive), gcc would throw you warnings that the priority values from 0 to 100 are reserved for implementation, so these values might be used internally that we might not know. So it is better to use values out of this range. The value of the priority does not depend, instead the relative values of the priority is the determinant of the sequence of execution.

Note that the function main () is not the first function/code block to execute in your code there are a lot of code already executed before main starts to execute. The function main is the user’s code entry point, but the program entry point is not the main function. There is a startup function which prepares the environment for the execution. The startup functions first call the functions declared as constructors and then calls the main. When main returns the control to the startup function it then calls those functions which you have declared as the destructors. There are separate sections in the executable .ctors and .dtors which hold these functions. (not discussed here).

As there is no class object creation in C language does not have such features like in C++ or other OOP languages but this feature can bring some flexibility by calling the functions automatically on execution and termination of the code which you needed to do inside the main. For example one use may be like, you have a dynamically allocated global variable, might point to a linked list head or an array, or a file descriptor which you can allocate inside a constructor. If some error is encountered you can immediately call exit () or the program terminates normally, depending on the error code you can make cleanup in the destructors.

Another good use is probably calling the initialization functions of some library, a bunch of which needs to be called in each program. You can make a separate file with the constructor files calling properly the library functions (probably operating on globals) and calling the library cleanup functions in the destructors. Whenever you make a program what you need to do is to compile your code with this file containing the constructors and destructors and forget about calling the initialization and cleanup functions in the main program. But doing this will make your code unportable, as it would only work under GCC.

결론:

__attribute__ 속성을 사용해서 C에서도 생성자와 파괴자 역활을 하는 함수를 선언하여 사용할 수 있다. main 함수가 호출되기 전에 자동으로 GCC가 __attribute__ 로 선언된 생성자 함수를 호출해 주고 main이 끝나는 곳에서 다시 __attribute__로 선언된 파괴자 함수를 호출하여 마무리.


장점:

1. 이런식으로 C에서 C++의 생성자 / 파괴자 흉내를 낼 수 있다

2. 사용 예로는 동적으로 선언되는 전역 변수를 할당 및 해제를 원큐에 해결 가능 (메모리 해제 걱정 NO!)

3. 에러를 만날 경우 즉각 exit를 호출하여도 자동으로 파괴자가 호출되므로 자원 해제가 용이함

4. 여러개의 프로그램에서 같은 라이브러리를 사용하는 경우, 생성자로 main 진입 전에 라이브러리를 초기화 시켜 주고 파괴자에서 해제해 주면 아주 굿!


단점:

GCC에서만 작동하므로 소스 코드가 portable이 아니다. 뭐, 그거야 당연한 거지만..


'프로그래밍 > C' 카테고리의 다른 글

C Standard Library  (0) 2010.09.25
싱글 스레드에서 멀티 스레드로... 왜 /MD 인가?  (0) 2010.03.11
호출 규약  (0) 2009.07.28
posted by 대갈장군
2014. 10. 28. 12:42 카테고리 없음
가수 신해철씨가 갑자기 세상을 떠났다는 소식을 오늘 접하고 가슴 한켠이 무거워 지는 느낌이 들었다.

잘 알지 못했던 그의 굴곡진 인생을 알게되었고, 귀에 익었던 그의 명곡들의 가사들을 곱씹어 보니 그가 그의 인생을 바쳐 말하고자 했던 진실을 느낄 수가 있다.


사람은 누구나 죽지만 죽는 순간까지 놓치지 않고 지켜나가는 그 사람만의 진실이 있다면 그것은 그 사람이 죽은 후에도 결코 변할 수 없는 영원한 진실이 된다.


나는 그의 인생이 걸어온 길에서 그가 지키고자 했던 진실이 느껴진다. 변함 없이 지켜온 그의 진심이 이제는 진실이되어 내 마음과 많은 사람들의 마음에 울려 퍼지는 것 같다.


죽는것보다 잊혀지는 것이 두렵다고 했던 신해철... 나는 최소한 그를 영원히 잊지 못할 것 같다. 그의 노래도, 그의 인생도, 그리고 그가 말하고자 했던 진실도 잊지 못할 것 같다.


안타깝게 떠나간 고인의 명복을 빕니다... 그곳에서 편안하시길.. 







posted by 대갈장군
2014. 8. 28. 04:47 프로그래밍/C++

출처: http://ikpil.com/540


내가 C++에 조예가 깊어서 글을 남기는 것이 아니라, Effecitve C++ 을 공부하는 사람들이 이 글을 보고, 도움이 되었으면 하는 생각과, 혹시 내가 틀린것이 있다면 지적해 주시지 않을까 란 생각으로 글을 올리는것임을 미리 밝힙니다.  - 최익필


typename .. 뭐 이렇게만 보면 이런게 있었나 싶다. 하지만 템플릿에서는 흔하게 보는 키워드인데, 이 키워드에 대해서 제대로 파악하자고 하는것 같다.

처음부터 진행하자면 typename 과 class 는 똑같은 의미인데, 이렇게 말만 하면 혼동의 요지가 있으니, 코드도 포함해 주는 센스를 발휘해 본다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// ikpil.com or ikpil.tistory.com
 
#include <iostream>
//template <typename _T>  // class _T 와 같은 의미
template <class _T>
class babo
{
typedef _T value_type;
public:
    void Draw( void )
    {
        std::cout << sizeof(value_type) << "의 용량을 가진 클래스" <<std::endl;
    }
private:
    _T *p;
};
 
int main( void )
{
    // int 타입을 가진 babo 클래스의 객체 Test 선언
    babo<int> Test;
     
    // 뭐~ 출력
    Test.Draw();
 
    return 0;
}


위의 주석을 다 읽어 보면 무슨 말인지 알듯 싶다. 하지만 스콧마이어어가 쓰고, 곽용재씨가 번역한 이번 항목에선 typename 은 또 다른 의미를 지니고 있다고 알려 주려고 하는듯 하다.

그렇다 typename 키워드는 .. ... 은 타입이다! 를 알려 주는 용도로 사용 한다. 요렇게만 들으면 또 .. 햇갈린다. 더 이야기 하자면 typename 키워는 템플릿 선언 내부에서만 사용 할수 있다는 것을 기억해야 한다. 아래 소스코드를 첨부 하겠다.

1
2
3
4
5
6
7
8
9
10
11
12
13
// ikpil.com or ikpil.tistory.com
int main( void )
{
    // int 타입을 가진 babo 클래스의 객체 Test 선언
    babo<int> Test;
     
    typename int a; // 컴파일 에러를 볼수 있을 것이다.
 
    // 뭐~ 출력
    Test.Draw();
 
    return 0;
}


그렇다면 이것은 템플릿 내부에서 써보자면

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// ikpil.com or ikpil.tistory.com
 
#include <iostream>
template <typename _T>    // class _T 와 같은 의미
class babo
{
typedef _T value_type;
public:
    void Draw( void )
    {
        std::cout << sizeof(value_type) << "의 용량을 가진 클래스" <<std::endl;
    }
private:
    _T *p;
public:
    typename int hehehe;    // 이렇게 쓸수 있다.
};


이렇게 쓸수 있다는 것인데, 이것만 본다면, 
1. int 는 타입이다
2. 그 타임으로 a 객체 생성!

.. 이것을 보고 비웃지 않는다면, 당신이야 말로 성인(成人)이다. 왜냐하면 int 는 누가봐도 타입인데 굳이 typename 을 피곤하게 붙인다는 것에 대해서 말이다.

그렇다면 다음 코드는 어떻게 이해 할 것인가.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// ikpil.com or ikpil.tistory.com
#include <iostream>
#include <vector>
 
template <typename _T>    // class _T 와 같은 의미
class babo
{
public:
    void Draw( void )
    {
        std::cout << sizeof(p) << "의 용량을 가진 클래스" <<std::endl;
    }
private:
    _T *p;
public:
    _T::iterator muhaha; // 이것 때문에 컴파일이 되지 않는다.
};
 
int main( void )
{
    // int 타입을 가진 babo 클래스의 객체 Test 선언
    babo<std::vector<int> > Test;
     
    // 뭐~ 출력
    Test.Draw();   
 
    return 0;
}


.. 물론 나는 _T의 타입인 iterator 란것을 분명이 알고 있다. 하지만 컴파일러는 다르다. 어떤 초특급 울트라 천재가 컴파일러를 만든다 하더라도, .. 저것이 "타입" 인것을 .. 알려면 몇달 몇일 간 별 수를 다 생각해야 할것이다. 

_T::iterator muhaha; 이것을
typename _T::iterator muhaha; 라고 바꾸면 정상적으로 컴파일이 되는 것을 볼수 있을것이다. 그렇다! typename은 템플릿 내부에서 사용하면서, 컴파일러에게 템플릿 내부에서 "이건 타입이야" 라고 알릴 필요가 있을 때 써준다.


이쯤에서 용어 설명을 하는게 좋을듯 싶은데,
_T 처럼 타입을 인자로 받아 그것을 타입으로 쓰기 때문에 _T를 의존 이름(dependent name) 이라 외국에서 불린다.요~런 사항에서 의존 이름(dependent name)속에 또 다시 다른 이름이 정의된 경우 그녀석은 중첩 의존 타입 이름(nested dependent type name .. 뭐 외국 따라가야지, .. 태생이 외국인인데..)이라고 불리는데,

바로 템플릿 내부에서 중첩 의존 타입 이름(nested dependent type name)이 있을 경우, 그녀석도 타입인것을 인지하지 못하는 사태가 발생 할 수 있다.(발생 할수 있다는 이유는 MSVC2005에선 발생하지만, 다른 컴파일러에선 발생 안할수도 있다는 뜻이다. 뭐.. 대부분은 발생한다.)

여담으로 int 같은 것들은 비의존 이름(non-dependent name)이라고 불린다. .. 뭐 그런게 있다~ 라고 알아두면 좋을듯 싶고.. 여기서 한가지 짚고 넘어가야 한다. 이런 typename 을 쓸때, template 내부여도 못쓰는 경우가 두가지 있다는 것이다!

이 경우는 중첩 의존 타입 이름(nested dependent type name)이 기본 클래스의 리스트에 있거나, 멤버 초기화 리스트 내의 기본 클래스 식별자로 있을경우 typename을 붙여 주면 안된다는 것이다!(사실 이 구역은 컴파일러가 알아서 이녀석은 타입이다! 라고 알수 있는 곳이기도 하다.) 이렇게만 말하면 다시 .. 난해 할 수 있으니 코드를 달아 주는 센스!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
// ikpil.com or ikpil.tistory.com
// typename 을 예외적으로 사용 할수 없는
// 구역을 나타내기 위해서 이렇게 코드를 만든다.
 
#include <iostream>
#include <vector>
 
template <typename _T>
class NON_TYPENAME
{
public:
    // 뭐 클래스 네임만 신경쓰고 나머진
    // 무시해도 된다.
    NON_TYPENAME() { }
    // 소멸자
    ~NON_TYPENAME() { }
private:
    _T m_p;
};
 
 
template <typename _T>    // class _T 와 같은 의미
class babo
    : public NON_TYPENAME<_T> // <-- 여기가 기본 클래스의 리스트
{
public:
    babo()
        : NON_TYPENAME<_T>() // <-- 여기가 멤버 초기화 리스트
    {
 
    }
    void Draw( void )
    {
        std::cout << sizeof(p) << "의 용량을 가진 클래스" <<std::endl;
    }
private:
    _T *p;
public:
    typename _T::iterator muhaha;   // <-- 여긴 사용 할수 있다~
};
 
int main( void )
{
    // int 타입을 가진 babo 클래스의 객체 Test 선언
    babo<std::vector<int> > Test;
     
    // 뭐~ 출력
    Test.Draw();   
 
    return 0;
}


자.. 이제 이해가 되었는가? 여기에 코딩 스타일이 좀더 편해질만한 이야기를 하나 더 들려 준다.
바로 typename _T::iterator muhaha; 를 쓸때 한번이나 두번 쯤이야 써주겠지만 3 이상 넘어가면 .. 슬슬 피곤해 진다. 이럴 때는 typedef typename _T::iterator iterator; 라고 쓰고 iterator muhaha; 라고 쓰면. 뭐 쉽지 않니한가? ㅋㅋ


여담으로 몇가지 더 이야기를 한다.
이 이야기는 곽용재씨가 들려주는 이야기로 스콧 마이어스와 허브서터, 안드레 알렉산드레스쿠는 사용자가 정의한 타입에 대해서는 class 를 쓰고 기본 제공 타입에 대해서는 typename 을 쓴다. 그리고 비야네 스트롭스트룹은 class 를 주로 쓰는 편이라고 한다;

이렇게 굳이 꼭 정해 둘 필요가 있을까? 하는 사람도 있지만, 코드가 몇만줄 넘어가면, 조금이라도 코드에 정보 아닌 정보를 남겨두는 편이, 디버깅이나 유지보수 등에 도움이 될때가 많다. 이런 습관도 있으니 배워두자는 취지에서 곽용재씨가 남겨 주셧던것 같다.


이것만은 잊지 말자!
1. 템플릿 매개변수를 선언할 때, class 나 typename 이나 똑같다.
2. 중첩 의존 타입 이름(nested dependent type name)을 식별하는 용도는 typename 키워드이다.
3. typename 은 템플릿 내부에서만 사용 되고, 초기화리스트 및 기본 클래스 리스트에서는 못쓴다!


관련링크
http://www.ibm.com ... typename keword <-- ibm의 typename 설명서 아, 영문이다 제길
http://pages.cs.wisc.edu/~driscoll/typename.html <-- 여기도 외국 . typename 설명서 .. 영문이다.
http://keiphoto.egloos.com/4182181 <-- 짤막한 .. 영문으로.
http://kukuta.tistory.com/12 <-- 오오! 괜찮은 설명~ 아 여긴 한국
http://codefactory.zc.bz/50 <-- 댓글이 설명이 좋았음
http://rein.upnl.org/wordpress/archives/494 <-- typename 관련 글은 아니지만, 템플린 관련해서 좋은 글
http://www.filewiki.net/tc/34 <-- typename 을 어떤 식으로 사용하는지 소스로 확인 할수 있음
http://dblab.co.kr/119 <-- 역시 마찬가지
http://kldp.org/node/62341 <-- typename 문법 없이 사용할 경우, 질문 내용
http://kldp.org/node/39641 <-- 역시 질답 내요이 좋음

posted by 대갈장군
2014. 8. 7. 22:57 프로그래밍

출처: http://www.cplusplus.com/articles/Gw6AC542/


 1> 왜 우리는 헤더 파일이 필요한가? 


만약 당신이 C++ 처음 접하는 사람이라면 프로그램이 왜 #include 를 하는지와 여러개의 .cpp 파일이 필요한 지 궁금할 것이다. 이유는 다음과 같다.


  1. Compile 속도를 높인다. 당신의 프로그램이 커지면 커질수록 코드의 양도 많아진다. 만약 모든 것이 하나의 파일에 다 들어간다면 작은 변경에도 큰 파일 전체가 컴파일 되어야 하므로 시간이 오래 걸린다. 물론 작은 프로그램의 경우에는 이거 별거 아니지만 적당한 규모 이상일 경우에는 컴파일 타임을 무시 할 수 없다.
  2. 코드가 보다 잘 정렬된다. (Organized) 만약 내가 코드의 각 부분을 컨셉에 따라 잘 나누어 놓으면 어떤 파일을 변경해야 하는지 손 쉽게 알 수 있으므로 잘 정리된 프로그램은 수정이 용이 하다.
  3. interface를 implementation으로 부터 분리 할 수 있다. 즉, 헤더에 프로그램의 interface를 정의해 주고 cpp 코드에 implementation을 하면 아주 깔끔하다.
C++ 프로그램들은 두 단계를 거쳐 생성된다. 첫번째 단계는 각각의 소스 파일들이 각각 컴파일 되는 것이다. 컴파일러는 각각의 컴파일된 소스파일에 대한 intermediate file들을 생성해 낸다. 이 중간 단계의 파일들을 우리는 object file이라고 부른다. 그리고 일단 모든 파일들이 각각 컴파일 된 후에는 모든 object 파일들을 링크하게 된다. 바로 이때 Binary 파일 (exe 파일)이 생성된다.


자, 좀 더 자세히 설명하자면, 각각의 소스 파일이 다른 소스 파일과는 완전히 별개로 컴파일 된다. 이말인 즉슨, a.cpp라는 소스 파일은 b.cpp라는 소스 파일 안에 무슨 일이 벌어지고 있는지 전~혀 모르고 있다는 말이다. 예를 들자면,


// in myclass.cpp

class MyClass
{
public:
  void foo();
  int bar;
};

void MyClass::foo()
{
  // do stuff
}


// in main.cpp

int main()
{
  MyClass a; // Compiler error: 'MyClass' is unidentified
  return 0;
}



위 두 파일의 경우 분명히 MyClass가 myclass.cpp에 선언되었지만 main.cpp에서는 이 클래스에 대해서 알수 있는 길이 없다는 것. 즉, 에러가 난다. 너무나도 당연한 에러다.


여기서 헤더파일의 역활이 드러난다. 헤더 파일은 다른 .cpp 파일들에게 interface를 공개하는 역활을 한다. 물론 implementation은 cpp파일에 남겨두고 오직 interface만 공개하는 것이 헤더 파일의 임무이다. 위의 경우를 예를 들자면 헤더 파일은 다음과 같아야 한다.


// in myclass.h

class MyClass
{
public:
  void foo();
  int bar;
};


// in myclass.cpp
#include "myclass.h"

void MyClass::foo()
{
}


//in main.cpp
#include "myclass.h"  // defines MyClass

int main()
{
  MyClass a; // no longer produces an error, because MyClass is defined
  return 0;
}


자, 이제 main.cpp에서 MyClass 클래스에 대해서 구조를 알 수 있으므로 컴파일에 에러가 나지 않는다.



 2> 확장자에 따른 차이점은?  


그렇다면 여기서 궁금한 것 한가지, .h / .cpp / .hpp / .cc 등등과 같은 서로 다른 확장자의 파일들은 무슨 차이가 있는가?


생각보다 룰이 간단한데, 모든 .h__ 파일들은 걍 헤더 파일이다.

모든 .c__ 파일들은 모두 C++ 소스 코드들이다. 무슨 확장자던 관계없다.

C 코드는 반드시 .c 파일이어야 한다. (요거만 예외적이네)


  3> Include Guards


C++ 컴파일러는 뇌가 없다. 우리가 시키는 대로만 하는 것이 컴파일러이다. 고로 헤더를 두번 include 시키게 되면 이미 정의된 놈이라는 어이없는 에러가 마구마구 터질 것이다. 아마도 어떤 바보가 이렇게 하겠느냐고 질문하겠지만 이런 경우는 의외로 빈번하게 발생한다. 예를 들자면 다음과 같은 경우이다.


1
2
// x.h
class X { };


1
2
3
4
// a.h
#include "x.h"

class A { X x; };


1
2
3
4
// b.h
#include "x.h"

class B { X x; };


1
2
3
4
// main.cpp

#include "a.h"  // also includes "x.h"
#include "b.h"  // includes x.h again!  ERROR 


a.h와 b.h가 둘다 x.h를 include 하는 상황에서 main.cpp에서 a.h와 b.h를 두 번 include 하게 되면 x.h가 두 번 불러지는 셈이된다. 고로 에러...


종종 어떤 사람들은 헤더 파일에서는 include를 하지 말라는 터무니 없는 말을 하는데 이것은 잘못된 지식이다. 헤더에 무슨 파일을 포함하던지 상관이 없다. 단, 두 가지 조건을 만족해야 한다.


1. 반드시 필요한 헤더만 include하라

2. include guards를 통해서 multiple include를 미연에 방지하라


여기서 처음 보는 단어의 등장. Include Guards라는 것인데, 이것은 다음과 같은 것이다.


1
2
3
4
5
6
7
8
//x.h

#ifndef __X_H_INCLUDED__   // if x.h hasn't been included yet...
#define __X_H_INCLUDED__   //   #define this so the compiler knows it has been included

class X { };

#endif 


자, 이렇게 헤더를 둘러쌓으면 x.h가 두번째 include 될때 __X_H_INCLUDED__가 이미 정의되었으므로 헤더가 통째로 스킵되어 버린다. 고로 에러가 없이 지나간다. 고로, 무조건 include guard를 해라, 모든 헤더파일에 해라. 해도 무해하다... 해라 해라 또 해라.



  4> 올바른 include 방법


내가 만드는 클래스는 종종 다른 클래스들에게 의존적인 경우가 많이 있을 것이다. 예를 들자면 파생 클래스의 경우 부모 클래스를 알고 있어야 한다. 부모가 있어야 자식이 나오기 때문이지.


일단 두 가지 종류의 Dependency 가 있는데 다음과 같다.

1. Forward declare가 가능한 것

2. #include가 되어야만 하는 것


자, 예를 들어 Class A가 Class B를 사용하고 있다면 Class B는 Class A의 Dependency 중 한 개다. 그렇다면 Class A 내부에서 B를 어떻게 사용하는 가에 따라 forward declared 될 것인지 아니면 included 되어야 할지 분류가 가능하다. 다음과 같은 룰을 따르면 된다.


1. 만약 A가 B에게로 어떤 reference 도 만들지 않았을 경우: 아무것도 할 필요 없음

2. 만약 B에게 향하는 유일한 reference 가 friend declaration인 경우: 아무것도 할 필요 없음

3. 만약 A가 B 포인터 혹은 레퍼런스를 포함한 경우: Forward Declare

4. 만약 하나 혹은 하나 이상의 함수가 B의 object/pointer/reference를 입력 변수로 가진 경우 (또는 리턴 타입으로 가지는 경우): Forward Declare

5. 만약 B가 A의 부모 클래스라면: #include "b.h"

6. 만약 A가 B의 객체를 포함한다면: #include "b.h"


자, 말로 설명하니 좀 이해가 안된다. 코드로 보면, 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//=================================
// include guard
#ifndef __MYCLASS_H_INCLUDED__
#define __MYCLASS_H_INCLUDED__

//=================================
// forward declared dependencies
class Foo;
class Bar;

//=================================
// included dependencies
#include <vector>
#include "parent.h"

//=================================
// the actual class
class MyClass : public Parent  // Parent object, so #include "parent.h"
{
public:
  std::vector<int> avector;    // vector object, so #include <vector>
  Foo* foo;                    // Foo pointer, so forward declare Foo
  void Func(Bar& bar);         // Bar reference, so forward declare Bar

  friend class MyFriend;       // friend declaration is not a dependency
                               //   don't do anything about MyFriend
};

#endif // __MYCLASS_H_INCLUDED__ 


위에서 설명한 모든 경우에 대해서 잘 보여주고 있다. Forward Declare는 헤더 시작 부분에 먼저 정의 하라는 의미네. 여기서 명심해야 할 점은, Foo class의 경우 MyClass 본체에서 포인터로만 사용되고 있기 때문에 include를 할 필요가 없고 다만 forward declare만 해주면 된다는 점. 최대한 include를 최소화 하는 방향으로 가는 것이 가장 올바른 코딩이다. 무분별한 include는 문제를 야기 할 수 있다는 점 명심하란다.



  5> 왜 위의 방법이 '올바른' 방법인가?


그렇다면 왜 필자가 말하는 방법이 이른바 '올바른' include 방법인가? 일단 해답은 Object Oriented에 있다. 필자가 이루고자 하는 기본적인 것은 myclass.h 파일이 그 자체로 self-contained, 즉, 완전체가 되기 위해서는 위와 같은 법칙을 따라야 한다는 것이다. 만약 다른 소스 파일에서 myclass를 써야 한다면 #include "myclass.h" 단 한줄로 모든것을 끝내버릴 수 있다. 


반면 헤더 파일안에 헤더 파일을 include 하지 않는 원칙을 따르게 되면 다음과 같은 문제가 생긴다. myclass를 사용하려면 myclass가 필요로 하는 모든 헤더 파일을 먼저 추가해야 하는 문제가 발생한다. 다음과 같은 코드를 보자.

1
2
3
4
5
6
7
//example.cpp

//  I want to use MyClass
#include "myclass.h"   // will always work, no matter what MyClass looks like.
                       // You're done
               //  (provided myclass.h follows my outline above and does
               //   not make unnecessary #includes) 


위의 코드를 보면 myclass.h를 포함시켰는데 만약 필자가 제시한 방법대로 했다면 다른 어떤 파일도 추가로 필요하지 않지만 만약 그렇지 않은 경우라면 다음과 같은 이유로 에러가 빠빡 터진다.


Here is an example of why so-and-so's method is bad:

1
2
3
4
5
//example.cpp

//  I want to use MyClass
#include "myclass.h"
   // ERROR 'Parent' undefined 



so-and-so: "Hrm... okay...."

1
2
3
#include "parent.h"
#include "myclass.h"
   // ERROR 'std::vector' undefined 



1
2
3
4
#include "parent.h"
#include <vector>
#include "myclass.h"
   // ERROR 'Support' undefined 



so-and-so: "WTF? MyClass doesn't even use Support! But alright..."

1
2
3
4
5
#include "parent.h"
#include <vector>
#include "support.h"
#include "myclass.h"
   // ERROR 'Support' undefined 


제일 마지막 줄에 Support가 없어서 에러가 터지는 이유는 parent.h에서 support 클래스를 사용하고 있기 때문이란다. 고로 support.h가 parent.h 보다 앞에 include되어야 에러가 안난다. 자, 고로 문제는 헤더 파일에 다른 헤더 파일을 포함하지 않는 경우, 하나의 클래스를 사용하기 위해 해당 클래스가 사용하는 모든 헤더파일을 먼저 불러와야 한다는 단점과 더불어 헤더 파일의 순서 또한 중요하다는 것을 알 수 있다. 이런 문제는 사실 비일비재 하게 발생하고 있다.



  6> Circular Dependencies


1
2
3
4
// a.h -- assume it's guarded
#include "b.h"

class A { B* b; };


1
2
3
4
// b.h -- assume it's guarded
#include "a.h"

class B { A* a };


상호 의존이라 함은 위의 예제 코드 처럼 서로가 서로의 클래스르 사용하는 경우를 말한다. 그냥 보기에는 문제가 없어 보이지만 실제로 컴파일러의 흐름을 따라가보면 아래와 같은 문제가 발생한다.


1
2
// a.cpp
#include "a.h" 


The compiler will do the following:

1
2
3
4
5
6
7
8
9
10
11
12
#include "a.h"

   // start compiling a.h
   #include "b.h"

      // start compiling b.h
      #include "a.h"

         // compilation of a.h skipped because it's guarded

      // resume compiling b.h
      class B { A* a };        // <--- ERROR, A is undeclared 


자 위 코드를 차례대로 따라가 보면, a.cpp를 컴파일 시작하면 우선 a.h 헤더 파일이 선두이므로 a.h를 컴파일 하기 시작하는데 a.h의 선두에는 b.h가 선언되므로 b.h를 컴파일 하려고 하는데 다시 b.h의 선두에 a.h가 선언 되므로 a.h를 컴파일 하려고 하자, include guard가 되어버린 경우, a.h를 스킵하고 지나가버리게 된다. 고로 A라는 클래스를 알 수 없다는 에러가 터진다. 이것이 circular include의 문제점이다. 


이것을 방지하려면 앞서 살펴본 원칙대로 forward declare를 하면 된다. 왜냐면 reference 타입만 사용된 경우에는 forward declare를 통해 이런 circular include의 연결 고리를 끊어줄 수 있기 때문이다.


고로 만약 두 헤더 파일이 서로의 클래스를 직접적인 객체로 사용하는 경우에는 이 circular include 문제를 해결할 수 가 없다. 


1
2
3
4
5
6
7
8
// a.h (guarded)

#include "b.h"

class A
{
  B b;   // B is an object, can't be forward declared
};


1
2
3
4
5
6
7
8
// b.h (guarded)

#include "a.h"

class B
{
  A a;   // A is an object, can't be forward declared
};


헌데 잘 보면 이러한 경우는 구조 자체가 설계가 잘못 된 경우다. 이런것은 infinite recursion을 발생시키게 된다. 고로 올바른 형태는 다른 클래스의 포인터를 사용하는 것이 올바르다. Forward declare가 바로 이런 이유로 사용되는 것이다.



 7> 인라인 함수 


1
2
3
4
5
6
7
8
9
10
class B
{
public:
  void Func(const A& a)   // parameter, so forward declare is okay
  {
    a.DoSomething();      // but now that we've dereferenced it, it
                          //  becomes an #include dependency
               // = we now have a potential circular inclusion
  }
};


자, 위의 코드를 보면 인라인 함수가 circular inclusion 문제를 야기할 수 있음을 알 수 있다. 입력 변수로 선언된 시점 까지는 forward declare가 가능하지만 DoSomething()이라는 함수를 호출하는 순간 객체가 구현되므로 반드시 #include를 해야하는 문제가 발생한다.


고로 이것을 고치려면 아래와 같이 바꾸면 된다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// b.h  (assume its guarded)

//------------------
class A;  // forward declared dependency

//------------------
class B
{
public:
  void Func(const A& a);  // okay, A is forward declared
};

//------------------
#include "a.h"        // A is now an include dependency

inline void B::Func(const A& a)
{
  a.DoSomething();    // okay!  a.h has been included
}


단순히 봐서 이게 뭐가 다르지 라는 생각이 들것이다. 헤더 파일을 인라인 함수 앞에 가져다 놓고 인라인 함수를 클래스 선언에서 분리했다는 점 외에는 차이점이 없다. 그런데... 이것은 완벽하게 안전하다. 왜냐면 Class B가 완전히 정의된 후에 a.h가 include 되었기 떄문이란다. 흠.


그런데 헤더 파일 안에 함수가 구현되어 있는게 보기 싫은 분들은 다음과 같이 해도 된다. 


1
2
3
4
5
6
7
// b.h

    // blah blah

class B { /* blah blah */ };

#include "b_inline.h"  // or I sometimes use "b.hpp" 


1
2
3
4
5
6
7
8
9
10
11
// b_inline.h (or b.hpp -- whatever)

#include "a.h"
#include "b.h"  // not necessary, but harmless
                //  you can do this to make this "feel" like a source
                //  file, even though it isn't

inline void B::Func(const A& a)
{
  a.DoSomething();
}


인라인 함수 부분은 .hpp 파일로 분리해 놨다. 이제 보니 hpp가 왜 hpp인지 알겠다. 헤더 파일이지만 코드를 구현하고 있기 때문에 pp를 붙여 놓은 것이다.



 8> Forward Declaring Templates 


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// a.h

// included dependencies
#include "b.h"

// the class template
template <typename T>
class Tem
{
 /*...*/
  B b;
};

// class most commonly used with 'int'
typedef Tem<int> A;  // typedef'd as 'A' 


1
2
3
4
5
6
7
8
9
10
11
// b.h

// forward declared dependencies
class A;  // error!

// the class
class B
{
 /* ... */
  A* ptr;
};


사실 forward declare은 굉장히 간단한 것이다. 헌데 만약 그것이 template class에 대한 것이라면 문제가 좀 복잡해 진다. 위의 예를 보면 알수 있듯이 template 클래스 Tem이라는 것이 B 클래스를 사용하고 있다. 고로 b.h를 반드시 include 해야 하고, typedef을 통해 A라는 클래스를 Tem<int> 클래스로 정의했다.


그리고 b.h에서 A 클래스를 forward declare 하려고 하면 에러가 빡 터진다.. 왜?


왜냐면 A는 클래스가 이니라 사실 typedef이기 때문이다. 문제는 a.h를 include 할 수 없는데 왜냐면 circular inclusion 문제가 발생하기 때문이다. 그러면 어떻게 해결하느냐?


a.h에서 해준 것 처럼 똑같이 어떤 템플릿 클래스를 typedef 했는지 고대로 알려주면 A라는 클래스를 forward declare 가능하다. 즉, 아래와 같이 친절하게 알려주어야 한다.


1
2
template <typename T> class Tem;  // forward declare our template
typedef Tem<int> A;               // then typedef 'A' 


근데 이건 단순히 class A라는 한 줄에 비해 참 보기 싫다. 그래서 실용적인 해결 방법은 이러한 템플릿 클래스의 typedef를 따로 헤도로 선언하여 포함시키는 것이다. 아래와 같이...


1
2
3
4
5
6
7
8
9
10
//a.h

#include "b.h"

template <typename T>
class Tem
{
 /*...*/
  B b;
};


1
2
3
4
//a_fwd.h

template <typename T> class Tem;
typedef Tem<int> A;


1
2
3
4
5
6
7
8
9
//b.h

#include "a_fwd.h"

class B
{
 /*...*/
  A* ptr;
};


이런식으로 템플릿 클래스의 typedef 또한 forward declare가 가능하다는 점~






posted by 대갈장군
2014. 6. 6. 06:03 풉...

유재석이 향후 10년간 무한 도전을 책임질 사람으로 당선 되었다. 개인적으로 정말 축하해 주고 싶다. :)


하지만 한편으로는 아쉽기도 하다. 우선 정형돈이 당선이 안되서 아쉽고... 정형돈은 사실 PD 같은 직종이 더 잘 맞을것 같다는 생각이 든다. ㅋㅋ


아쉬운 이유 첫번째는 10년이 너~무 길다. 10년간 유재석 체제가 유지된다는 것을 의미하는 것인데... 물론 그를 믿고 신뢰하지만 계속 무엇인가 도전하며 변화하는 모습을 추구하는 무한 도전의 기본 기념에 조금은 반한다는 생각이 든다.


그렇게 긴 임기를 보장 받는 것은 독재에 가깝다고 본다. 물론 유느님을 믿어 의심치 않지만, 고여 있음으로 발생하는 문제는 유느님의 개인적 문제가 아니라 시스템에 의해 발생할 수 있는 문제라고 본다. 


이전에도 유재석의 고립화 문제에 대해서 생각해 본적이 있는데, 유느님이라는 별명과 더불에 국민 MC 그리고 10년간 재신임 받은 무한도전의 1인자라는 너무나 막중한 타이틀이 그의 보폭과 실험 정신을 제한 하지 않을까 하는 걱정을 해본다.


참을 수 없는 가벼움으로 무장한 무한도전이 그리워 지는 것은 나뿐만은 아닐거라는 생각을 종종 한다. 무한도전이 사회적으로 가지는 가치와 의미를 잘 알기에 무한도전과 김태호 PD가 시도하려하는 것들의 의미를 잘 알지만 의미와 재미 둘 다를 얻는 것은 정말 정말 힘들다. 가끔은 하나를 완전히 포기하고 하나만 선택하는 것이 더 쉽고 더 효과적일 수 있다.


무한도전 자체가 매회의 포맷이 정해진 것이 아니므로 이런 선택이 더 쉽다고 볼 수 있다. 김태호 PD와 유느님이 더더욱 이런 부분을 활용해서 한없이 가볍고 말도 안되는 도전을 다시 해줬으면 한다. 너무 커버린 그리고 너무 나이들어 버린 우리 무한 도전 멤버들, 다시 한번 지하철과 대결할 생각은 없을까? ㅋㅋ 이젠 나이들어서 뛰기 힘들꺼야.. 


과거 무한도전이 가진것 없고 젊어서 할 수 있었던 아이템들, 예를 들자면 황소와의 대결, 지하철과의 대결 등등 (목욕탕 물빼기, 차승원 연탄편 등등) 요런 것들이 정말 재미 있었다. 지금 무한도전은 사실 그런 도전을 하기에는 그 무게감이 너무 커져 버렸다는 생각이 든다.


하지만 뭔가 색다른 것이 필요하다. 그래서 많은 사람들이 색다른 것을 제안한 노홍철에게 무려 유느님과 경쟁할 수 있을 만큼의 표를 준것이 아닐까? 


아무튼 글이 좀 산으로 갔는데, 10년은 너무 길다는게 내 생각이다. 그렇다면 유재석은 1인자로 10년간 인정을 하되 국무총리를 매년 혹은 매달 (심지어 매회) 바꿔가며 선출하여 그들에게 일정 부분에 있어 권한을 주고 리드할 수 있게 해주되 유재석이 뒤에서 받쳐 주는 형태라면 좀 더 좋지 않을까 생각해본다.


둘째로 아쉬운 점은, 물론 이건 시간이 좀 걸리는 문제라 힘들었겠지만, 선거 공약을 발표한 후 매주 각 후보가 공략을 실천하는 무한도전을 편성해서 보여 줬다면 어땠을까 하는 생각이다.


나머지 두 후보의 공약도 나름 정말 좋았는데 하는 생각이 많이 들었기 때문이기도 하고 무엇보다 신선했을 것 같다는 생각이 든다. 한표를 얻기 위해, 그리고 당선을 위해 거짓말을 서슴치 않는 현재의 정치판을 조롱이라도 하듯이 보란듯이 노홍철과 정형돈의 공약이 실현 되었다면, 보는 사람들이 더더욱 투표가 왜 권력인가를 잘 알지 않았을까?


하지만 김태호 PD는 지방선거에 맞추어 생각한 아이템이라 이런 형태로 방송을 하기에는 좀 힘든 점이 있었으리라 생각한다. 장기 아이템이 이것 저것 많은데 저것까지 묶어 넣기에는 어려웠겠지.


9년 넘게 무한도전을 빠지지 않고 본 시청자이자 애청자로써 내 생각 주절 주절 써봤는데, 결론은 무한도전이 잘 되었으면 좋겠다는 마음이다. :) 사랑합니다 우리 무한도전! 



posted by 대갈장군
2014. 4. 24. 04:24 카테고리 없음

몇일간 세월호 뉴스를 수도 없이 보고 또 봤다.


마음이 정말 아팠다. 꽃다운 나이의 청소년 300명이 펼쳐야 했을 아름다운 미래가 한 줌의 재가 되어 버릴지도 모를꺼라는 생각이 내 머리 속을 계속 맴돈다.


청소년 뿐 아니라 그 배에 탔다가 실종된 모든 사람. 말도 안되고 어이없는 사고로 목숨을 잃었던 수많은 사람들. 대구 지하철 사건, 삼풍 백화점 사고...


정부의 늦장 대응, 선장의 어처구니 없는 위기 대응, 청해진해운의 어이없는 배 구조 변경, 그걸 또 허락해준 정부 감사 기관, 출항을 허락해준 인천항, 첫 신고를 받은 해경의 대응, 진도 VTS의 업무 소홀, 등등등...


마치 계산된 폭파로 무너지는 건물처럼 이번 사고는 모든 부분의 허술함이 딱딱 맞물려 초대형 인재로 우리의 미래인 수백명의 청소년을 물 속에 떠밀어 넣었다.


같이 일하는 미국 친구들이 물어본다. 뭐라 대답해야 할 지 모르겠다. 부끄럽기도 하다... 우리나라가 이런 나라라고 말하는 것이. 진실을 말해주는 것이 너무나 부끄럽다...


특히 선장이 나몰라라 하고 도망갔다고 하던데 그게 정말이냐고 묻는 친구에게는 얼굴이 화끈 거릴 정도 였다. 


우리가 이렇게 책임감 없고 허술한 나라였던가... 서울에 즐비한 건물과 세계적으로 명성을 떨치는 우리의 대기업들을 보며 우리는 착각을 한다. 우리나라가 이정도라고...


하지만 우리는 세월호를 보며 이렇게 생각해야 한다. 우리는 요정도라고.. 겨우.


사람의 생명을 우습게 보고 경시하는 것은 인간으로서의 존엄성, 가치를 부정하는 것이다. 생명과 직결된 안전을 소홀히 하는 것은 마찬가지로 인간의 존엄성 그리고 가치를 무시하는 것이다.


그러한 기본중의 기본을 지키지 않는 다면 우리나라는 겨우 요정도 밖에 안되는 나라다. 슬프지만 인정하기 싫지만, 그런 나라가 내 나라 대한 민국이다... 







posted by 대갈장군
2013. 10. 2. 05:46 풉...

지난주 무한도전을 참 재미나게 봤다. 특히 마지막과 처음 부분에서 투 유(유재석 & 유희열)가 댄스냐 R&B냐를 놓고 난상 토론을 펼치는 것은 정말 대단했다.


유재석은 과거 힘센 강호동에 대적하는 소심하지만 때때로 용감해지는 쿵쿵따 시절이나 일요일이 좋다에서 이혁재와 신정환과 치고 받는 역활, 또 무한도전 네멋대로 해라 특집에서 박명수 역활 처럼 프로그램을 이끌어가는 엠씨가 아닌 선수 입장으로 예능을 참여하는 경우에 그의 입담과 재치는 개그맨 중에 일등이라고 단언할 수 있다.


유재석의 개그를 110% 활용하려면 강호동 유재석의 관계처럼 힘으로 눌리는 약자의 입장에 있거나, 동거동락 때처럼 게임에 참여하지는 않는 진행자 역활만을 하거나, 일요일이 좋다 처럼 동등한 예능 선수로 출연하거나 인데 무한도전의 현재 포맷은 간간히 이런 경우가 발생하기는 하지만 이것을 잘 활용하지는 않는다고 본다.


무한도전이 조금은 고착화 되는 이유중에 하나가 유재석의 역활이 국민 엠씨라는 타이틀에 메여 있기 때문이라고 본다. 물론 유재석이 엠씨로서 다른 멤버들을 뒤에서 받쳐주고 개그가 흘러갈 수 있도록 윤활유 역활을 매우 잘 하고 있지만 정작 자신이 가지고 있는 개그 본능은 가려질 수 밖에 없다. 나는 개인적으로 유재석의 재치있는 개그가 더 보고 싶다. 


김태호 피디의 의도인지 아닌지는 모르겠지만 마치 거울을 보는 듯한 유희열과의 100분 토론을 통해 유재석이 가지고 있는 고집적인면, 즉, 국민 엠씨라는 타이틀에 걸맞지 않는 모습을 보여주고자 한 것은 아닐까? 지금 현재 무한도전은 유재석이 제일 위에 위치하는 계층적 구조로 굳어졌다고 봐도 무방한데 이것은 유재석의 잘못이라기 보다는 다른 무한도전 멤버들이 구설수와 각종 루머에 휩싸이고 흔들리면서 유재석에게 의지하는 경향이 많아 졌기 때문이라고 본다.


무한도전 멤버들에게도 이미 유재석은 신과 같은 존재가 되어 이미 건드릴수 없는 영역의 사람이 되어 버렸지만 드디어 유희열과 같은 유재석과 똑같은 라이벌 고집쟁이의 등장으로 유재석이 드디어 자신의 토크를 터트릴수 있었던 좋은 기회였다. 과거 이산 특집, 용궁특집으로 이어지는 박명수의 짧았던 반장 기간이 재미 있었던 이유는 유재석이 무너지는 신선함이 있었기 때문이 아닐까? 그때 당시에 최초로 30% 시청률을 돌파 했던 것으로 기억한다.


유재석이 뒤에 서서 멤버들의 개그를 짜내는 구조가 아닌 유재석이 제일 앞에 서서 개그를 치고 나가는 구조로 바뀐다면 무한도전의 또다른 새로운 매력이 뿜어져 나올 수 있지 않을까 하는 생각을 해본다. 


솔직히 유재석이 현역 개그맨으로 더 활동할 수 있는 기간동안 그의 신들린 재치를 더 보고 싶은 것이 오랜 기간동안 그를 지켜본 시청자로써의 희망이기도 하다. 유재석이 굳이 엠씨라는 한가지 타이틀에 머무르지 않았으면 하는 것, 그것이 바램이다. 


유재석은 오랜 시간 무명을 거쳤기 때문에 인기의 소중함을 너무나 잘 알고 있다. 하지만 이것이 어떻게 보면 그를 지금의 타이틀에 묶고 있는 족쇄일지도 모른다. 그가 보여준 오랜 기간동안의 진실된 노력은 이미 모든 사람의 마음에 잘 전달되었다. 이제 좀 바뀐다고 해도 아무도 유재석을 이상하게 보지 않을 것이라 믿어 의심치 않는다. 그런 모습을 김태호 피디가 보여주고자 유희열과의 라이벌 구도를 만들었다면 정말 칭찬해주고 싶다. 대단한 기획이었고 정말 긴장감 넘치는 한 회 였다. 다음회가 또 기대된다. :)


추가로 정형돈은 진짜 개그맨인것 같다. 역시 공채는 다른가보다.. ㅋㅋ 


'풉...' 카테고리의 다른 글

유재석 재선 성공에 대해  (0) 2014.06.06
정글의 법칙 개뻥 소동  (0) 2013.02.09
크레용 미사일 발사도전기  (0) 2010.02.23
개+사람?  (0) 2010.02.19
[아고라 즐보드에서 펌] 너무 급하게 짜장면 먹으려다...  (0) 2007.09.19
posted by 대갈장군