프로그래밍/C++2012. 4. 23. 17:43

아래 내용은 Android Framework 분석을 위한 C++ 강의를 정리한 내용으로 사용된 모든 소스는 강의에서 나옴을 밝힙니다.

 

개발환경:

Visual Studio 2010 Express

 

참고하기에 좋은 문서/책:

문법: Effectiv c++

template c++ template programming

디자인 패턴 : GoF's 디자인 패턴

 

visual c++ 개발환경에서 기본적인 빌드/실행: 

build                  : F7

build + execute   : ctrl + F5

 

1. 헬로 월드

 

Visual Studio 2010 Express에서 리소스 부분의 .cpp 파일을 클릭해서 빌드에서 제외 하면 컴파일을 안 시키고 다른 소스를 계속 추가해서 실험해 볼 수 있음.

 

 

2. 함수 오버로딩

console에서 컴파일을 할 때, 리눅스의 경우 gcc를 사용하지만, 윈도우는 cl.exe를 사용하며 다음과 같이 컴파일 한다.

cl filename.cpp

cl filename.cpp /FAs  //assembly 소스를 만들어 달라고 요청

 

이제 오버로딩을 보자.(asm파일을 쉽게 보기 위해, iostream은 제외했다)

 

asm파일을 열어보면, 오버로딩 된 함수에 대한 이름이 각기 다른 것을 알 수 있다. (함수오버로딩의 결과물)

보다시피 int 인자를 가진 함수는 ?square@@YYHH@Z 이고, double 인자를 가진 함수는 ?square@@YANN@Z라고 보인다.

 

그럼 다음과 같이 하면 어떨까?

 

컴파일 시에는 각기 컴파일을 한 뒤 링크 과정을 거침으로, c와 c++의 혼합 컴파일 시, c에 정의된 함수는 오버로딩을 지원하지 않는 관계로 name mangling(컴파일 시 같은 오버로딩함수들의 이름을 다른 심볼 이름으로 교체)현상이 없고, 이로 인해 cpp파일의 컴파일 후, 링킹 과정에서 위에서 본 바와 같이 ?sqaure형식의 심볼이름을 찾으려다 못 찾아서 에러가 난다.

 

 

이를 해결하기 위해선, 함수 앞에 extern "c"를 붙여 주면 된다.

그러나, 만약 오버로딩함수2.cpp를 c로 변형한다면 역시나 에러가 난다. 이는 extern "C"는 cpp에서만 지원하기 때문이다.

 

 

cpp에서는 ifdef __cplusplus내의 모든 함수에 extern "C"가 붙고 c의 경우 제거된다.

 

결론은, header파일에 __cplusplus 라는 매크로를 쓰고, c나 cpp에서 부르면 된다. 

 

우리가 자주 쓰는 stdio.h에서도 이와 같은 매크로가 있어서 우리가 c 이던  c++ 이던, 상관하지 않고 쓸 수 있다.

 

추가적으로 overloading 된 함수를 찾는 순서는 다음과 같다.

1. Exactly matching.                        인자와 정확히 들어 맞는 type이 있는 함수를 먼저 찾는다.

2. Promotion.                                  인자가 변환될 때 data의 손실이 없는 타입을 가진 함수가 있는지 찾는다.

3. Standard Conversion                   암시적인 변환이 가능한 타입을 가진 함수를 찾는다.

4. user defined conversion operator  사용자가 만든 변환 연산자가 있으면 변환 후, 함수를 호출한다.

5. '...'                                           가변인자를 갖는 함수는 type에 무관하므로 이를 호출한다.

 

3. 인라인함수

 

위의 코드를 console에서 컴파일 할 경우, 그냥 하면 inline이 컴파일러에 의해 무시된다.  제대로 할려면 다음과 같이 해보자.

 

cl.exe fileName.cpp /Ob1 /FAs  // 인라인을 쓰겠다고 컴파일러에게 알려준다. 그럼 이렇게 생성된 asm파일을 보면,

 

 

우리가 일반적으로 inline을 쓸 경우, 속도가 빨라지나 obj 파일이 커질거라고 생각하나, 간단한 inline함수의 경우 오히려 크기가 작아진다.(asm파일을 참조하라)

 

console이 아닌 Visual C++ IDE에서 컴파일 시, debug옵션이면 inline을 거부하나, release일 경우 inline옵션으로 컴파일 되어 진다.

 

 

inline함수를 쓸 때 주의해야 할 점이 있다. 다음을 보자.

보통 헤더파일에 선언을 하고, 소스파일에서 구현을 하기 때문에, 인라인함수도 그런 식으로 한다면? 빌드 에러난다.  왜냐면, 인라인 함수에 대한 치환은 컴파일러가 담당하므로, 컴파일시, 인라인 함수에 대한 정의가 완전하게 나와야 한다.  즉 인라인 함수에 대한 정의가 치환 시점 전에 있거나, include되는 헤더파일 안에 있어야 한다.  다시 말하면, inline함수는 internel linkage를 갖는다.

 

참고로,

 

internal linkage : 임의의 심볼이 선언된 컴파일 단위(파일)에서만 사용가능.  internal linkage는 주로 헤더에 만든다.

예) static 전역변수, 인라인 함수, 매크로 상수/함수, 함수/클래스 템플릿, 구조체

 

external linkage : 임의의 심볼의 프로젝트내의 모든 컴파일 단위에서 사용가능

예) 전역변수, 일반함수

 

예제)

template<typename T> T square(T a){return a * a;}    internal linkage

int a;                                                                     다른 파일에서 extern int a;라고 선언 후 접근 가능

static int b;                                                             다른 파일에서 접근 불가.

const int c = 0;                                                       .c에서는 external

                                                                            .cpp에서는 internal

#define    MAX 10                                                   internal linkage

struct People {};

 

 

4. 함수 템플릿

 

c의 경우 필요시 코드 생성 기술을 다음과 같이 활용한다.

 

 

C++에서는 함수 tempalte을 써서 컴파일러가 처리할 수 있다.

 

반면에 template은 잘못 사용하면, 다음과 같은 결과가 생긴다.

 

 

한편 다음과 같은 경우는 어떨까?

 

T가 결정되어서 함수 템플릿이 진짜 함수가 되는 과정: "인스턴스화"라고 하고 "명시적 인스턴스화" 와 "암시적 인스턴스화"가 있다.

이렇게 위와 같이 <int>로 프로그래머가 정한 것을 명시적 인스턴스화 라고 한다.

암시적 인스턴스는 컴파일시 컴파일러에 의해 결정되는 것을 말한다.

 

 

5. 캐스팅

한마디로 C에서의 캐스팅은 위험하다.  물불 안 가리고 캐스팅을 하기 때문이다.  자세한 내용은 밑의 소스 코드를 참조하자.

 

//---------------------------------------------

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

static_cast 는 C 스타일 캐스트와 똑같은 의미와 형변환 능력을 가지고 있는, 기본적인 캐스트 연산자입니다. C 스타일의 그것과 구실이 똑같다 보니 받는 제약도 똑같습니다. 예를 들어,struct 를 int 타입으로 바꾼다든지 double을 포인터 타입으로 바꾸는 일은 이것으로 할 수 없습니다. 게다가, static_cast 는 표현식이 원래 가지고 있는 상수성(constness)을 떼어버리지도 못합니다. 이런 일을 하는 캐스트 연산자인 const_cast 가 따로 있는 것을 보면 짐작할 수있지요.
나머지 세 가지의 C++ 캐스트 연산자는 좀 더 구체적인 목적을 위해 만들어졌습니다. const_cast는 표현식의 상수성이나 휘발성(volatileness)을 없애는 데에 사용합니다. 이 연산자가 쓰여진 소스를 만나면, 아, 이 개발자는 const 나 volatile로 선언한 변수라든지 이런 타입의 값을 내는 표현식에서 이런 특성만 바꾸고 싶어하는구나 라고 생각하면 되겠습니다. 이러한 프로그래머의 의도는 컴파일러에 의해서 더욱 확실해집니다. 즉, 상수성이나 휘발성을 제거하는 것 이외의용도로 const_cast 를 쓰면 통하지 않습니다.

구체적인 용도를 가진 C++ 캐스트 연산자 두 번째는 dynamic_cast 입니다. 이 연산자는 상속
계층 관계를 가로지르거나 하향시킨 클래스 타입으로 안전하게 캐스팅할 때 사용합니다. 말하자
면, dynamic_cast 는 기본 클래스의 객체에 대한 포인터나 참조자의 타입을 파생(derived) 클래
스, 혹은 형제(sibling) 클래스의 타입으로 변환해 준다는 것입니다3). 캐스팅의 실패는 널 포인터
(포인터를 캐스팅할 때)나 예외(참조자를 캐스팅할 때)를 보고 판별할 수 있습니다

네 가지 C++ 캐스트 연산자의 마지막은 reinterpret_cast 입니다. 이 연산자가 적용된 후의변환 결과는 거의 항상 컴파일러에 따라 다르게 정의되어 있습니다. 따라서, 이 연산자가 쓰인소스는 직접 이식이 불가능합니다.
reinterpret_cast 의 가장 흔한 용도는 함수 포인터 타입을 서로 바꾸는 것입니다. 예를 들어, 어떤 특정한 타입의 함수 포인터를 배열로 만들어 놓았다고 가정합시다.
typedef void (*FuncPt r) ( ) ; // FuncPtr 은 인자를 받지 않고
/ / void를 반환하는 함수에 대한
/ / 포인터입니다.

FuncPtr funcPtrArray[10]; // funcPtrArray는 10개의 FuncPtr로 만들어진 배열입니다.
이때 다음의 함수에 대한 포인터를 funcPtrArray에 넣어야 할 피치 못할 사정이 생겼습니다.
int doSomething();
간단할 것 같지만 캐스팅을 하지 않으면 절대로 안 됩니다. 왜냐하면 doSomething은 funcPtrArray에 넣기에는 타입이 맞지 않기 때문입니다. 이 배열에 들어가는 함수는 void를 반환하지만, doSomething은 i nt 를 반환하지 않습니까?
funcPtrArray[ 0 ] = &doSomething ; / / 에러입니다! 타입불일치이군요.
reinterpret_cast 를 쓰면 컴파일러에게 이 일을 강제로 시킬 수 있습니다.
funcPtrArray[ 0 ] = / / 이것은 컴파일됩니다.
reinterpret_cast <FuncPtr> (&doSomething) ;

 

//----------------------------------------------

 

6. 레퍼런스

 

변수 선언을 했다고 치자.

그럼 메모리가 할당되고, 이를 만약 포인터 변수를 써서 assign했다 치자. 그럼 역시 변수를 가리키는 주소를 가지는 포인터가 메모리에 생긴다.  이와는 달리 레퍼런스의 경우, 별다른 메모리를 차지않고 기존 메모리에 대한 별명이라고 보면 된다.

 

 

 

다음을 보자.

 

답은 다음과 같다.

foo()를 호출 했을 때, p를 리턴하는데, 이는 임시객체를 리턴한다.  그러므로, 위에 정의된 구조체에 영향을 미치지 않는다.

당연히 p.x를 출력하면, 1이 출력된다. 

 

한편 임시객체를 리턴할 경우, 생성자/소멸자 등이 불리우고 메모리가 잡히기 때문에 성능에 큰 영향을 미친다. 

그럼 어떻게 해야 할까?  다음과 같이 레퍼런스를 사용하면 된다.

 

 

정리하자면,

임의의 함수가 값을 리턴하면,
     1. built in type인 경우, (ex: int foo() )       : 상수로 리턴된다.
     2. user defined type인 경우, (ex: Point foo() ) : 임시객체가 리턴된다.

 

어떤 함수의 리턴값이 참조라면
     1. built in type  (int& foo) : 함수 호출을 lvalue에 놓게다는 의미
         foo() = 10;
     2. user type ( Point& foo() ) : 임시객체를 제거하겠다는 의미!

 

 

참조 변수와 함수 인자

여기서 정리하고 가는 단계로 다음의 코드를 보자.

 

주의할 점은 inc3(z)로 z를 패스하는데, &z를 패스하지 않도록 주의한다.

이렇게 보면 레퍼런스로 받는게 코드 복잡성에서도 나아보이고, 메모리 구조면에서도 좋아보이나, 코드 가독성 측면에서 포인터가 더 좋다.( inc3(z)를 본다면, z가 변할지 안 변할지 알아보기 힘들다. 그러나 inc2(&y)를 보면 딱 봐도 y가 변할 거라는게 보이지 않는가?)

 

그러나 레퍼런스 인자를 쓸 일이 있는데 다음을 보자.

 

 

한가지 재밌는 것은, 다음의 경우다.

위에서 막 레퍼런스를 얘기했으니 2번 같겠지만, 1번이다.  int같은 경우 overhead가 크게 일어날 경우도 없고, const T&를 쓸 경우, 컴파일러가 최적화를 할 수 가 없다.

 

혼돈스러우니, 정리하자면,

함수가 값을 변경하지 않는다면 const T&를 사용하자.
  (A) built in type의 경우 : call by value 가 좋다.

         foo( int a )
  (B) user type 의 경우    : const T& 를 사용하자.
         foo( const Data& a )

 

 

 

7. Namespace

 

다음과 같이 코드를 짜다보면 같은 이름을 쓰고 싶은데 중복될까봐 이런 식으로 쓰게 되는 경우가 있는데,

이걸 매번 확일 할 수도 없고, 그래서 사용할 수 있는게 namespace이다.

 

이렇게 같은 함수 이름을 중복해서 써도, namespace만 정의 잘하면 문제 없이 쓸 수 있다.

 

참고로, #include <iostream.h> 에는 cout, endl이 전역공간에 있다. 그러나 <iostream>의 경우,

namespace std

{

   // cout, endl이 여기 있음.

}

와 같이 되어 있다.

 

한가지 더 알면 좋은 거 추가...

 

 

8. OOP 개념

 

다음의 코드를 보자.

 

 

복소수를 리턴하기 위해 복잡하게 리턴을 하는 것보다, 구조체를 만들어 보자.

 

 

다음은 OOP개념을 익히기 위해 기본적인 C의 방법부터 코드를 수정하는 과정이다.

 

 

 

9. 생성자 정리

 

 

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

Android Framework 분석을 위한 C++ 4일차  (0) 2012.04.26
[C++] Smart Pointer  (0) 2012.04.25
Android Framework 분석을 위한 C++ 3일차  (0) 2012.04.25
swap 함수  (0) 2012.04.25
Android Framework 분석을 위한 C++ 2일차  (0) 2012.04.24
Posted by code cat
프로그래밍2012. 4. 12. 10:24

출처: stackoverflow.com

콘솔상에서 간단하게 현재 상태가 진행 중 임을 알리는 progress animation이다.  우리가 흔히 카피중이거나 작업중에 볼 수 있는 형식이다.  정식으로는 쓰레드로 하나 돌리는게 맞겠지만, 어떻게 보면 프로세스가 무언가에 걸리고 있으면 애니메이션도 멈추는게 맞을지도 모르겠다.



실행하면 |,/,-,\, 를 반복한다.

Posted by code cat
프로그래밍2012. 4. 8. 20:41

조엘 온 소프트웨어에서 나온 문제이긴 한데, 나도 인터뷰 때 저런 문제 풀어 본 적이 있어서, 이 정도는 스도코드로 몇분(그래 면접 때 떨리는데 머리가 팍팍 돌겠나..)안에 할 수 있어야 한다.  모르면 평소에 시간내서 풀어보자.

1. 원래 저장위치에서 문자열을 역순으로 변환하기

2. 연결 리스트를 역순으로 만들기

3. 한 바이트에서 1인 비트 세기

4. 이진 검색

5. 문자열에서 '연속적으로 문자가 반복되는 길이 run-length' 가 가장 긴 부분 문자열 찾기

6. atoi

7. itoa(스택이나 strrev를 써야 하기 때문에 좋은 문제임)

Posted by code cat
프로그래밍/C2012. 4. 4. 14:25

정적 라이브러리

컴파일 시 링커로 라이브러리의 오브젝트 코드를 만들고자 하는 타겟 바이너리에 추가된다.

동적 라이브러리

컴파일 시, 라이브러리가 사용된 곳에 공유 라이브러리를 쓴다고 해놓고, 런타임시 동적링크로 링크한다.

 

 

정적 라이브러리 만들기

언제나와 같이 예제로 살펴보자.
우선 foo1.c라는 파일을 하나 만들어 다음과 같이 넣어보자.

자 이제는 foo2.c를 만들어 다음과 같이 만들자.

마지막으로 이 둘을 interface해 줄 역활의 foolib.h을 만들자.

 

코드 입력을 다 끝냈으면 이제 foo1.c와 foo2.c를 컴파일 하자.

gcc -c foo1.c foo2.c

 

그러면 foo1.o 와 foo2.o라는 오브젝트 파일이 만들어지는데, 이 둘을 다음과 같이 묶어주자.

ar rscv libfoo.a foo1.o foo2.o

 

그러면 libfoo.a라는 파일이 생성된다.  이 파일이 이제 우리가 쓸 수 있는 정적라이브러리 파일이다. 제대로 동작하나 보기 위해, example.c를 만들자.

 

컴파일은 다음과 같이 한다.

gcc -o example example.c -L./ -lfoo

-L 은 gcc에게 라이브러리를 찾을 디렉터리를 지정한다.

-l은 링크할 라이브러리를 지정한다. l뒤에 붙은 이름은 라이브러리 이름에서 lib을 뗀(libfoo의 경우 foo만 남는다) 것이며, -l 옵션은 컴파일 할 대상의 소스가 지정되는 뒤에 두면 된다.

 

 

동적 라이브러리 만들기

 

동적 라이브러리를 살펴 볼때도 위에서 나열한 소스를 쓰자. (재활용은 위대하다.)

gcc -fPIC -c foo1.c foo2.c

-fPIC은 컴파일러에게 위치에 상관없이 수행할 수 있는 코드로 컴파일 하라고 하는 것이다. 이제 이렇게 해서 나온 오브젝트 파일을 정적 라이브러리와 합쳐(?)보자.

 

다음과 같이 쳐보자.

gcc -shared -Wl,-soname,libfoo.so.0 -o libfood.so.0.0.0 foo1.o foo2.o

-shared는 동적 라이브러리를 우선 시해서 링크 하라고 지시한다.

-soname은 정적 라이브러리의 soname을 정한다.

 

자 이제 gcc 링크와 동적 링크를 위한 심볼릭 링크만 생성해 주자.

ln -s libfoo.so.0.0.0 libfoo.so
ln -s libfoo.so.0.0.0 libfoo.so.0

libfoo.so는 컴파일 시에 실행 파일들을 링크 할 때 링커가 찾을 수 있게 만든 것이며, libfoo.so.0은 실행 시에 동적 링커가 soname으로 찾을 수 있게 soname을 파일명으로 하는 libfoo.so.0.0.0파일의 심볼릭 링크를 만든 것이다. 

 

동적 링크에 대한 작업도 필요하다.  libfoo.so.0.0.0파일이 있는 디렉터리를 /etc/ld.so.conf에 추가해 줘야 하며, ldconfig로  /etc/ld.so.cache를 새로 생성한다.

 

마지막으로 다음과 같이 실행하여 컴파일 하자.

gcc -o example example.c -L./ -lfoo

 

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

구조체 초기화 방법  (0) 2012.08.21
error: expected identifier before numeric constant  (0) 2012.07.03
sprintf 사용법  (0) 2012.04.03
sscanf 사용법  (0) 2012.03.28
exit(1) vs exit(EXIT_FAILURE)  (0) 2012.02.29
Posted by code cat
프로그래밍/C2012. 4. 3. 20:46

sscanf에 이어 이번에는 sprintf의 사용법에 대해서 알아보자 (sscanf에 대한 사용법은 여기를 참조http://codecat.tistory.com/entry/sscanf-사용법)

 

sprintf는 man page에 다음과 같이 선언되어 있다.

 

int sprintf(char *str, const char *format, ...);


 

sscanf 사용법에서, scanf가 standard input stream, stdin, 에서 input을 읽어오는 반면, sscanf는 str이라는 문자열(character string)에서 입력을 받아온다라고 했는데, sprintf 또한 input<->output이라는 점만 제외하면 이와 유사하다.

 

즉 printf가 standard output stream, stdout에 output을 출력한다면, sprintf는 str이라는 문자열에 출력을 한다고 생각하면 된다.

 

sscanf 사용법에 썼던 예제를 다시 한번 보면, (예제출처: 초보자를 위한 Linux & Unix C 프로그래밍)

sprintf(buff1, "%d", i) 라는 부분이 있다.  이는 위에서 이미 정의된 i를 %d에 맞게 buff1에 출력(저장이라고 보는게 맞겠다)한다고 보면 된다.  (포맷에 관련해서는 sscanf 사용법을 참조하자.)

 

그럼 이걸 언제 써먹냐면은... 물론 여러가지 용도가 있겠지만, 대표적으로 생각나는게 여러가지 옵션을 모아서 하나의 커맨드를 만들어서 실행시킬 때 사용할 수 있겠다. 

 

다음과 같이 실행 커맨드와 옵션들을 긁어 모아서 system()으로 한방에 실행시킬 수 있다.

sprintf(cmd_buf, "%s %s %s", cmd_exec, opt1, opt2);
system(cmd_buf);
Posted by code cat
프로그래밍/C2012. 3. 28. 11:40

sscanf는 manpage에 다음과 같이 정의되어 있다.

#include <stdio.h>

int sscanf(const char *str, const char *format, ...);


scanf가 standard input stream, stdin, 에서 input을 읽어오는 반면, sscanf는 str이라는 문자열(character string)에서 입력을 받아온다

즉, 메모리에서(const char *str) 받은 값을 포맷 형식에 맞게 읽어오는 것이다.

간단한 예제를 보자.

 
예제출처: 초보자를 위한 Linux & Unix C 프로그래밍


위의 예제에서는 이미 정의되어 있는 값을 sprintf를 이용해 buff에 넣어두었다가, sscanf를 통해 포맷에 맞게 읽어오는 예제이다.


가끔 이 포맷으로 간편한 작업을 해낼 수도 있는데, 다음의 예제를 보면,


예제출처: Microsoft Support @http://support.microsoft.com/kb/38335/eng


sscanf를 이용해서 내부 버퍼에서 ','(컴마)로 문자를 잘라서 읽어오는 것이다.  (이렇게 하기 위해서 필요한 포맷은 %[^',']이다)  물론 일반적인 토큰화를 해야 한다면 strtok을 사용하는 걸 추천한다. 


말 나온 김에 포맷에 대해서 정리해 보자.


  %o 

   8진 정수

  %d 

  10진 정수

  %ld

  long형 10진 정수

  %x

  16진 정수

  %f

  float 형

  %lf

  double 형

  %c

  문자

  %s

  문자열

  %n

  읽은 문자 갯수 

주의 할 점은 포맷을 잘못 사용 할 경우, 이어지는 모든 데이터가 다 틀어지게 된다는 것이다.  sscanf를 사용해서 받은 값이 이상하거나 할 때는, 포맷을 제대로 썼는지 확인하자.



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

정적 라이브러리, 동적 라이브러리 만들기  (0) 2012.04.04
sprintf 사용법  (0) 2012.04.03
exit(1) vs exit(EXIT_FAILURE)  (0) 2012.02.29
file에서 읽어와 strtok을 이용해 struct에 저장하기  (2) 2012.02.28
C의 역사  (0) 2012.02.19
Posted by code cat
프로그래밍/C2012. 2. 29. 11:10

출처: http://www.mkssoftware.com/docs/man3/exit.3.asp


exit() 함수는 atexit() 함수로 등록된 모든 함수들을 역순서로 호출한다.  


그 후, exit()는 데이터가 남겨있는 버퍼를 가진 모든 오픈 스트림을 비우고, 닫는다(모든 오픈 스트림에 fclose()를 하는 것과 유사하다).  마지막으로 exit()_exit(status)를 호출하여 호스트 환경으로 제어를 넘겨준다.


만약 exit(0) 혹은 exit(EXIT_SUCCESS)라면,  exit()는 임플리멘테이션에 정의된 성공적인 상태의 마침을 반환하며

만약 exit(EXIT_FAILURE)라면, exit()는 임플리멘테이션에 정의된 실패상태의 마침을 반환한다.  


마지막으로 이식성을 위해,


exit(0), exit(1)     대신 

exit(EXIT_SUCCESS), exit(EXIT_FAILURE)


를 사용하자.

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

sprintf 사용법  (0) 2012.04.03
sscanf 사용법  (0) 2012.03.28
file에서 읽어와 strtok을 이용해 struct에 저장하기  (2) 2012.02.28
C의 역사  (0) 2012.02.19
enum vs #define vs const  (0) 2012.02.05
Posted by code cat
프로그래밍/C2012. 2. 28. 17:32

다음과 같이 작성하면 간단하게 구현할 수 있다. main은 생략했다.


struct안의 char는 char*형을 쓰면 오동작을 한다.

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

sscanf 사용법  (0) 2012.03.28
exit(1) vs exit(EXIT_FAILURE)  (0) 2012.02.29
C의 역사  (0) 2012.02.19
enum vs #define vs const  (0) 2012.02.05
enum 타입 c vs c++  (0) 2012.02.04
Posted by code cat
프로그래밍2012. 2. 20. 16:23
출처:

[1] http://en.wikipedia.org/wiki/Thread-local_storage

[2] http://november11.tistory.com/entry/TLSThread-Local-Storage

[4] http://gcc.gnu.org/onlinedocs/gcc-3.3.1/gcc/Thread-Local.html

[3] http://sian999.egloos.com/3945779


TLS는 static혹은 global 메모리를 쓰레드에 지역적으로 쓸 수 있게 하는 프로그래밍 메소드다.

쓰레드는 일반적으로 같은 프로세스 안에서 같은 주소 공간(address space)를 사용하기 마련인데, 가끔은 이런걸 원치 않을 때가 있다.  같은 주소 공간을 사용한다는 뜻은 곧 같은 프로세스안의 쓰레드들이 접근할 때, 정적 혹은 전역 변수들이 같은 메모리 location에 있다는 말이다  이와 반대로, 스택에 있는 변수들은 각 쓰레드들에게 지역적(local)이다.(쓰레드는 각각의 스택을 지니고 있으므로 당연하다)

예를 들어, 두개 이상의 쓰레드가, 같은 정적 혹은 전역 변수를 접근할 때, 실제로는 다른 메모리 location을 접근하도록 하고 싶을 때가 있을 것이다.(즉 정적/전역변수를 로컬화 한다는 소리) 이런한 경우의 클래식 적인 예가 errno가 되겠다.


TLS를 사용하는 방법에는 2가지가 있는데, 

  1. 한가지는 정적으로 compiler 타임에 사용하는 것이고,

  2. 다른 하나는 동적으로 API를 사용하는 것이다.


우선 정적인 경우를 보자.

[3]비쥬얼 C++ 컴파일러 기준으로는 저장소 선언 지정자 __declspen(thread)를 사용한다.  이렇게 해서 변수를 정의하면, 컴파일러가 변수를 .tls라는 이름을 갖는 바이너리 파일의 데이터 영역에 변수를 넣도록 지시한다.  링커는 오브젝트 파일에 있는 모든 .tls 파일을 묶어서 exe나 dll바이너리 안의 .tls 섹션에 위치시킨다.

그러면 운영체제는 바이너리 실행파일이나 dll를 로드할 때, .tls영역에 대해서 주 스레드의 영역에 있는 모든 변수를 담을 수 있을만한 별도의 메모리를 할당하고, 각각의 스레드가 자신을 위해서 특별히 할당된 데이터 블럭만을 접근하도록 보장한다.

문제가 있다면 이렇게 정적으로 할당된 경우, 프로그램이 처음 로드될 때 운영체제가 .tls 메모리 블럭의 크기를 결정해야 한다.  exe파일에 정의된 정적 tls변수와 제한적으로 링크된 dll의 경우 잘 동작하나 loadlibray를 사용해서 로드된 dll은 .tls메모리 블록의 크기를 알기 어려우며, 처음에 생성할때 정해진 tls영역의 크기를 확장할 수 없다.  그래서 애플리케이션에서만 정적 스레드 로컬 저장소를 사용해야 하고 라이브러리는 동적 스레드 로컬 저장소 API를 사용해야만 한다.


[4]GCC의 경우 우선 compiler를 사용하는 방법에는 linker(ld), dynamic linker(ld.so), system libraries(libc.so & libpthread.so)들의 support가 필요하다.

유저 레벨에서의 확장은 __thread라는  새로운 기억 클래스 부류를 사용한다.

예를 들어,

__thread int i;

extern __thread struct state s;

static __thread char *p;

처럼 선언 할수 있다.

__thread는 어떠한 전역변수, 파일스코프 정적, 정적변수 에게나 적용할 수 있으나, 블록 한정 automatic 혹은 non-static 데이터 멤버에게는 적용 할 수 없다.


실제로 위의 코드를 첨가해서 파일을 만든 뒤 readelf -S 로 읽어보면, 

.tbass

.tdata

라는 섹션이 생김을 확인 할 수 있다.


arm 컴파일러에서는 RVCT 3.0에서 TLS를 지원한다고 나와있으며, __declspec(thread)과 __thread라는 2개의 키워드를 지원한다.

이제 동적인 경우를 보자.

MSDN에 따르면, TLS를 구현하기 위한 단계는 다음과 같다.

  1. 프로세스나 dll 초기화 때 TLS 인덱스를 할당하기 위해서 TlsAlloc함수를 사용하라.
  2. TLS인덱스를 사용할 필요가 있는 각 쓰레드를 위해 동적 메모리를 할당하고, TlsSetValue를 사용해서 TLS 인덱스와 방금 할당한 메모리를 연결하라
  3. 쓰레드가 그 자신의 메모리를 접근해야 할 때, TlsGetValue함수를 사용해서 TLS인덱스를 지정해 그 메모리의 포인터를 얻어라
  4. TLS인덱스와 연결된 동적 메모리를 더 이상 사용할 필요가 없을때는 반드시 해제하라.  TLS 인덱스를 사용하는 모든 쓰레다가 종료되면, TlsFree함수를 사용하여 TLS인덱스를 해제하라.








Posted by code cat
프로그래밍/C2012. 2. 19. 14:28
출처: secure coding in c and c++

C언어의 아버지 데니스 리치는 The Second History of Programming Language 컨퍼런스에서 C언어의 개발을 발표하였고, 1970년 초반  UNIX 오퍼레이팅 시스템에서의 시스템 구현 언어로서의 역활을 하기 위해 만들어 졌다.  C는 typeless 언어 B에서 파생되었으며, B언어 또한 BCPL이라는 언어에서 파생되었다.  BCPL은 1960 년대 마틴 리차드가 설계했으며 1920년 초반 여러 프로젝트에서 사용되었다. B는 type이 없는 C, 혹은 더 정확하게 말하자면, 메모리에 8K 바이트로 압축된 BCPL이라고 할 수 있겠다.


그 후,  1978년에 "K&R"이라고 자주 불리우는 'The C Programming Language'이  출판되었다.  이 시점의 C언어에 있었던 많은 변화들은 interdata 8/32라는 컴퓨터로 코드를 포팅하는 과정에서 생겨났으며 이 당시 C는 여전히 typeless라는 성격을 강하게 지니고 있었다.  


1983년 ANSI는 X3J11위원회를 설립했다. ANSI의 목표[ANSI 89]는 "명확하고, 일관되었으며, 모호하지 않는 C의 스탠다드를 만들어 C언어 환경안에서 사용자 프로그램의 portability 를 지원하고, 일반적인, 기존에 이미 존재하는 C의 정의를 코드화"하는 것이었다.  1989 년 말에 X3J11은 [ANSI 89]라는 리포트를 만들어 냈으며 이는 이후 ISO / IEC 9899-1990로 ISO에 의해 승인되었다.   물론, ANSI 표준의 섹션의 재배치가 있었고 ISO표준에서는 조항이 되었다고 하나 이 둘 사이의 기술적인 차이는 없다.  이 표준은 두 형태로, 일반적으로 C89와 같은, 또는 간혹 C90로 알려져 있다.


이 스탠다드는 우리가 흔히 아는 C89가 되었다.(혹은 승인 기준으로 C90이라고 불리우기도 한다.)  

후일 이 스탠다드의 첫번째 에디션은 수정을 거쳐 AMD1:1995가 되었다.(이 또한 C95라고 알려졌다)

이 또한 계속적인 수정( ISO/IEC 9899 )으로  인해 우리가 흔히 알고 있는 C99가 되었다.

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

exit(1) vs exit(EXIT_FAILURE)  (0) 2012.02.29
file에서 읽어와 strtok을 이용해 struct에 저장하기  (2) 2012.02.28
enum vs #define vs const  (0) 2012.02.05
enum 타입 c vs c++  (0) 2012.02.04
system vs exec  (0) 2012.01.30
Posted by code cat