블로그 이미지
대갈장군

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.05.31 10: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#' 카테고리의 다른 글

Asynchronous programming  (0) 2017.05.31
C# 이벤트 - 객체 지향적 메시지  (0) 2010.06.24
C# - 제너릭 (Generic) 에 대한 질문  (0) 2010.06.17
Covariance and Contravariance  (0) 2010.06.17
델리게이트 - Delegate  (0) 2010.06.17
posted by 대갈장군
2016.02.08 09: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.02.05 15: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.02.11 14: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 Language Constructors and Destructors with GCC  (0) 2015.02.11
C Standard Library  (0) 2010.09.24
싱글 스레드에서 멀티 스레드로... 왜 /MD 인가?  (0) 2010.03.10
호출 규약  (0) 2009.07.27
posted by 대갈장군
2014.10.27 23:42 분류없음
가수 신해철씨가 갑자기 세상을 떠났다는 소식을 오늘 접하고 가슴 한켠이 무거워 지는 느낌이 들었다.

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


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


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


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


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







저작자 표시 비영리
신고
posted by 대갈장군
2014.08.27 15: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: