레이블이 Visual C인 게시물을 표시합니다. 모든 게시물 표시
레이블이 Visual C인 게시물을 표시합니다. 모든 게시물 표시

2011년 1월 11일 화요일

BSTR CString char* CComVariant VARIANT

BSTR <-> CString

CString BSTRToCString(BSTR bstr)
{
    char* pbstr;
    USES_CONVERSION;    // <atlconv.h>, convert를 위한 macro
    pbstr = OLE2A(bstr);      // BSTR -> char
    return CString(bstr);
}

BSTR CStringToBSTR(CString str)
{
    return str.AllocSysString();    // CString to BSTR
}


 
BSTR <-> char*

// BSTR to char*
BSTR
 bstr
char* pbstr;
USES_CONVERSION;    // <atlconv.h>, convert를 위한 macro
pbstr = OLE2A(bstr);      // BSTR -> char

// char to BSTR
TCHAR szString[512];
_bstr_t
 bstr_string = szString;
BSTR bstr

*bstr = bstr_string.copy();


 
CString <-> CComVariant

// CString -> CComVariant
CString str;
CComVariant
 out(str.AllocSysString());
or
CComVariant out(str);

// CComVariant -> CString
str = out.bstrVal;


 
VARIANT

VARIANT BSTRToVARIANT(BSTR bstr)
{
    VARIANT var;
    VariantInit
(&var)
    V_BSTR(&var) = SysAllocString(&bstr);
    V_VT(&var) = SysAllocString(&bstr);
    return var;
}

 

2010년 8월 6일 금요일

assert의 사용

ASSERT에 대해 살펴보자.


여러분은 얼마나 많이 ASSERT를 사용하나요? ASSERT를 얼마나 잘 사용했는지를 알아보기

 위해서는 내 코드를 사용한 동료가 입력값을 잘못 입력했을 때, 또는 나의 모듈을 잘못 사용

했을 때 너무 많은 오류 메시지 상자 때문에 작업을 할 수 없다고 불평할 정도가 되어야 한다.

 그만큼 모든 것을 Debug 빌드에서 확인할 수 있을 정도가 되어야 한다. 그렇다면 결국 우리는

 ''가능한 한 많이''라는 말을 머리에 항상 염두해 두어야 하겠다. 하지만 말이 아무리 좋다고

하더라도 사용하지 않으면 전혀 쓸모가 없는 법이다.

 

그래서 오늘은 ASSERT의 의미에 대해서 살펴보고, ASSERT를 사용하는 방법에 대해서

살펴보도록 하겠다. 이번 시간에는 C#을 다루도록 하겠다. 만약 여러분이 이 ASSERT를

 제대로 사용할 줄 안다면 다른 언어(예를 들면, C++ 또는 VB .NET)에도 동일한 ASSERT

 개념이 적용되기 때문에 레퍼런스를 활용하여 쉽게 활용하실 수 있을 것이다.

 

ASSERT는 무엇인가?


ASSERT는 프로그래머가 의도한 대로 입력 값 또는 결과가 정확하게 존재하는지를 확인

하기 위한 작업이다. 여기에서 가장 중요한 단어는 확인이다. 뒤에서 다시 한번 언급하겠지만,

ASSERT는 확인을 위한 과정이지 코드를 수행하기 위한 과정은 아니다. 만약 이에 대한 개념을

 잘못 잡고 있다면 디버그 빌드와 릴리즈 빌드가 서로 다르게 작동하는 결과를 낳을 수도 있다.

ASSERT는 다음과 같이 사용한다.


Debug.Assert(조건, "오류 메시지");

만약 괄호안의 조건이 거짓이면 두번째 인자에 해당하는 "오류 메시지"를 표시한다. 매우 간단

하다. 백명중에 한명꼴로 ASSERT가 거짓인 조건을 검사한다고 이해하는 분도 있다. 그럼 완전히

 반대의 상황이 발생해서 잘못된 값을 넣어주면 잘했다고 칭찬하고 제대로 된 값을 넣어주면 마구

 마구 뭐라고 하는 청개구리 코드가 되어 버린다. 그런 분들이 왜 그렇게 이해했는지 얘기를 해

보니 ASSERT에 대한 개념을 외우려고 하다보니 그랬다라고 한다. 우리는 그냥 상식 선에서 생각

하자. 당연히 그게 맞는지 확인하려고하는거지, 틀린지 확인하지는 않을 것이다.


자, 그럼 간단한 예를 하나 들어보자.

 

public static void MyMethod(Object object)

{

     Debug.Assert(object != null, "object 매개 변수가 null입니다.");

      /* 작업 수행 */
}

 

이 코드에서는 매개 변수로 객체(Object 형식)가 넘어오기 때문에 이 객체를 사용하기 전에 null

인지 아닌지를 확인하는 코드이다. 물론 매개 변수로 넘어온 모든 객체에 대해서 null인지

확인해야 하느냐에 대한 질문에는 상황마다 다르다라고 말해줄 수 밖에 없다. 경우에 따라서

 객체가 null이어도 전혀 문제가 없을 수 있기 때문이다. 하지만 기본적으로 매개 변수로 넘어온

 객체를 이 메서드에서 사용한다라고 가정하고 있기 때문에, 예제와 같은 코드는 매우 흔히 볼 수

 있는 ASSERT의 대표적인 경우라고 보면 되겠다. 자, 그럼 여러분은 이제 매개 변수로 넘어온

객체에 대해서 null인지 아닌지를 구분해야 한다라는 것을 알게 되었다.

 

 

ASSERT를 어떻게 사용할 것인가?

앞서 말했듯이, ASSERT는 굉장히 단순한 개념이다. 프로그래머가 확인하고 싶은 조건을 걸어두고

 그냥 실행만 하면 조건에 맞지 않는 값이 들어왔을 때, 프로그래머가 디버깅을 하지 않아도 오류가

 발생한 위치를 정확하게 알 수 있다. 일반적으로 ASSERT는 다음과 같은 상황에서 사용한다.

1. 내가 작성한 메서드에 넘어온 매개 변수를 확인하고 싶을 때
2. 내가 호출한 메서드에서 반환한 값을 확인하고 싶을 때
3. 내가 호출하는 메서드의 매개 변수를 확인하고 싶을 때

첫번째, ''내가 작성한 메서드에 넘어온 매개 변수를 확인하고 싶을 때''가 ASSERT를 사용하는

가장 흔한 경우이다. 앞에서 소개한 예제 코드도 바로 이런 경우에 속한다고 볼 수 있다.

이 경우에는 대부분 메서드의 코드 시작 부분에서 오류가 발생할 수 있는 모든 상황을 검증하는

 것이 일반적이다. 왜냐하면 시작 부분에서 완벽하게 검증하지 않고 ASSERT 코드가 분산되어

있다면 매개 변수로 넘어온 값이 내가 작성한 코드에 의해서 영향을 받게 되어 ASSERT가 실패할

 수 있기 때문이다. 이런 경우에는 비록 ASSERT로 오류가 발생하는 위치를 찾았다고 하더라도

 디버깅하기 위해서 처음부터 코드를 다시 살펴봐야 하기 때문이다. 따라서 매개 변수는 함수

시작 부분에서 확인한다라고 알아두시면 되겠다.

두번째 ''내가 작성한 메서드에서 반환한 값을 확인하고 싶은 경우''는 우리가 잊어버리기가 굉장히

쉽다. 하지만 첫번째 상황보다는 ASSERT를 해야 한다라는 인식면에서 볼때 더 많이 알려진 경우

라고 볼 수 있다. 사실 프로그래머가 되면서 가장 많이 듣는 말이 리턴 값을 검사해야 한다는 말이다.

 리턴 값을 넘기지 않는 함수들도 많지만, 함수가 제대로 작성되어 있다면 적어도 성공 또는 실패라는

 정도는 알려주어야 한다고 생각한다. 리턴 값이 없는 함수들(리턴 값이 void인 함수들)을 작성한

프로그래머는 거의 모든 상황에서 함수가 성공하며 이 함수를 사용하는 사람은 함수의 성공 여부에

관심을 가질 필요가 없다라고 생각하기 때문일 것이다. 하지만 그건 함수를 작성하는 사람의 생각일

뿐이지, 함수가 리턴값으로 성공이라고 알려준다고 해도 전혀 해가 될것이 없고 언젠가는 함수의

결과를 확인해야 하는 순간이 올 수 있기 때문에 될 수 있으면 리턴 값을 넘기도록 함수를 작성하는

것이 좋다고 생각한다. 결국 내가 호출하는 어떠한 함수도 실패할 수 있고 내가 원하는 대로 함수가

작동하는지 확인해야 할 필요가 있기 때문에 함수의 리턴값은 함수를 호출한 다음 곧바로 검사할

수 있도록 해야한다.

마지막으로 ''내가 호출하는 메서드의 매개 변수를 확인하고 싶을 때''에는 일반적으로 자주 일어나는

 경우는 아니지만, 다른 사람이 작성한 함수가 잘 작동할 수 있도록 배려하는 차원에서 확인한다고

 보면 되겠다. 하지만 내가 아무리 정확하게 검증한다고 하더라도 해당 함수를 작성한 사람이 함수

 안에서 내가 전달한 매개 변수를 검증하는 것보다는 확실하지 못할 것이다. 결국 첫번째 경우를

모두가 지켜준다면 세번째 경우는 필요가 없다.

 
출처 : 네이버 지식인

2010년 7월 28일 수요일

char*, LPCTSTR, TCHAR 의 차이

어떠한 문자열을 처리하는 자료형은 보통 char, wchar, TCHAR 를 사용한다.

쉽게 보면,
char* => LPSTR 라고 생각하면 되고
const char* => LPCSTR 라고 할 수 있다.

가운데 'C' 는 const 의 의미다. 'LP' 는 long pointer 의 의미.
끝에 STR 이라는 의미는 NULL 포인터로 끝난다는 의미. 즉, 마지막 인덱스에는 항상 NULL 이 채워져 있어야 한다는 것.
그래야만 str 관련 함수들이 동작을 하는 것이다. strlen, strcpy, strcmp 등등.. 이것들이 src param 의 끝에 NULL 이 없으면 무지 당황하게 된다.

가운데 'T' 가 들어간다는 것은 TCHAR 이라는 의미.
그렇다면 왜 T를 쓸까??

영문은 1바이트.

그런데, 우리나라 말이나, 중국어 등 영문권이 아닌 제 3세계 언어를 표현하기 위해서는
2바이트가 필요하다.. 이를 위해서 MultiByte 를 사용하기도 하지만 MultiByte 는
어떤 글씨는 1바이트 이고 어떤 글씨는 2바이트여서 메모리 관리가 어렵다.
그래서 나온 것이 wchar 인데,

wchar 는 모든 글씨(영문 포함)가 2바이트로 구성이 된다.
당연히 wchar 는 일반 ascii 타입의 char 보다 메모리 공간이 2배 필요하다.

그런데 본론인 TCHAR 는 무엇이냐?
바로 ACSCII 타입의 일반 char 또는 wchar 라는 의미다.
TCHAR 을 사용하면 char 인지 wchar 인지 구별하지 않고 그냥 코딩을 하면 된다.

TCHAR tch = _T('A'); <=> char ch = 'A';
TCHAR tch = _T('A'); <=> wchar wch = 'A';

배열도 역시.. 그냥 숫자 인덱스로 원하는 글씨의 위치를 추적할 수 있다.

그럼 무엇으로 현재 프로젝트의 TCHAR가 char 인지 wchar 인지를 구별할까?
UNICODE 라는 precompile 상수.

해당 프로젝트 project -> settings 에 _DEBUG 등등 선언되어 있는데, 끝에다
UNICODE 라고 쓰고 컴파일 하면 앞으로 나오는 모든 TCHAR 타입은 wchar 으로 변환해서
컴파일을 시도하게 된다.

가운데 C 다음의 T 는 TCHAR 이라는 의미

당연히 상수로 변환을 했으므로 변수에 값을 대입할 수 없겠다.
( b[3] = _T('A'); <- syntax error  <=> const char str = "ABCD"; str[3] = 'E' ; <- 이때 에러와 같은 이치)

(TCHAR 을 나타는 매크로가 _T 이다. (한문자 _T(' '), 문장 _T(" ") )

2010년 7월 26일 월요일

WPARAM 과 LPARAM

WPARAM은 unsigned int 형이고, LPARAM은 unsigned long 형이다.

특징은, WPARAM는 주로 값들을 넘기는데 사용하고, LPARAM는 값들 뿐만 아니라 포인터를 넘겨줄 때 사용된다.


보통 window procedure 또는 callback function의 인자로, 먼저 나오는 wParam에 자주 쓰이는 정보를 기술하고, lParam에 추가 정보를 기술한다. 지금은 모두 32bit이므로 사용자가 정의하기 나름이다.


윈도우는 메시지 방식으로 프로그램이 진행된다. WPARAM나 LPARAM는 모두 MS에서 지정해 놓은 형식이다. 메시지를 보낼 때 추가로 부가적인 정보를 넣는 것이다. 구체적으로 어떤 정보인지는 아무도 모른다. 이것은 각 메시지마다 다르게 구성되어 있다는 것을 뜻한다.


WM_KEYDOWN을 사용한다면, MS에서 키다운 메시지에 대한 제어를 하고 싶을 땐 이것을 써라. 그리고 부가적인 정보에는 WPARAM와 LPARAM를 사용해라. WPARAM에는 Virtual Key정보를 주고 LPARAM에는 KeyData정보를 넣을 것이니 구현은 사용자가 알아서 제어를 해라. 하는 것과 같다.


W : word

L : long


typedef UINT WPARAM;
typedef LONG LPARAM;

현재는 둘다 32bit 값을 갖는 데이터 타입이다. 그냥 unsigned int, long 이라 해도 상관은 없겠지만, 굳이 이름을 WPARAM, LPARAM이라 한 것은 예전 16bit OS 시절에 이름 붙인 것이 이어져 온 것이다. 당시는 WPARAM은 word 형 파라미터, LPARAM은 long 형 파라미터라는 뜻이어서, (여기서 word형은 2바이트, 참고로 dword(double word)형은 4바이트, byte형은 말그대로 1바이트) 그 때도 위처럼 typedef되었었다. 그런데 32bit OS로 오면서 int형이 4바이트가 되었다. 그런데 typedef은 그대로 두다보니 현재 WPARAM는 이름과는 다르게 4바이트를 갖는 형이되었다. 그러니까 지금은 WPARAM이나 LPARAM이나 4바이트를 갖는 데이터 타입이다. 참고로 windef.h를 보면 여러가지 데이터 타입이 typedef되어 있는 걸 볼 수 있다.

2010년 4월 15일 목요일

전처리 구문에 대해

#define, #ifndef, #ifdef, #endif... 등등이 쓰이는 이유는

컴파일 하기 전에 처리한다는 뜻이죠.

저 구문들을 먼저 처리한 뒤 컴파일이 이루어집니다.

 

이해를 돕도록 한 가지 예를 들겠습니다.

 

//  먼저 아무 의미 없어보이는 HELLO를 define했습니다.

#define HELLO

 

//  여기서 의미가 생기죠. 이 구문을 해석하면 "HELLO가 define이 되었다면.."입니다. 일반 if와 다르지 않습니다.

#ifdef HELLO

printf("Hello\n");

#else                 // 이건 HELLO가 define되지 않았다면..이 되겠죠

printf("Bye\n");

#endif                // end if 즉 if를 끝낸다...뭐 그런 뜻이죠

 

그럼 이 코드가 어떻게 되냐면..

printf("Hello\n");

이거 하나만 컴파일 됩니다.

 

만약에 printf("Bye\n");를 prif"Bye\n"); 이 따위로 써도 컴파일 시 에러가 안납니다.

 

그럼 왜 printf("Bye\n"); 같이 쓰지도 않을 코드를 써서 낭비하는가...

 

프로그램을 많이 짜다가 보면

어쩔 때는 printf("Hello\n");로 컴파일 해야하고

어쩔때는 printf("Bye\n");로 컴파일 해야할 때가 있습니다.

이게 한 줄이면 모르겠지만...

100줄 1000줄..이러면 일일이 컴파일 할 때마다 주석처리하기 엄청 짜증나겠죠.

이럴 때 #define HELLO만 살짝 손보면 컴파일 결과가 사사삭 바뀌니 엄청 편리합니다.

 

 

그럼 질문하신 부분중에

#ifndef TURBOC_HEADER

#define TURBOC_HEADER

 

ifndef는 if no define...if not define??? 뭐 그런겁니다.

그래서 TURBOC_HEADER가 define되지 않았다면 TURBOC_HEADER를 define해라...

뭐 그런겁니다.

 

이게 왜 필요하냐면..

프로젝트가 커질 수록 헤더파일을 여기 저기서 가져다 쓰게 됩니다.

그럼 컴파일시 같은 내용을 반복해서 컴파일 하게 되고 에러가 발생할 수도 있습니다.

질문하신 코드의 경우 #include문이 여러개 있는데 한번만 하면 될 일을 여러번 할 수도 있다는 겁니다.

분명히 에러의 소지도 있고요. (아주 쉬운 예로 전역변수를 헤더에 선언했다면 바로 에러나겠죠)

 

그래서 TURBOC_HEADER가 define되지 않았다면 define하고 헤더를 컴파일하고..

또 다른 곳에서 헤더를 컴파일 하려고 하면 이미 TURBOC_HEADER가 define 되었으니까

그냥 넘어가겠죠.

MFC같은 것을 할 때는 이런 구조가 기본적으로 생성됩니다. 그만큼 꼭 필요한 구조입니다.

 

#endif는 #ifndef와 쌍이고요..

 

출처: http://kin.naver.com/qna/detail.nhn?d1id=1&dirId=1040101&docId=68987639&qb=YysrIOunpO2BrOuhnOqwgCDrqLzqsIDsmpQ=&enc=utf8&section=kin&rank=1&sort=0&spq=0

2010년 4월 6일 화요일

[MFC] DoModal() 이란??

비주얼 C++에 다이얼로그는 두가지 종류가 있습니다.

 

한가지가 모달리스 다이얼로그이고...

또 다른 한가지가 모달 다이얼로그입니다.

 

이 두 다이얼로그의 차이는

모달 다이얼로그의 경우는 다이얼로그가 생성되면

자신을 생성시킨 다이얼로그가 활성화되지 않습니다.

 

하지만 모달리스 다이얼로그는 다이얼로그가 생성되더라도

자신을 생성시킨 다이얼로그로 마음대로 왔다 갔다 할 수 있습니다.

 

사실 이게 정확한 설명은 아니지만 대략의 차이점이라고 생각하시면 될 것 같고..^^;;

 

 

DoModal 함수에 대한 간단한 예제를 들면...

 

일단 모달 다이얼로그로 클래스가 생성되어 있다고 하죠...

그 클래스 이름을 "TestClassModal" 이라고 했을 때...

 

다이얼로그를 생성시킬 곳에서는

일단 해당 다이얼로그 클래스의 해더 파일을 포함시켜야 할 거구요..

 

그 다음에는 그 클래스를 선언해 주어야 겠지요~^^

 

TestClassModal   dlg;

 

if (dlg.DoModal == IDOK)   // 다이얼로그가 종료될 때 반환되는 값을 검사, OK 버튼으로 종료되었을 경우 IDOK를 반환

{

      // OK 로 다이얼로그가 종료되었을 경우의 작업 코딩

}

else

{

       // 그렇지 않을 경우의 작업 코딩..

}

 

 

DoModal 함수는 보통 위와 같은 형태로 사용됩니다.

[MFC] CDialog::EndDialog

1. 함수의 원형
 
    void EndDialog(int nResult);
 
 
2. 함수의 기능
 
    CDialog 대화상자를 이용해서 프로그램 하는 경우, 대화상자를 종료시킬 때, 어떤 함수를
    사용해야하는지 고민하는 경우가 종종있습니다. 일반적으로는 OnOK, OnCancel 을 호출해서
    많이 종료시키는데, EndDialog 함수도 대화상자를 종료시킬 때 사용합니다.
 
 
3. 함수의 매개변수에 대한 설명
 
    3.1 nResult
 
        대화상자는 보통 DoModal 이라는 함수를 이용해서 대화상자를 출력하게 됩니다. 그리고
        대화상자가 종료될때 DoModal 함수를 빠져나오게 됩니다. DoModal 이라는 함수는 종료될 때,
        정수형 값을 반환하는데 그 값을 이 변수를 이용해서 지정할 수 있습니다.
 
        일반적으로 대화상자가 종료되었을때, 확인 버튼을 눌러서 종료했는지 또는 취소버튼을 눌러서
        종료했는지를 구분하려면 아래와 같이 코드를 작성하시면 됩니다.
 
        MyTestDlg ins_dlg;
        int return_value = ins_dlg.DoModal();
        if(IDOK == return_value){
            // 확인을 눌러서 대화상자가 종료된 경우를 처리 한다.
        } else if(IDCANCEL == return_value){
            // 취소를 눌러서 대화상자가 종료된 경우를 처리 한다.
        }
 
        위 코드에서 보시는것과 같이 DoModal 함수는 IDOK 또는 IDCANCEL 값을 반환하게 됩니다.
        왜냐하면, 대부분 OnOK, OnCancel 함수로 종료시키기 때문입니다. 하지만, 좀더 효과적으로
        프로그램을 하려고 하다보면 DoModal 함수가 또다른 값을 반환해야 하는 경우도 있습니다.
        이럴때, EndDialog 함수를 호출하고 이 매개변수에 DoModal 이 반환할 값을 명시하면 됩니다.
 
        EndDialog(5);
 
        이렇게 종료하면 DoModal 함수는 5의 값을 반환하게 됩니다.
 
 
4. 함수의 주의사항
 
    EndDialog 함수는 즉각적으로 대화상자를 닫는 함수는 아닙니다. 따라서 EndDialog 함수 아래에
    코드가 있다면 그 코드를 수행합니다. 그리고 EndDialog 함수를 사용한 함수가 종료되는 시점에
    대화상자를 종료 시킵니다.
 
    // m_member_data는 MyTestDlg 클래스의 멤버 변수이고 1의 값을 가진다고 가정하겠습니다.
    void MyTestDlg::CloseMyDlg()
    {
        EndDialog(5);         // 대화상자가 종료되어야 한다는 값을 내부적으로 설정.
                                    // 하지만 이 순간에 종료되지는 않는다.
 
        m_member_data++; // 값을 2로 증가시킨다.
    }  // CloseMyDlg 함수가 종료되면서, CDialog 클래스는 종료상황이 체크된것을 인지하고
       // 대화상자를 종료합니다. 

2010년 3월 16일 화요일

LPSTR, LPCSTR, LPCTSTR…

LPSTR :  A 32-bit pointer to a character string.
         이 데이터 형같은 경우 예전에 우리가 사용하던 방식으로 선언하면
         char *
         와 같은 경우이죠. 유니코드를 지원하지 않기 때문에 각각의
         문자는 1바이트를 가집니다.

LPCSTR :  A 32-bit pointer to a constant character string.
         이 데이터 형같은 경우 예전에 우리가 사용하던 방식으로 선언하면
        const char *
         와 같은 경우이죠. const 형이 어떤건지는 아시죠? 이것역시
         유니코드를 지원하지 않기 때문에 각각의 문자는 1바이트를 가집니다.        

LPTSTR : A 32-bit pointer to a character string that is
           portable for Unicode and DBCS
          이 데이터 형같은 경우, 윈도우즈가 유니코드를 지원하면서 새로
          생긴형식으로 풀어쓰면 다음과 같습니다.
          TCHAR *
          이 형은 유니코드를 지원하기 때문에 각 문자가 2바이트를 가지게
          됩니다. 실제로 유니코드를 지원하는 프로그램을 작성하지 않는
          이상 거의 사용하지 않습니다.

LPCTSTR : A 32-bit pointer to a constant character string that
            is portable for Unicode and DBCS.
          이것 역시 윈도우즈가 유니코드를 지원하면서 새로 생긴형식으로
          풀어쓰면 다음과 같습니다.
         const TCHAR *
          마찬가지로 유니코드를 지원하기 때문에 한문자가 2바이트 크기를
          가지게 됩니다.

함수뒤에 const 를 붙이는건 클래스 멤버함수내에서 this 사용을 제한하기 위해서 사용합니다.
일반적으로 멤버 함수내에서 사용되는 this는 해당 클래스가 MyClass 인 경우에,

MyClass * const this;

와 같이 내부적으로 선언되어서 사용되는데, 함수의 뒤에 const를 붙이면

const MyClass * const this;

와 같이 속성이 바뀌어져서 사용됩니다. 즉 내부에 사용되는 데이터 멤버가 이 함수에서는 모조리
읽기 전용으로 바뀌어져 버리는거죠…

this 변수가 가리키는 멤버들이 모두 쓰기 불능으로 되어버리기 때문에… 즉, this는 우리가 직접
변경할수 없기 때문에 함수뒤에 const를 붙여 간접적으로 this를 const형으로 변경하는 기능을
구현한겁니다.

2010년 2월 19일 금요일

C++ Coding Conventions

version      : v0.6

최종수정일 : 2007.10.15

최종수정자 : 이주현

 

형상품목별 적용언어#

  • C++을 사용한다.

간격#

  • 프로그램에서 서로 다른 기능을 수행하는 블록이나 서브루틴 사이에는 1행 이상의 빈 줄을 삽입한다.
  • 각 블록이나 서브루틴은 적절한 탭 간격으로 들여쓰기를 하고, 블록의 open문과 closer 괄호는 열을 맞추어 프로그램의 이해와 식별을 쉽게 한다.
  • 하나의 탭은 공백 4칸에 맞도록 간격을 설정한다.

문자 사용의 규칙#

  • 소스 코드를 프린터로 출력할 때는 크기 9의 Courier New 폰트를 사용한다.
  • 기술자료 작성 및 규격화에 사용되는 워드프로세서는 한글로 하며, 문자는 굴림체, 크기는 11, 줄 간격은 180%, 문단 위 간격은 5(pt)로 한다. 기타 언급되지않은 사항은 별도의 기술자료 작성 지침서를 따른다.

파일 레이아웃#

  • 파일이름은 첫문자와 각 단어의 첫자는 대문자로 하며, 나머지는 소문자로 한다. ex) ClientSocket.h, ClientSocket.cpp
  • 각각의 소스 파일과 헤더 파일은 아래의 템플릿을 참고한다.

헤더 파일 레이아웃#

  • 인클루드 중복 방지용 #ifdef, #endif 문은 다음과 같이 작성한다.
  • 레이아웃은 아래의 예제와 같은 순서로 기입한다. 하지만, 필요에 따라 각각의 레이아웃에 다른 지정자를 넣거나 선언을 할 수 있다.
  • 아래 예제의 파일의 이름은 Header.h 이다. 주석에 대한 자세한 설명은 다음 장에 설명되어 있다.
  1. /**
     * @file  
     * @author  
     * @version     
     * @brief     
     *
     * @date       
     * @todo     
     */
    #ifndef _HEADER_H_
    #define _HEADER_H_
    /********************************** includes **********************************/

    /*********************************** defines **********************************/

    /********************************** typedefs **********************************/

    /***************************** class declarations *****************************/

    /****************************** extern variables ******************************/

    /*********************** inline function declarations *************************/

    /*************************** function declarations ****************************/


    #endif

소스 파일 레이아웃#

  • 소스 파일에 대한 레이아웃은 아래와 같다. 주석에 대한 자세한 설명은 다음 장에 설명되어 있다.
  1. /**
  2.  * @file  
  3.  * @author  
  4.  * @version  
  5.  * @brief        
  6.  *
  7.  * @date        
  8.  * @todo       
  9.  */
  10. /********************************** includes **********************************/
  11. /*********************************** defines **********************************/
  12. /********************************** typedefs **********************************/
  13. /****************************** global variables ******************************/
  14. /****************************** local variables *******************************/
  15. /***************************** class definitions ******************************/
  16. /**************************** function definitions ****************************/

 

주석에 대한 표준#

  • 기본적으로 doxygen 스타일을 준수한다.

    • @tag + [tab] + 내용 으로 서술한다.
    • 리스트 사용시 공백은 [tab]으로 구분을 하며, 한단계 하위로 내려갈 때 마다 [tab]을 추가한다.
  • 프로그램의 파일 의 주석

    • header 및 source 주석은 각 프로그램 파일의 시작 부분에 위치하며, 다음 사항을 포함한다. 볼드체는 반드시 기입해야 하는 내용이며, 나머지는 상황에 맞추어 추가/삭제 할 수 있다.

      • 파일명 : 현재 파일 명
      • 작성자 : 홍길동
      • 버전 번호 : V0.0(변경사항이 많거나 중요한 경우 소수점 앞의 정수를 1씩 증가시키고, 변경사항이 적거나 중요하지 않은 경우 소수점 뒤쪽의 값을 1씩 증가시킨다.)
      • 파일 설명 : 프로그램에 대한 요약 설명. 구성 모듈, 사용된 알고리즘, 가정, 제한사항, 부작용 등을 기록
      • 변경 이력 : 최초 파일 생성일자경 이력 : YYYY.MM.DD + [tab] + 수정자 이름 + [tab] + 간략한 수정 사항 기록
      • 해야 할 일 : 개선 사항이나 앞으로 추가되어야 할 내용을 기록한다.

 

  1. /**
     * @file       TBCodingConvention.h
     * @author     홍길동
     * @version   V0.1
     * @brief     파일에 대한 간략한 설명을 기술한다.
     *
     * 상세한 설명은 이곳부터 시작한다.
     *
     * @date      
     *   - 2007.10.13   이주현   알고리즘 수정
     *   - 2007.09.10   이주현   먼가를 수정하였음
     * @todo      
     *   - 유니코드에 대비할 것
     *   - 버그를 잡을 것
     */

 

 

  • 클래스에 대한 주석

    • 사용자가 개발한 클래스에 대한 주석은 클래스에 대한 기능을 설명하기 위해 작성되는 것으로서 모든 클래스에 적용되어야 한다. 클래스가 선언된 곳 바로 위에 사용한다. 볼드체 부분은 필수적으로 기입해야 하며, 그외의 항목은 선택적으로 기입한다.
    • 기술 순서는 다음과 같다.

      • friend 클래스
      • 클래스 내부에서 쓰이는 typedef
      • enum 타입 변수
      • public -> protected -> private 순으로 기술하며, 각각 내부에서는 attribute -> contructor & destructor -> operation 순으로 한다. attribute와 operation의 경우, instance 용을 먼저 기술하고 class 용을 나중에 기술한다.
    • 헤더 파일에서는 각 메쏘드에 대한 설명은 하지않고, 소스파일에서 자세히 기술한다.
    • public, protected, private 등의 지시자는 클래스 블럭({) 와 같은 레벨로 들여쓰기를 한다.

 

  1. /**
     * @brief     클래스에 대한 설명을 기술한다.
     *
     * 보다 자세한 설명을 기술한다.
  2.  * @todo
     */
    class CTBCodingConvention
    {
    /// friend 클래스
        friend class CFriendCodingConvention;
     
    /// typedef 타입은 언더스코어를 가지는 대문자이다.
        typedef unsigned int TB_BYTE;

  3. /// enum 타입, EState에 대한 간략 설명
        enum EState
        {
            STATE_APPROVED = 0,     ///< STATE_APPROVED 에 대한 설명
            STATE_REJECTED          ///< STATE_REJECTED 에 대한 설명
        };
     
    public:
    // Attributes 
        // 상수는 모두 대문자이며, 언더 스코어를 가지는 대문자를 사용한다.
        string strCodeName;
        static const char *BASE_NAME    ///< BASE_NAME 상수에 대한 설명

  4. // Constructor & Destructor
        CTBCodingConvention();
        ~CTBCodingConvention();
           
    // Methods
        // 멤버 함수는 대소문자를 섞어 쓰며 동사로 시작하며 첫문자는 소문자이다.
        void printTBStandard(void);
        // Get 또는 Set 멤버함수는 설정하거나 얻어오는 멤버 변수를 이름으로 사용하며, Get 또는 Set 키워드로 시작한다.
        void setNameOfStandard(string name);
        string getNameOfStandard(void);
        static int* getInstance();
    protected:
    // Attributes
       
    // Constructor & Destructor
        int nAccessibleByDerivedClasses   ///< 멤버 변수에 대한 설명
       
    // Methods
       
    private:
    // Attributes
        string strNameOfStandard          ///< 멤버 변수에 대한 설명
        CInfo* pBaseStandard;             ///< 간략한 설명
        static CTBCodingConvention *pInstance;    ///< 간략한 설명

  5. // Methods
        void setInternalHandler(CTBCodingConvention *t_variable);
    };

 

  • 함수에 대한 주석

    • 사용자가 개발한 Function에 대한 주석은 함수 기능을 설명하기 위해 작성되는 것으로서 모든 함수에 적용되어야 한다. 다음과 같은 정보들이 표현되어야 한다. 함수 바로 위 상단에 다음과 같은 사항을 기록한다. 볼드체로 된 항목은 필수적으로 기입해야 하며, 그 외의 항목은 선택적으로 기입한다. 단 인자가 없거나, 리턴 타입이 void일 경우는 생략할 수 있다.

      • 함수 설명 : 함수의 목적을 적는다. 필요한 경우 알고리즘의 설명을 기입한다.
      • 작성자 : 최종 개정한 사람의 이름을 적는다.
      • 최종 수정일 : 최종 개정한 날짜를 적는다.
      • 입력 데이터 : 입력되는 매개변수를 적고, 값의 범위가 중요한 경우 범위를 기입한다.

        • @param + [tab] + 인자이름 + [tab] + 인자에 대한 설명
      • 출력 데이터 : 반환 값의 간략한 설명을 적고, 값의 범위가 중요한 경우 범위를 기입한다.

        • @return + [tab] + 리턴타입(e.g. int, long, so on) + [tab] + 리턴 값에 대한 설명
    • 주석은 반드시 해당 함수 위에 기술되어야 하며, 빈 줄을 삽입하지 않는다.

 

  1. /**
     * @author     함수를 작성한 사람
     * @brief     함수에 대한 설명을 기술한다.
     * @param      name 이름에 대한 설명을 기술한다. 인자가 없다면 생략해도 된다.
     * @return     리턴하는 값에 대한 설명을 기술한다. 리턴하는 값이 없다면 생략해도 된다.
     * @exception  함수에서 발생할 수 있는 예외상황에 대해 기술한다.
     */
    void TBCondingStandard::SetNameOfStandard(string name)
    {
        // 함수의 바디
    }

 

  • 중요한 블록(Block)에 대한 주석

    • 프로그램에서 중요한 코드 블록들은 블록의 상단에 기능 설명을 하는 주석이 반드시 선행되어야 한다.
    • 함수 내부의 소스 코드에 수행하는 작업과 그 순서가 표현되도록 다음과 같이 주석을 작성한다.  형식은 '/// 숫자. 기술내용' 으로 구성된다.

 

  1. /// 1. XXX를 수행한다.
    ...
    /// 2. XXX를 수행한다.
    ...
    /// 3. XXX를 수행한다.
  2. ...

 

  • 라인(Line)에 대한 주석

    • Doxygen으로 파싱되길 원하지 않으면서, 프로그램의 설명이 필요하다고 판단되는 연산(Computation)문과 할당(Assignment)문에는 '//' (슬래시 두개) 주석을 넣는다.
    • 라인 주석은 하나의 라인 뒤에 위치하며 해당 라인의 행위나, 선언된 변수의 설명을 기입한다. 단, 실행문 뒤에 주석을 기입할 수 없는 경우는 실행문 위에 주석을 기입한다.
    • 선언된 변수 값의 범위가 중요할 경우 범위를 기입한다.

 

  1. int nId;           // The player id < MAX_PLAYER

    // 사격이라는 윈도우 화면을 달도록 메시지 전송
    TheApp.m_pMainWnd->SendMessage(WM_CLOSEFRAME, (unsigned int)"사격");
  2. SelectSides(nId);  // Choose partners and positions

 

  • 매크로에 대한 주석

    • 다음 세가지 중 한가지 방법을 택하여 주석을 단다.
  1. /// X와 Y 중 큰 값을 리턴하는 매크로
    #define MAX(X, Y) ((X) > (Y) ? (X) : (Y))

    #define MAX(X, Y) ((X) > (Y) ? (X) : (Y)) ///< X와 Y 중 큰 값을 리턴하는 매크로

    /// @brief X와 Y 중 큰 값을 리턴하는 매크로
    /// 두 인수를 같은 타입으로 줘야 안전할 것이다.
    #define MAX(X, Y) ((X) > (Y) ? (X) : (Y))

 

  • 주석에 리스트 만들기

    • 칸을 맞추어 '-' 를 사용하면 된다. 여기서는 [tab]으로 들여쓰기를 맞춘다.
  1. /**
     * @brief    간단한 설명
     *
     * 자세한 설명은 이곳에서 부터 이다.
     *  - <b>10 11 12</b>
     *   모든 것이 순서대로 왔다. LastID는 매번 증가한다.
     *
     *  - <b>11 10 12</b>
     *    먼저 LastID가 11로 증가한다. 9->11로 변했으므로, 10의 값이 OmitList에
     *    추가된다. 다음으로 10이 오면 리스트에 존재하므로, 실행한다. 그리고
     *    리스트에서 데이터를 삭제한다. 12는... 정상적이므로 실행한다.
     *
     *  - <b>10 10 11</b>
     *    제일 처음 오는 10은 당연히 정상적으로 실행된다. 두번째로 오는 10은
     *    LastID값에 의해서 무시된다. 11은 다시 정상적으로 실행된다.
     */

 

명칭에 대한 표준#

  • 객체의 역할을 나타내는 Full Name의 표준 군사용어를 사용한다.
  • 언더바("_")로 시작되는 이름은 사용하지 않는다.  단, 단어 전체를 대문자로 표기하며 2개 이상의 단어를 조합하여 사용하는 경우 각 단어 사이에 밑줄(??_??)로 구분하여 사용한다.
  • 변수명

    • 변수의 명명은 '범위(전역일 경우에만 g_ 을 붙이고, 멤버 변수일 경우 m_을 붙인다.) + 접두사 + 설명적인 본체'로 이루어진다. 변수가 지역 변수인 경우의 명명은 범위를 생략한다.
    • 범위 : 변수의 전역 변수 여부를 나타낼 수 있도록 한다.

      • 예제 : 전역(g_), 멤버(m_)
    • 접두사 : 변수명에 그 변수의 타입을 나타내는 접두사를 붙여 표기하는 헝가리언 표기법을 사용한다.
    • 설명적인 본체

      • 첫글자는 대문자로 시작하며 나머지는 소문자를 사용한다. 두 단어 이상일 경우 각 단어의 첫 글자는 대문자로 쓴다.     g_iCountButton, m_pInstance
    • 포인터의 경우 p+[해당형 접두어]를 붙여 사용한다. m_piVar, m_pstrName
접두어 접두어의 의미
a 배열
b bool 형 변수
ch char 형 변수
uch unsigned char 형 변수
d double 형 변수
f float 형 변수
h handle 형 변수
l long 형 변수
p pointer 형 변수
pfn 함수 포인터
s short 형 변수
us unsigned short 형 변수
st 구조체 형 변수(객체)
str string 형 변수
sz NULL로 끝나는 문자열
i int 형 변수
ui unsigned int 형 변수
n 개수를 의미하는 int 형 변수
e enum 형 변수
c class 형 변수
un UNION 형 변수(객체)
pv void *
vec vector 형 변수
mtx mutex 형 변수
sem semaphore 형 변수
mq message queue 형 변수

 

  • 함수명

    • 첫글자는 소문자로 시작하며 단어의 시작은 대문자로 나머지는 소문자를 사용한다.

      • 단 윈도우용 프로그램을 작성할 때는 윈도우 API의 작명법을 따라, 함수를 대문자로 시작한다.
    • 동사 + 명사의 순서로 조합한다.      countBattalion()
  • 클래스명

    • 클래스를 대표할 수 있는 단어를 사용하고, 첫 문자는 대문자 'C'로 시작한다.
    • 클래스 객체의 변수명은 소문자 'c'로 시작하며, 두 단어 이상일 경우 각 단어의 첫 글자는 대문자로 쓴다.

 

  1. class CButton
    {
        ...
    };

    CButton cButtonMouse;

 

  • 상수명

    • 모두 대문자를 사용하며, 두 단어 이상일 경우 언더바("_")로 단어를 구분한다.
  1. MAX_NIFV

 

기타 코딩 스타일#

  • 기본 규칙

    • 하나의 행위는 하나의 라인으로 구성한다.
    • 대괄호('{}')를 이용하여 블록을 구성할 때는 새로운 줄에 블록을 기입한다.
  1. // 지양
    while (m_nCount > m_nLineLen) newLine();

    // 권장
    while (m_nCount > m_nLineLen)
    {
        newLine();
    }

 

  1. // 지양
    while (nCount > nLineLen) {
        if (m_nId>1) {
            m_nPlayer --;
        }
        else
  2.     {
            m_nPlayer ++;
        }
    }

    // 권장
    while (nCount>nLineLen)
    {
        if (nId>1)
        {
            nPlayer --;
        }
  3.     else
        {
            nPlayer ++;
        }
    }

 

  • 상수의 선언

    • 상수의 선언은 하나의 파일 또는 파일의 상단에 모아서 선언할 수 있도록 한다. 또한 이때 수직 정렬을 할 수 있도록 한다.
  1. #define PACKET_SIZE     20    ///< chars in message packet
    #define BUF_SIZE_MAX    256    ///< max allocatable space for buffer

 

  • 분기 문 작성

    • 분기 문의 등식에 상수를 사용할 때에는 반드시 상수를 왼편에 두어 '='가 빠졌을 때 발생할 수 있는 오류를 컴파일러가 찾을 수 있도록 한다. 또한 이는 찾고자 하는 값을 즉시 발견할 수 있도록 해준다.
  1. if ( 6 == nErrorNum )

 

  • 'if' 구문 작성

    • if 구문을 작성할 떄는 반드시 else 문을 이용하여 예외 처리를 할 수 있도록 한다. else 문에 내용이 없으면 주석을 사용하여 내용이 없음을 인지할 수 있도록 한다.
  1. if ( nId > i )
    {
        nPlayer --;
    }
  2. else
    {
        // do nothing
    }

 

  • 'switch' 문 작성

    • 각각의 case 문에는 break 이용하여 격리 시킬 것을 권장하며, default 문을 사용하여 예외처리를 한다.
    • case 의 경우 switch 지시자보다 한단계 낮은 레벨의 들여쓰기를 사용한다.
  1. switch ( nCount )
    {
        case NUM1:
            // do something
            break;
        case NUM2:
            // do something
            break;
        default:
            // do something
    }

 

  • 'goto' 문 작성

    • goto 문을 부득이 사용해야 하는 경우에는 반드시 그 목적과 사용범위를 명시한다.

 

관례#

  • C/C++ 은 하나의 문장을 아주 복잡하게 만들 수 있다. 이는 프로그램의 수행시간 단축, 사용 공간의 축소의 이득은 있으나, 가독성을 해친다. 따라서 복잡한 하나의 문장은 여러 개의 문장으로 분리해서 작성하길 권장한다.
  • 대입 연산자 사용

    • 하나의 문장에는 하나의 대입연산자 만을 사용한다. 여러 개의 대입연산자를 사용함으로써 가독성을 해치기 때문이다.
    • 분기 문의 등식/부등식 내에서는 대입연산자의 사용을 피한다.
  • 매개변수

    • 함수에 매개변수를 전달할 때는 연산자의 사용을 자제한다.
    • if, for, while 등의 조건절에서 연산자의 사용을 자제한다.
  1. // 지양
    plotPoint(mouse_x + prev_mouse_x * x_factor, mouse_y + prev_mouse_y * y_factor);

    // 권장
    new_x = mouse_x + prev_mouse_x * x_factor;
    new_y = mouse_y + prev_mouse_y * y_factor;
    plotPoint(new_x, new_y);

    // 지양
    if ( (mouse_x + prev_mouse_x * x_factor ) < max_x )
    {
        // do something
    }
       
    // 권장
    new_x = mouse_x + prev_mouse_x * x_factor;
    if ( new_x < max_x )
    {
        // do something
    }

 

  • 구조체 및 공용체

    • 구조체 및 공용체의 선언은 typedef 문을 사용한다.
    • 구조체 형식의 자료형은 St 를 접두어로 사용하고, 공용체 형식의 자료형은 Un을 사용한다.
    • 구조체의 객체명은 소문자 'st'를 접두사로 붙여서 사용한다.
    • 공용체의 경우 객체명은 소문자 'un'를 접두사로 붙여서 사용한다.
  1. typedef struct
  2. {
  3. char name[40];

  4. int age;

  5. dougle height;

  6. } StPerson;
  7. StPerson stP1

 

  • 열거형

    • 열거형의 명은 대문자 'E'를 접두사로 붙여서 사용한다.
    • 열거형의 객체명은 소문자 'e'를 접두사로 붙여서 사용한다.
  1. typedef enum
  2. {
  3. WHITE = 0,
  4. BLACK
  5. } EColor;
  6. EColor eColor1;

 

예제 파일 #

2010년 2월 4일 목요일

C++ const 키워드

 

멤버함수에서의 const 의 특성(2) C++  2006/12/16 18:58 http://blog.naver.com/hacker79kr/120032397120
 
앞서...값을 리턴할 때의 멤버함수에 붙는 const 에 대해 알아보았다...

이번에는 레퍼런스를 리턴하는 경우에 대해 한번 살펴보자...

 

레퍼런스를 리턴하는 경우

 

class Test{
private :
 char Ch;
public :
 Test(const char c) { Ch = c; }

 char& GetName() { return Ch; }   <== 레퍼런스를 리턴(복사본을 리턴하지 않는다)
};

 

int main()
{
   Test Ob('C');

    Ob.GetName() = 'A';   <== 호출 결과로 원본이 넘어오므로 또다른 대입이 가능하다...

    cout<<Ob.GetName()<<endl;
    return 0;
}

 

=> 위에서 보다시피...레퍼런스(원본 대상체) 가 리턴되므로,,,컴파일러 내부적으로 복사과정을 거치지 않는다...

     주소값과 레퍼런스는 l-value (즉,,,메모리를 점유) 가 될 수 있으므로 = 대입연산자의 좌측에 올 수가 있다...

 

그럼,,,다시  char& GetName() { return Ch; } 함수를 가지고 장난을 쳐보자...

 

char& GetName() const   <== 수정불가인 함수이다...(컴파일 되지 않는다...)

{

   return Ch;

}

 

=> 수정불가인 함수에서 원본 대상체를 리턴하고 있다...

    그러나,,,값을 복사해서 리턴하는 것이 아니기 때문에...const 성질을 가지고 있어서...

    수정 불가인 멤버변수 그 자체가 리턴되어...l-value가 될 수 있는 자격을 가지고 있다.

    즉,,,리턴받는 쪽에서도 수정하지 못하게 만들어야 한다...

    이러한 이유로....멤버함수 뒤에 const 가 붙을 경우에는 리턴되어서도 그 성질을

    유지해야 하므로...리턴타입 앞에도 const로 명시적으로 지정해야 한다..

 

const char& GetName() const   <== 수정불가인 함수이다...

{

   return Ch;

}

 

=> 리턴되는 원본 대상체도 역시,,,수정불가인 특성을 유지하므로...아무런 문제가 없다...

const char& GetName()   <== 아무 문제없는 문장이다...

{

   return Ch;

}

 

=> 이 함수내에서는 멤버변수가 수정 가능하지만...리턴되는 원본대상체를 가지고 수정을 하지 못한다...

그러므로,,,레퍼런스를 리턴시에는 const 멤버함수는 리턴 타입으로도 그 상수성 유지를

위해...const 지정을 하여주도록 한다...

 

공유메모리 사용

공유메모리(shared memory) 보통 프로세스에서 사용되는 메모리영역은 해당 프로세스만이 사용할수 있다.
하지만 때때로 여러개의 프로세스가 특정 메모리영역을 사용했으면 하는때가 있을것이다.
System V IPC 설비중의 하나인 "공유메모리"를 통해서 이러한일을 할수있다.

개요

모든 프로세스는 자신의 업무를 수행하기 위해서 필요한 자료를 저장하기 위한 메모리 공간을 가지게 된다. 이러한 메모리공간에는 CPU에 의해 수행되는 명령어들, 프로그램 시작시 정의되고 초기화된 데이타, 프로그램 시작시 정의되었지만 초기화 되지 않은 데이타, 함수호출에 필요한 정보, 동적할당이 이루어지는 데이타등 이 들어가게 된다.
프로세스는 시작시 혹은 실행중에 이러한 데이타를 저장하고 사용하기 위한 메모리 공간을 커널에 요구하여서 할당받아 사용하게 되는데, 이러한 메모리공간은 기본적으로 메모리를 요청한 프로세스만이 접근가능하도록 되어있다.
하지만 가끔은 여러개의 프로세스가 특정 메모리 공간을 동시에 접근해야할 필요성을 가질때가 있을것이다.
공유메모리는 이러한 작업을 위한 효율적인 방법을 제공한다.

공유메모리는 여러 IPC 중에서 가장 빠른 수행속도를 보여준다.
그이유는 하나의 메모리를 공유해서 접근하게 되므로, 데이타 복사와 같은 불필요한 오버헤드가 발생하지 않기 때문으로, 빠른 데이타의 이용이 가능하다.
그러나 하나의 프로세스가 메모리에 접근중에 있을때, 또다른 프로세스가 메모리에 접근하는 일이 발생하면 자칫 데이타가 홰손될수 있을것이므로, 한번에 하나의 프로세스가 메모리에 접근하고 있다는걸 보증해줄수 있어야 할것이다.
이러한 작업을 위해서 Unix 에서는 Semaphore 라는 또다른 공유자원을 제어할수 있도록 해주는 도구를 제공해준다.
이번 문서에서는 Semaphore 를 다루지는 않을것이다. 이것은 다른 문서에서 다루도록 하고 여기에서는 단지 공유메모리에 대해서만 다루도록 할것이다.

다음은 공유메모리에 관련된 함수들의 모음이다.
#include <sys/types.h>
#include <sys/shm.h>

int shmget(key_t key, int size, int shmflg)
void *shmat( int shmid, const void *shmaddr, int shmflg )
int shmdt( const void *shmaddr)
int shmctl(int shmid, int cmd, struct shmid_ds *buf)

공유메모리는 어떻게 할당되는가

위의 함수들을 설명하기 전에 우선 공유메모리가 어떻게 할당되고, 어떤 과정을 통해서 접근가능한지에 대해서 우선 알아보도록 하겠다.

공유메모리의 생성요청은 최초 공유메모리 영역을 만드는 프로세스가 커널에 공유메모리 공간의 할당을 요청함으로써 이루어지며, 만들어진 공유메모리는 커널에 의해서 관리 되게 된다.

이런 이유로 한번만들어진 공유메모리는 운영체제를 리부팅하거나, 직접 공유메모리 공간을 삭제시켜주지 않은한, 공유메모리를 사용하는 모든 프로세스가 없어졌다고 하더라도, 계속적으로 유지되게 된다.

프로세스가 커널에게 공유메모리 공간을 요청하게 되면, 커널은 공유메모리 공간을 할당시켜주고 이들 공유메모리공간을 관리하기 위한 내부자료구조를 통하여, 이들 공유메모리를 관리하게 된다.
이 자료는 shmid_ds 라는 구조체에 의해서 관리되며 <shm.h> 에 정의되어 있다.
struct shmid_ds
{
    struct         ipc_perm shm_perm;    // 퍼미션
    int            shm_segsz;            // 메모리 공간의 크기
    time_t         shm_dtime;            // 마지막 attach 시간 
    time_t         shm_dtime;            // 마지막 detach 시간 
    time_t         shm_ctime;            // 마지막 변경 시간
    unsigned short shm_cpid;             // 생성프로세스의 pid
    unsigned short shm_lpid;             // 마지막으로 작동한 프로세스의 pid
    short          shm_nattch;           // 현재 접근한 프로세스의 수
};
Unix 버젼에 따라서 멤버변수들이 약간씩 차이를 보일수 있다.
shm_perm
공유메모리는 여러개의 프로세스가 동시에 접근 가능하므로, 파일과 같이 그 접근권한을 분명히 명시해줘야 한다.
shm_segsz
할당된 메모리의 byte 크기이다
shm_atime
가장최근의 프로세스가 세그먼트를 attach한 시간
shm_dtime
가장최근의 프로세스가 세그먼트를 detach한 시간
shm_ctime
마지막으로 이 구조체가 변경된 시간
shm_cpid
이 구조체를 생성한 프로세스의 pid
shm_lpid
마지막으로 작동을 수행한 프로세스의 pid
shm_nattch
현재 접근중인 프로세스의 수
이러한 공유메모리에 접근을 하기 위해서는 고유의 공유메모리 key 를 통해서 접근가능해지며, 이 key값을 통해서 다른 여러개의 공유메모리들과 구분되어 질수 있다.

shmget

shmget 은 커널에 공유메모리 공간을 요청하기 위해 호출하는 시스템 호출 함수이다. key 는 바로 위에서 설명했듯이 고유의 공유메모리임을 알려주기 위해서 사용된다. shmget 을 이용해서 새로운 공유메모리 영역을 생성하거나 기존에 만들어져있던 공유메모리 영역을 참조할수 있다.

첫번째 아규먼트는 여러개의 공유메모리중 원하는 공유메모리에 접근하기 위한 Key 값이다. 이 Key 값은 커널에 의해서 관리되며, Key 값을 통해서 선택적인 공유메모리에의 접근이 가능하다. 두번째 아규먼트는 공유메모리 의 최소크기 이다. 새로운 공유메모리를 생성하고자 한다면 크기를 명시해주어야 한다. 존재하는 메모리를 참조한다면 크기는 0으로 명시한다.

3번째 아규먼트는 공유메모리의 접근권한과, 생성방식을 명시하기 위해서 사용한다.
아규먼트의 생성방식을 지정하기 위해서 IPC_CREAT 와 IPC_EXCL 을 사용할수 있다. 아래 이들에 대해서 설명을 해두었다.

IPC_CREAT
key 를 이용 새로운 공유메모리 공간을 만든다.
IPC_CREAT
IPC_CREAT와 같이 사용되며, 공유메모리 공간이 이미 존재할경우 error 를 되돌려준다.
만약 IPC_CREAT 만 사용된다면 shmget()은 새로 생성되는 공유메모리공간을 지시하는 공유메모리공간 "식별자" 되돌려준다. 만약 입력된 key 값이 지시하는 공유메모리 공간이 이미 존재하고 있다면 존재하는 공유메모리 공간의 "식별자"를 되돌려준다. IPC_EXCL 과 IPC_CREAT 를 같이 사용할경우, 공유메모리 공간이 존재하지 않으면 새로 생성시켜주며, 존재할경우에 error 를 되돌려준다.

3번째 아규먼트는 이외에도 권한을 지정해줄수도 있다. 권한은 파일권한과 동일하게, 유저, 그룹, Other 에 대한 읽기/쓰기 권한을 지정할수 있다. 단 실행권한은 줄수 없도록 되어 있다.
아래와 같이 사용가능하다.
int shmid;
key_t keyval;

keyval = 1234;
shmid = shmget(keyval, sizeof(keyval), IPC_CREAT | 0666)); 
if (shmid == -1)
{
    return -1;
}

shmat

일단 공유메모리 공간을 생성했으면, 우리는 공유메모리에 접근할수 있는 int 형의 "식별자" 를 얻게 된다. 우리는 이 식별자를 shmat 를 이용해서 지금의 프로세스가 공유메모리를 사용가능하도록 "덧붙임" 작업을 해주어야 한다.

첫번째 아규먼트는 shmget을 이용해서 얻어낸 식별자 번호이며, 2번째 아규먼트는 메모리가 붙을 주소를 명시하기 위해 사용하는데, 0을 사용할경우 커널이 메모리가 붙을 주소를 명시하게 된다. 특별한 사항이 없다면 0을 사용하도록 한다.
3번째 아규먼트를 이용해서, 우리는 해당 공유메모리에 대한 "읽기전용", "읽기/쓰기가능" 모드로 열수 있는데, SHM_RDONLY를 지정할경우 읽기 전용으로, 아무값도 지정하지 않을경우 "읽기/쓰기 가능" 모드로 열리게 된다.

shmdt

프로세스가 더이상 공유메모리를 사용할필요가 없을경우 프로세스와 공유메모리를 분리 하기 위해서 사용한다. 이 함수를 호출할 경우 단지 현재 프로세스와 공유메모리를 분리시킬뿐이지, 공유메모리 내용을 삭제하지는 않는다는 점을 기억해야 한다.
공유메모리를 커널상에서 삭제 시키길 원한다면 shmctl 같은 함수를 이용해야 한다.

shmdt 가 성공적으로 수행되면 커널은 shmid_ds 의 내용을 갱신한다.
즉 shm_dtime, shm_lpid, shm_nattch 등의 내용을 갱신하는데, shm_dtime 는 가장 최근에 dettach (즉 shmdt 를 사용한)된 시간, shm_lpid 는 호출한 프로세세의 PID, shm_nattch 는 현재 공유메모리를 사용하는 (shmat 를 이용해서 공유메모리에 붙어있는) 프로세스의 수를 돌려준다. shmdt 를 사용하게 되면 shm_nattch 는 1 감소하게 될것이며, shm_nattch 가 0 즉 더이상 붙어있는 프로세스가 없다라는 뜻이 될것이다. shm_nattch 가 0이 되어있을때 만약 이 공유메모리가 shm_ctl 등에 의해 삭제표시 가 되어 있다면, 이 공유메모리는 삭제되게 된다.

shmctl

이것은 공유메모리를 제어하기 위해서 사용한다.
즉 shmid_ds 를 직접 제어함으로써, 해당 공유메모리에 대한 소유자, 그룹 등의 허가권을 변경하거나, 공유메모리를 삭제혹은, 공유메모리의 잠금을 설정하거나 해제하는 등의 작업을 한다.

2번째 아규먼트를 이용해서 shmid 가 가르키는 공유메모리를 제어하며, cmd 를 이용해서 원하는 제어를 할수 있다. cmd 를 이용해 내릴수 있는 명령에는 다음과 같은 것들이 있다.

IPC_STAT
공유메모리 공간에 관한 정보를 가져오기 위해서 사용된다. 정보는 buf 에 저장된다.
IPC_SET
공유메모리 공간에 대한 사용자권한 변경을 위해서 사용된다. 사용자 권한 변경을 위해서는 슈퍼유저 혹은 사용자권한을 가지고 있어야 한다.
IPC_RMID
공유메모리 공간을 삭제하기 위해서 사용된다. 이 명령을 사용한다고 해서 곧바로 사용되는건 아니며, 더이상 공유메모리 공간을 사용하는 프로세스가 없을때, 즉 shm_nattch 가 0일때 까지 기다렸다가 삭제된다. 즉 해당 공유메모리 공간에 대해서 삭제표시를 하는거라고 생각하면 된다.
다음은 실제로 공유메모리를 사용하는 방법에 대한 가장간단한 예제이다. 자식과 부모프로세스간에 어떻게 메모리가 공유되는지 보여준다.
예제 : shm.c
#include <sys/ipc.h> 
#include <sys/shm.h> 
#include <string.h> 
#include <unistd.h> 


int main()
{
    int shmid;
    int pid;

    int *cal_num;
    void *shared_memory = (void *)0;


    // 공유메모리 공간을 만든다.
    shmid = shmget((key_t)1234, sizeof(int), 0666|IPC_CREAT);

    if (shmid == -1)
    {
        perror("shmget failed : ");
        exit(0);
    }

    // 공유메모리를 사용하기 위해 프로세스메모리에 붙인다. 
    shared_memory = shmat(shmid, (void *)0, 0);
    if (shared_memory == (void *)-1)
    {
        perror("shmat failed : ");
        exit(0);
    }

    cal_num = (int *)shared_memory;
    pid = fork();
    if (pid == 0)
    {
        shmid = shmget((key_t)1234, sizeof(int), 0);
        if (shmid == -1)
        {
            perror("shmget failed : ");
            exit(0);
        }
        shared_memory = shmat(shmid, (void *)0, 0666|IPC_CREAT);
        if (shared_memory == (void *)-1)
        {
            perror("shmat failed : ");
            exit(0);
        }
        cal_num = (int *)shared_memory;
        *cal_num = 1;

        while(1)
        {
            *cal_num = *cal_num + 1;
            printf("child %d\n", *cal_num); 
            sleep(1);
        }
    }

    // 부모 프로세스로 공유메모리의 내용을 보여준다. 
    else if(pid > 0)
    {
        while(1)
        {
            sleep(1);
            printf("%d\n", *cal_num);
        }
    }
}
에제가 하는 일은 간단하다. int 형의 공유메모리 공간을 할당한다음. 자식프로세스에서 여기에 1씩을 더하고 부모프로세스에서는 공유메모리 내용을 출력하는 일을한다.

쉘 코멘드로 공유메모리 제어하기

쉘에서 공유메모리의 상황을 보여주기 위해서"ipcs"란 도구를 제공한다. ipcs 를 사용하면 공유메모리 뿐만 아닌, Semaphore, Message Queue 등 소위 sytem V IPC 설비에 대한 내용을 보여준다.
그리고 ipcrm 도구를 이용해서 필요없는 공유메모리, Message Queue, Semaphore 등을 지워줄수 있다.
위의 예제코드를 컴파일 시켜서 실행시킨다음 ipcs 를 이용해서 확인을 해보면 공유메모리 자원이 어떤식으로 관리되는지 좀더 이해를 쉽게 할수 있을것이다. 

 

c언어의 이스케이프문자 약호 정리

  • Escape 문자의 종류

escape 문자

ASCII

10진수

ASCII16진수

약호

기  능

\a

\b

 

\f

\n

\r

\t

 

\v

 

\\

\'

\"

\?

\0

\0101

\0x41

7

8

 

12

10

13

9

 

11

 

92

39

34

63

0

65

65

07

08

 

0C

0A

0D

09

 

0B

 

5C

2C

22

3F

00

41

41

BEL

BS

 

FF

LF

CR

HT

 

VT

 

 

 

 

 

NUL

 

 

내장 벨소리(beep sound)를 냄 : alert

커서를 그 행의 1문자 앞으로 이동 :

                         back space

커서를 새 페이지로 이동 : form feed

커서를 다음 행으로 이동 : line feed

커서를 그 행의 맨 앞으로 이동 :

커서를 일정한 간격(tab)만큼 수평이동 :

                        horizontal tab

커서를 일정한 간격(tab)만큼 수직이동 :

                        vertical tab

\ : back-slash 자신

' : 문자상수 ' 자신

" : 문자상수 " 자신

? : 문자상수 ? 자신

null 문자 : 종단문자로도 사용

A : 문자상수 A 자신(8진수로 표현)

A : 문자상수 A 자신(16진수로 표현)

 

 

  • 제어문자의 종류

제 어 문 자

의   미

%d

%ld

%u

%lu

%o

%x 또는 %X

%f

%lf

%e 또는 %E

 

%g 또는 %G 

%c

%s

%%

decimal integer          부호 있는 10진수 정수

long decimal integer     부호 있는 long형 10진수 정수

unsigned decimal integer 부호 없는 10진수 정수

unsigned long decimal integer 부호 없는 10진수 정수

octal integer            8진수 정수 :

hexadecimal integer      16진수 정수

floating point number    10진수 실수

double                   double형 10진수 실수

floating point number, e-notation
                 10진수 실수의 지수표현

 %f 나 %e 중에 짧은 표현을 선택

a single character       단일 문자

character string         문자열

%기호 자체