비쥬얼 스튜디오 닷넷이 나온지 한참 되어가지만 여전히

익숙한건 버릴수 없는 처지네요..

아직도 무슨 개발을 하던 스튜디오 6.0을 사용하는 추세인건

부정할 수 없듯이..ㅋㅋ


http://www.microsoft.com/msdownload/platformsdk/sdkupdate/default.htm
에 접속하여 core SDK 및 Internet Development SDK 를 설치하면 됩니다~~~.
설치가 끝나면 기본적으로 C:\Program Files\Microsoft SDK 에 설치 되게 됩니다.
그럼 이넘을 비쥬얼 스튜디오 내부에서 인지 할 수 있도록 세팅 해 줍니다.

Tools -> Options... -> Directory 탭 을 선택하고
include 와 Lib 에 위 디렉토리를 추가하고 가상 상위로 올려 줍니다.
(최신 인클루드 파일과 라이브러리를 사용하겠다란 의미죠)

일단 지금 Mdi 프로그래밍을 공부하는데 있어서 이런 기초적인 유틸정보는

가지고 있어야 겠다는 생각을 합니다~~

Posted by '김용환'
,

c programming FAQs

c or linux 2005. 6. 23. 09:02

'c or linux' 카테고리의 다른 글

[펌] C언어 연산자 우선 순위  (0) 2005.11.15
[펌] Visual Studio 6.0의 라이브러리 링크  (0) 2005.11.05
enumeration + function pointer  (0) 2005.03.16
define 문에서 do while 문 사용이유  (0) 2005.03.15
상수 포인터  (0) 2005.03.15
Posted by '김용환'
,

(문제)

1~4값을 입력받아 procA~procD함수를 호출하는 함수를 작성하시오.

 

#define n_A (0)

#define n_B (1)

#define n_C (2)

#define n_D (3)

 

static void sub(int cmd)

{

    switch(cmd)

    {  case n_A : procA( ); break;

        case n_B : procB( ); break;

        case n_C : procC( ); break;

        case n_D : procD( ); break;

     }

}

 

=>안정성이 아니라, 효율성때문에 함수포인터와 enum을 쓰는 겁니다. 일반적으로 상수의 범위가 작기 때문에 전처리기에서 상수를 처리하면, 용량을 많이 차지 할수 있는 부분이 있는 겁니다. 그래서 간단히 상수만을 선언하고 사용하기 위해서, enum을 쓰는게 훨씬 메모리를 아낄수 있는 부분입니다.

어떤 일을 비슷한 범위에서 하는데, 같은 함수를 정의하는게 상당히 좋지 않을 수 있습니다.

코드의 낭비를 줄이기 위해서 씁니다. 그래서 함수포인터는 쓰는 거지요. 임베디드 시스템에서는 주로 이렇게 쓰죠..

 

 

 

typedef enum

{  n_A = 0,

    n_B,

    n_C,

    n_D

};

 

static void sub(int cmd)

{

     static const struct t_convtbl

     {    int val;

           BOOL (* proc)(void);

      } convtbl[ ] = { { n_A, procA },

                            { n_B, procB },

                            { n_C, procC },

                            { n_D, procD }};

      if ((sizeof(convtbl)/sizeof(convtbl[0]))>cmd )

     {

           (*convtbl[cmd].proc)();

     }

}

 

 

펌) http://cafe.naver.com/cafec.cafe

'c or linux' 카테고리의 다른 글

[펌] Visual Studio 6.0의 라이브러리 링크  (0) 2005.11.05
c programming FAQs  (0) 2005.06.23
define 문에서 do while 문 사용이유  (0) 2005.03.15
상수 포인터  (0) 2005.03.15
library  (0) 2005.03.13
Posted by '김용환'
,

http://bbs.kldp.org/viewtopic.php?t=19289&highlight=while%280%29

 

<linux/module.h>에 보면 아래와 같은 매크로가 있습니다.

코드:
#ifdef CONFIG_MODULES
#define SET_MODULE_OWNER(some_struct) do { (some_struct)->owner = THIS_MODULE } while (0)
#else
#define SET_MODULE_OWNER(some_struct) do { } while (0)
#endif


 

왜 do while문을 쓰는 이유는??

 

 

소스: http://www.kernelnewbies.org/faq/

Why do a lot of #defines in the kernel use do { ... } while(0)?

There are a couple of reasons:

* (from Dave Miller) Empty statements give a warning from the compiler so this is why you see #define FOO do { } while(0).
* (from Dave Miller) It gives you a basic block in which to declare local variables.
* (from Ben Collins) It allows you to use more complex macros in conditional code. Imagine a macro of several lines of code like:

#define FOO(x) \
printf("arg is %s\n", x); \
do_something_useful(x);

Now imagine using it like:

if (blah == 2)
FOO(blah);

This interprets to:

if (blah == 2)
printf("arg is %s\n", blah);
do_something_useful(blah);;

As you can see, the if then only encompasses the printf(), and the do_something_useful() call is unconditional (not within the scope of the if), like you wanted it. So, by using a block like do{...}while(0), you would get this:

if (blah == 2)
do {
printf("arg is %s\n", blah);
do_something_useful(blah);
} while (0);

Which is exactly what you want.
* (from Per Persson) As both Miller and Collins point out, you want a block statement so you can have several lines of code and declare local variables. But then the natural thing would be to just use for example:

#define exch(x,y) { int tmp; tmp=x; x=y; y=tmp; }

However that wouldn't work in some cases. The following code is meant to be an if-statement with two branches:

if(x>y)
exch(x,y); // Branch 1
else
do_something(); // Branch 2

But it would be interpreted as an if-statement with only one branch:

if(x>y) { // Single-branch if-statement!!!
int tmp; // The one and only branch consists
tmp = x; // of the block.
x = y;
y = tmp;
}
; // empty statement
else // ERROR!!! "parse error before else"
do_something();

The problem is the semi-colon (Wink coming directly after the block.

The solution for this is to sandwich the block between do and while(0). Then we have a single statement with the capabilities of a block, but not considered as being a block statement by the compiler.

Our if-statement now becomes:

if(x>y)
do {
int tmp;
tmp = x;
x = y;
y = tmp;
} while(0);
else
do_something();

'c or linux' 카테고리의 다른 글

c programming FAQs  (0) 2005.06.23
enumeration + function pointer  (0) 2005.03.16
상수 포인터  (0) 2005.03.15
library  (0) 2005.03.13
[펌] 이중 포인터 사용 용도 질문(내공있음)  (0) 2005.03.12
Posted by '김용환'
,

상수 포인터

c or linux 2005. 3. 15. 19:15

void test1( char const * const x )
{
   x++;    // No. 1
   (*x)++;   // No. 2
   return;
}

 

void test2( char const *y )
{
   y++;      // No. 3
   (*y)++;   // No. 4
   return;
}

 

void test3( char * const z )
{
   z++;      // No. 5
   (*z)++;   // No. 6
   return;
}

 

void main( void )
{
    char x;
    test1( &x );
    test2( &x );
    test3( &x );
}

-----------------------------------------------------------------------------------------

1,2,3,6으로 생각했었는데...아니군요...전 지금까지 const에 대해 잘못 알고있었군요...에고...큰일날뻔했네요...

 

'const data_type variable_name' 으로만 해왔었는데, 가끔 'data_type const variable_name'형식으로 되어있는걸 보면 왜 보기 불편하게 이렇게 해놓나 생각했었는데 그 포인터의 경우 그 위치에 따라 결정되는 군요.

 

찾아봤더니 C99엔 다음과 같이 친절히 설명이 되어있더군요. 친절한 설명도 영어의 압박이 '불친절'의 누명을 씌웠지만요^^

 

1) The type designated as "float *" has type "pointer to float". Its type category is pointer, not a floating type. The const-qualified version of this type is designated as "float * const" whereas the type designated as "const float *" is not a qualified type — its type is "pointer to constqualified float" and is a pointer to a qualified type.

 

2) The following pair of declarations demonstrates the difference between a "variable pointer to a constant value" and a "constant pointer to a variable value".


const int *ptr_to_constant;
int *const constant_ptr;


 

The contents of any object pointed to by ptr_to_constant shall not be modified through that pointer, but ptr_to_constant itself may be changed to point to another object. Similarly, the contents of the int pointed to by constant_ptr may be modified, but  constant_ptr itself shall always point to the same location.

 

1) "float *"형은 float의 pointer형으로 명시된다. 이 타입은 float형이 아니라 포인터에 속한다. 이 데이터 타입(float의 pointer형)의 상수형은 "const float *"가 아닌 "float * const"로 명시된다. "const flaot *"은 상수로 제한된 float의 포인터이다.

 

2) 다음 두 쌍의 선언문은 "상수의 포인터변수"와 "변수의 포인터상수"의 다름을 설명한다.

 

const int *ptr_to_constant;
int *const constant_ptr;

 

ptr_to_constant(상수를 가리키는 포인터변수)에 의해 접근되는 어떤 개체의 요소는 그 포인터에 의해 변경될 수 없다. 하지만  ptr_to_constant 자체는 다른 개체를 접근할 수 있도록 변경될 수 있다. 이와 반대로, constant_ptr(포인터상수)가 가리키는 개체는 수정될 것이다. 하지만 constant_ptr 자체는 항상 같은 위치(메모리 상의 주소)를 가리킬것이다.

Posted by '김용환'
,

library

c or linux 2005. 3. 13. 04:15

library 의 사용

윤 상배

yundream@coconut.co.kr



1절. 소개

이 문서는 library 의 사용방법에 대한 내용을 담고 있다. 왜 라이브러리가 필요한지, 라이브러리는 어떤 종류가 있으며, 어떻게 작성할수 있는지, 그리고 어떻게 사용하는지에 대해서 얘기하도록 할것이다. 그리고 중간중간에 이해를 돕기 위한 실제 코딩역시 들어갈 것이다.

라이브러리에 대한 이러저러한 세부적인 내용까지 다루진 않을것이다. 좀더 이론적인 내용을 필요로 한다면 Program Library HOWTO 를 참고하기 바란다. 이 문서에서는 라이브러리를 만들고 활용하는 면에 중점을 둘것이다. 그러므로 위의 문서는 이문서를 읽기전에 대충이라도 한번 읽어보도록 한다.

정적 라이브러리와 공유라이브러리는 일반적인 내용임으로 간단한 설명과 일반적인 예제를 드는 정도로 넘어갈 것이다. 그러나 동적라이브러리에 대해서는 몇가지 다루어야할 이슈들이 있음으로 다른 것들에 비해서 좀더 비중있게 다루게 될것이다.


2절. Library 이야기

2.1절. 라이브러리란 무엇인가

라이브러리란 특정한 코드(함수 혹은 클래스)를 포함하고 있는 컴파일된 파일이다. 이러한 라이브러리를 만드는 이유는 자주 사용되는 특정한 기능을 main 함수에서 분리시켜 놓음으로써, 프로그램을 유지, 디버깅을 쉽게하고 컴파일 시간을 좀더 빠르게 할수 있기 때문이다.

만약 라이브러리를 만들지 않고 모든 함수를 main 에 집어 넣는다면, 수정할때 마다 main 코드를 수정해야 하고 다시 컴파일 해야 할것이다. 당연히 수정하기도 어렵고 컴파일에도 많은 시간이 걸린다.

반면 라이브러리화 해두면 우리는 해당 라이브러리만 다시 컴파일 시켜서 main 함수와 링크 시켜주면 된다. 시간도 아낄뿐더러 수정하기도 매우 쉽다.


2.2절. 라이브러리의 종류

라이브러리에도 그 쓰임새에 따라서 여러가지 종류가 있다(크게 3가지). 가장 흔하게 쓰일수 있는 "정적라이브러리"와 "공유라이브러리", "동적라이브러리" 가 있다.

이들 라이브러리가 서로 구분되어지는 특징은 적재 시간이 될것이다.

정적라이브러리

정적라이브러리는 object file(.o로 끝나는) 의 단순한 모음이다. 정적라이브러린느 보통 .a 의 확장자를 가진다. 간단히 사용할수 있다. 컴파일시 적재되므로 유연성이 떨어진다. 최근에는 정적라이브러리는 지양되고 있는 추세이다. 컴파일시 적재되므로 아무래도 바이너리크기가 약간 커지는 문제가 있을것이다.

공유라이브러리

공유라이브러리는 프로그램이 시작될때 적재된다. 만약 하나의 프로그램이 실행되어서 공유라이브러리를 사용했다면, 그뒤에 공유라이브러리를 사용하는 모든 프로그램은 자동적으로 만들어져 있는 공유라이브러리를 사용하게 된다. 그럼으로써 우리는 좀더 유연한 프로그램을 만들수 잇게 된다.

정적라이브러리와 달리 라이브러리가 컴파일시 적재되지 않으므로 프로그램의 사이즈 자체는 작아지지만 이론상으로 봤을때, 라이브러리를 적재하는 시간이 필요할것이므로 정적라이브러리를 사용한 프로그램보다는 1-5% 정도 느려질수 있다. 하지만 보통은 이러한 느림을 느낄수는 없을것이다.

동적라이브러리

공유라이브러리가 프로그램이 시작될때 적재되는 반면 이것은 프로그램시작중 특정한때에 적재되는 라이브러리이다. 플러그인 모듈등을 구현할때 적합하다. 설정파일등에 읽어들인 라이브러리를 등록시키고 원하는 라이브러리를 실행시키게 하는등의 매우 유연하게 작동하는 프로그램을 만들고자 할때 유용하다.


2.2.1절. 왜 정적라이브러리의 사용을 지양하는가

예전에 libz 라는 라이브러리에 보안 문제가 생겨서 한창 시끄러웠던적이 있다. libz 라이브러리는 각종 서버프로그램에 매우 널리 사용되는 라이브러리였는데, 실제 문제가 되었던 이유는 많은 libz 를 사용하는 프로그램들이 "정적라이브러리" 형식으로 라이브러리를 사용했기 때문에, 버그픽스(bug fix)를 위해서는 문제가 되는 libz 를 사용하는 프로그램들을 다시 컴파일 시켜야 했기 때문이다. 한마디로 버그픽스 자체가 어려웠던게 큰 문제였었다. 도대체 이 프로그램들이 libz 를 사용하고 있는지 그렇지 않은지를 완전하게 알기도 힘들뿐더러, 언제 그많은 프로그램을 다시 컴파일 한단 말인가.

만약 libz 를 정적으로 사용하지 않고 "공유라이브러리" 형태로 사용한다면 bug fix 가 훨씬 쉬웠을것이다. 왜냐면 libz 공유라이브러리는 하나만 있을 것이므로 이것만 업그레이드 시켜주면 되기 때문이다.

아뭏든 이렇게 유연성이 지나치게 떨어진다는 측면이 정적라이브러리를 사용하지 않는 가장 큰 이유가 될것이다. 프로그램들의 덩치가 커지는 문제는 유연성 문제에 비하면 그리큰문제가 되지는 않을것이다.


3절. 라이브러리 만들고 사용하기

이번장에서는 실제로 라이브러리를 만들고 사용하는 방법에 대해서 각 라이브러리 종류별로 알아볼 것이다.


3.1절. 라이브러리화 할 코드

라이브러리의 이름은 libmysum 이 될것이며, 여기에는 2개의 함수가 들어갈 것이다. 하나는 덧셈을 할 함수로 "ysum" 또 하나는 뺄셈을 위한 함수로 "ydiff" 으로 할것이다. 이 라이브러리를 만들기 위해서 mysum.h 와 mysum.c 2개의 파일이 만들어질것이다.

mysum.h

int ysum(int a, int b); 
int ydiff(int a, int b);
			

mysun.c

#include "mysum.h"
int ysum(int a, int b)
{
    return a + b; 
}
int ydiff(int a, int b)
{
    return a - b;
}
			


3.2절. 정적라이브러리 제작

정적라이브러리는 위에서 말했듯이 단순히 오브젝트(.o)들의 모임이다. 오브젝트를 만든다음에 ar 이라는 명령을 이용해서 라이브러리 아카이브를 만들면 된다.

[root@localhost test]# gcc -c mysum.c
[root@localhost test]# ar rc libmysum.a mysum.o
			
아주아주 간단하다. 단지 ar 에 몇가지 옵션만을 이용해서 libmysum 이란 라이 브러리를 만들었다. 'r' 은 libmysum.a 라는 라이브러리 아카이브에 새로운 오브젝트를 추가할것이라는 옵션이다. 'c' 는 아카이브가 존재하지 않을경우 생성하라는 옵션이다.

이제 라이브러리가 실제로 사용가능한지 테스트해보도록 하자.

예제 : print_sum.c

#include "mysum.h"
#include <stdio.h>
#include <string.h>

int main()
{
    char oper[5];
    char left[11];
    char right[11];
    int  result;

    memset(left, 0x00, 11);
    memset(right, 0x00, 11);

    // 표준입력(키보드)으로 부터  문자를 입력받는다.
    // 100+20, 100-20 과 같이 연산자와 피연산자 사이에 공백을 두지 않아야 한다.  
    fscanf(stdin, "%[0-9]%[^0-9]%[0-9]", left, oper, right);
    if (oper[0] == '-')
    {
        printf("%s %s %s = %d\n", left, oper, right, 
                        ydiff(atoi(left), atoi(right)));
    }
    if (oper[0] == '+')
    {
        printf("%s %s %s = %d\n", left, oper, right, 
                        ysum(atoi(left), atoi(right)));
    }
}
			

위의 프로그램을 컴파일 하기 위해서는 라이브러리의 위치와 어떤 라이브러리를 사용할것인지를 알려줘야 한다. 라이브러리의 위치는 '-L' 옵션을 이용해서 알려줄수 있으며, '-l' 옵션을 이용해서 어떤 라이브러리를 사용할것인지를 알려줄수 있다. -l 뒤에 사용될 라이브러리 이름은 라이브러리의 이름에서 "lib"와 확장자 "a"를 제외한 나머지 이름이다. 즉 libmysum.a 를 사용할 것이라면 "-lmysum" 이 될것이다.

[root@localhost test]# gcc -o print_sum print_num.c -L./ -lmysum
			
만약 우리가 사용할 라이브러리가 표준 라이브러리 디렉토리경로에 있다면 -L 을 사용하지 않아도 된다. 표준라이브러리 디렉토리 경로는 /etc/ld.so.conf 에 명시되어 있다.

정적라이브러리 상태로 컴파일한 프로그램의 경우 컴파일시에 라이브러리가 포함되므로 라이브러리를 함께 배포할 필요는 없다.


3.3절. 공유라이브러리 제작 / 사용

print_sum.c 가 컴파일되기 위해서 사용할 라이브러리 형태가 정적라이브러리에서 공유라이브러리로 바뀌였다고 해서 print_sum.c 의 코드가 변경되는건 아니다. 컴파일 방법역시 동일하며 단지 라이브러리 제작방법에 있어서만 차이가 날뿐이다.

이제 위의 mysum.c 를 공유라이브러리 형태로 만들어보자. 공유라이브러리는 보통 .so 의 확장자를 가진다.

[root@localhost test]# gcc -fPIC -c mysum.c
[root@localhost test]# gcc -shared -W1,-soname,libmysutff.so.1 -o libmysum.so.1.0.1 mysum.o
[root@localhost test]# cp libmysum.so.1.0.1 /usr/local/lib
[root@localhost test]# ln -s /usr/local/lib/libmysum.so.1.0.1 /usr/local/lib/libmysum.so
			
우선 mysum.c 를 -fPIC 옵션을 주어서 오브젝트 파일을 만들고, 다시 gcc 를 이용해서 공유라이브러리를 제작한다. 만들어진 라이브러리를 적당한 위치로 옮기고 나서 ln 을 이용해서 컴파일러에서 인식할수 있는 이름으로 심볼릭 링크를 걸어준다.

컴파일 방법은 정적라이브러리를 이용한 코드의 컴파일 방법과 동일하다.

[root@coco test]# gcc -o print_sum print_sum.c -L/usr/local/lib -lmysum
			

공유라이브러리는 실행시에 라이브러리를 적재함으로 프로그램을 배포할때는 공유라이브러리도 함께 배포되어야 한다. 그렇지 않을경우 다음과 같이 공유라이브러리를 찾을수 없다는 메시지를 출력하면서 프로그램 실행이 중단될 것이다.

[root@coco library]# ./print_sum
./print_sum: error while loading shared libraries: libmysub.so: cannot open shared object file: No such file or directory
			
위와 같은 오류메시지를 발견했다면 libmysub.so 가 시스템에 존재하는지 확인해 보자. 만약 존재하는데도 위와 같은 오류가 발생한다면 이는 LD_LIBRARY_PATH 나 /etc/ld.so.conf 에 라이브러리 패스가 지정되어 있지 않을 경우이다. 이럴때는 LD_LIBRARY_PATH 환경변수에 libmysub.so 가 있는 디렉토리를 명시해주거나, /etc/ld.so.conf 에 디렉토리를 추가시켜주면 된다.

만약 libmysub.so 가 /usr/my/lib 에 복사되어 있고 환경변수를 통해서 라이브러리의 위치를 알려주고자 할때는 아래와 같이 하면된다.

[root@localhost test]# export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/my/lib 
			
그렇지 않고 ld.so.conf 파일을 변경하길 원한다면(이럴경우 관리자 권한을 가지고 있어야 할것이다) ld.so.conf 에 라이브러리 디렉토리를 추가하고 ldconfig 를 한번 실행시켜주면 된다.
[root@localhost test]# cat /usr/my/lib >> /etc/ld.so.conf 
[root@localhost test]# ldconfig
			
ldconfig 를 실행시키게 되면 /etc/ld.so.conf 의 파일을 참조하여서 /etc/ld.so.cache 파일이 만들어지고, 프로그램은 ld.so.cache 의 디렉토리 경로에서 해당 라이브러리가 있는지 찾게 된다.


3.4절. 동적라이브러리의 사용

동적라이브러리라고 해서 동적라이브러리를 만들기 위한 어떤 특별한 방법이 있는것은 아니다. 일반 공유라이브러리를 그대로 쓰며, 단지 실행시간에 동적라이브러리를 호출하기 위한 방법상의 차이만 존재할 뿐이다.

정적/공유 라이브러리가 라이브러리의 생성방법과 컴파일방법에 약간의 차이만 있고 코드는 동일하게 사용되었던것과는 달리 동적라이브러리는 코드자체에 차이가 있다. 그럴수밖에 없는게, 동적라이브러리는 프로그램이 샐행되는 중에 특정한 시점에서 부르고 싶을때 라이브러리를 적재해야 하므로, 라이브러리를 적재하고, 사용하고 해제(free) 하기 위한 코드를 생성해야 하기 때문이다.

linux 에서는 이러한 라이브러리를 호출하기 위한 아래와 같은 함수들을 제공한다. 아래의 함수들은 solaris 에서 동일하게 사용될수 있다.

#include <dlfcn.h>

void *dlopen (const char *filename, int flag);
const char *dlerror(void);
void *dlsym(void *handle, char *symbol);
int dlclose(void *handle); 
			
dlopen 은 동적라이브러리를 적재하기 위해서 사용된다. 첫번째 아규먼트인 filename 은 /usr/my/lib/libmysum.so 와 같이 적재하기 원하는 라이브러리의 이름이다. 만약 적재시킬 라이브러리의 이름이 절대경로로 지정되어 있지 않을경우에는 LD_LIBRARY_PATH 에 등록된 디렉토리에서 찾고, 여기에서도 찾지 못할경우 /etc/ld.so.cache 에 등록된 디렉토리 리스트에서 찾게 된다. dlopen 이 성공적으로 호출되면 해당 라이브러리에 대한 handle 값을 넘겨 준다. flag 는 RTLD_LAZY와 RTLD_NOW 중 하나를 정의할수 있다. RTLD_LAZY는 라이브러리의 코드가 실행시간에 정의되지 않은 심볼을 해결하며, RTLD_NOW 는 dlopen 의 실행이 끝나기전에(return 전에) 라이브러리에 정의되지 않은 심볼을 해결한다.

dlerror 는 dl 관련함수들이 제대로 작동을 수행하지 않았을경우 에러메시지를 되돌려준다. dleooro(), dlsym(), dlclose(), dlopen()중 마지막 호출된 함수의 에러메시지를 되돌려준다.

dlsym 은 dlopen 을 통해서 열린라이브러리를 사용할수 있도록 심볼값을 찾아준다. 심볼이라고 하면 좀 애매한데, 심볼값은 즉 열린라이브러리에서 여러분이 실제로 호출할 함수의이름이라고 생각하면 된다. handle 는 dlopen 에 의해서 반환된 값이다. symbol 은 열린라이브러리에서 여러분이 실제로 부르게될 함수의 이름이다. dlsym 의 리턴값은 dlopen 으로 열린 라이브러리의 호출함수를 가르키게 된다. 리턴값을 보면 void * 형으로 되어 있는데, void 형을 사용하지 말고 호출함수가 리턴하는 형을 직접명시하도록 하자. 이렇게 함으로써 나중에 프로그램을 유지보수가 좀더 수월해진다.


3.5절. 동적라이브러리를 사용하여 프로그램의 확장성과 유연성을 높이기

동적라이브러리는 실행시간에 필요한 라이브러리를 호출할수 있음으로 조금만(사실은 아주 많이겠지만 T.T) 신경쓴다면 매우 확장성높고 유연한 프로그램을 만들수 있다.

동적라이브러리의 가장 대표적인 예가 아마도 Plug-in 이 아닐까 싶다. 만약에 모질라 브라우저가 plug-in 을 지원하지 않는 다면 우리는 새로운 기능들 이 추가될때 마다 브라우저를 다시 코딩하고 컴파일하는 수고를 해야할것이다. 그러나 동적라이브러리를 사용하면 브라우저를 다시 코딩하고 컴파일 할필요 없이, 해당 기능을 지원하는 라이브러리 파일만 받아서 특정 디렉토리에 설치하기만 하면 될것이다. 물론 동적라이브러리를 사용하기만 한다고 해서 이러한 기능이 바로 구현되는 건 아니다. Plug-in 의 효율적인 구성을 위한 표준화된 API를 제공하고 여기에 맞게 Plug-in 용 라이브러리를 제작해야만 할것이다.

우리가 지금까지 얘로든 프로그램을 보면 현재 '+', '-' 연산을 지원하고 있는데, 만약 'x', '/' 연산을 지원하는 라이브러리가 만들어졌다면, 우리는 프로그램의 코딩을 다시해야만 할것이다. 이번에는 동적라이브러리를 이용해서 plug-in 방식의 확장이 가능하도록 프로그램을 다시 만들어 보도록 할것이다.


3.5.1절. 동적라이브러리를 이용한 예제

동적라이브러리를 이용해서 main 프로그램의 재코딩 없이 추가되는 새로운 기능을 추가시키기 위해서는 통일된 인터페이스를 지니는 특정한 형식을 가지도록 라이브러리가 작성되어야 하며, 설정파일을 통하여서 어떤 라이브러리가 불리어져야 하는지에 대한 정보를 읽어들일수 있어야 한다. 그래서 어떤 기능을 추가시키고자 한다면 특정 형식에 맞도록 라이브러리를 제작하고, 설정파일을 변경하는 정도로 만들어진 새로운 라이브러리의 기능을 이용할수 있어야 한다.

설정파일은 다음과 같은 형식으로 만들어진다. 설정파일의 이름은 plugin.cfg 라고 정했다.

+,ysum,libmysum.so
-,ydiff,libmysum.so
				
'-' 연산에대해서는 libmysum.so 라이브러리를 호출하며, ydiff 함수를 사용한다. '=' 연산에 대해서는 libmysum.so 라이브러리를 호출하고 ysum 함수를 사용한다는 뜻이다. 설정파일의 이름은 plugin.cfg 로 하기로 하겠다.

다음은 동적라이브러리로 만들어진 print_sum 의 새로운 버젼이다.

예제 : print_sum_dl.c

#include <stdlib.h>
#include <stdio.h>
#include <dlfcn.h>
#include <string.h>

struct input_data
{
    char    oper[2];
    char    func[10]; 
    char    lib[30];
};

int main(int argc, char **argv)
{
    char oper[2];
    char left[11];
    char right[11];
    char buf[50];
    char null[1];
    int data_num;

    struct input_data plug_num[10]; 

    void *handle;

    int (*result)(int, int);
    int i;
    char *error;

    FILE *fp;

    // 설정파일을 읽어들이고 
    // 내용을 구조체에 저장한다. 
    fp = fopen("plugin.cfg", "r");
    data_num = 0;
    while(fgets(buf, 50, fp) != NULL)
    {
        buf[strlen(buf) -1] = '\0';
        sscanf(buf, "%[^,]%[,]%[^,]%[,]%[^,]", plug_num[data_num].oper, 
                                               null,    
                                               plug_num[data_num].func, 
                                               null,
                                               plug_num[data_num].lib);
        data_num ++;
    }
    fclose(fp);

    printf("> ");
    memset(left, 0x00, 11);
    memset(right, 0x00, 11);
    fscanf(stdin, "%[0-9]%[^0-9]%[0-9]", left, oper, right);

    // 연산자를 비교해서 
    // 적당한 라이브러리를 불러온다. 
    for (i  = 0; i < data_num ; i++)
    {
        int state; 
        if ((state = strcmp(plug_num[i].oper, oper)) == 0) 
        {
            printf("my operator is      : %s\n", plug_num[i].oper);
            printf("my call function is : %s\n", plug_num[i].func);
            break;
        }
    }    

    if (i == data_num)
    {
        printf("--> unknown operator\n");
        exit(0);
    }

    handle = dlopen(plug_num[i].lib, RTLD_NOW);
    if (!handle)
    {
        printf("open error\n");
        fputs(dlerror(), stderr);
        exit(1);
    }

    // 연산자에 적당한 함수를 불러온다. 
    result = dlsym(handle, plug_num[i].func);
    if ((error = dlerror()) != NULL)
    {
        fputs(error, stderr);
        exit(1);
    }

    printf("%s %s %s = %d\n",left, oper, right, result(atoi(left), atoi(right)) ); 

    dlclose(handle);
}
				
위의 예제 프로그램은 다음과 같이 컴파일되어야 한다. 라이브러리 파일의 위치는 /usr/my/lib 아래에 있는것으로 하며, 라이브러리 찾기 경로에 등록되어 있다고 가정하겠다.
[root@localhost test]# gcc -o print_sum_dl print_sum_dl.c -ldl 
				
이 프로그램을 실행하면 사용자의 입력을 기다리는 "> "가 뜨게 되고, 여기에 계산하기 원하는 값을 입력하면 된다. 현재는 '+'와 '-' 연산만을 지원하며, 연산자와 피연산자들 간에 간격이 없어야 한다. 다음은 실행결과 화면이다.
  
[root@localhost test]# ./print_sum_dl
> 99+99
my operator is      : +
my call function is : ysum
99 + 99 = 198
[root@localhost test]#
				
사용자가 프로그램을 실행하면 프로그램은 사용자의 입력을 받아들이고 sscanf 를 이용해서 연산자와 피연산자를 구분하게 된다. 그리고 피연산자를 값으로 하여, 설정파일에 설정된 라이브러리를 불러들이고(dlopen) 해당 함수를 가져와서(dlsym) 실행시키게 된다.

자 이렇게 해서 우리는 '+', '-' 연산이 가능한 프로그램을 하나 만들게 되었다. 그런데 A 라는 개발자가 '*','/' 연산도 있으면 좋겠다고 생각해서 아래와 같은 코드를 가지는 '*', '/' 연산을 위한 라이브러리를 제작하였다.

예제 : mymulti.h

int multi(int a, int b);
int div(int a, int b);
				
예제 : mymulti.c
int multi(int a, int b)
{
    return a * b;
}

int div(int a, int b)
{
    return a / b;
}
				

A 라는 개발자는 이것을 다음과 같이 공유라이브러리 형태로 만들어서 간단한 라이브러리의 설명과 함께 email 로 전송했다.

[root@localhost test]# gcc -c -fPIC mymulti.c
[root@localhost test]# gcc -shared -W1,-soname,libmymulti.so.1 -o libmymulti.so.1.0.1 mymulti.o
				

라이브러리를 받았으므로 새로운 라이브러리가 제대로 작동을 하는지 확인을 해보도록 하자. 우선 libmymulti.so.1.0.1 을 /usr/my/lib 로 복사하도록 하자. 그다음 설정파일에 다음과 같은 내용을 가지도록 변경 시키도록 하자.

 
+,ysum,libmystuff.so
-,ydiff,libmystuff.so
*,ymulti,libmymulti.so.1.0.1
/,ydiv,libmymulti.so.1.0.1
				
이제 print_sum_dl 을 실행시켜보자.
[root@localhost test]# ./print_sum_dl
> 10*10
my operator is      : *
my call function is : ymulti
10 * 10 = 100

[root@localhost test]# ./print_sum_dl
> 10/10
my operator is      : /
my call function is : ydiv
10 / 10 = 1
				
print_sum_dl.c 의 원본파일의 아무런 수정없이 단지 설정파일만 변경시켜 줌으로써 기존의 print_sum_dl 에 "곱하기"와 "나누기"의 새로운 기능이 추가 되었다.

위에서도 말했듯이 이러한 Plug-in 비슷한 기능을 구현하기 위해서는 통일된 함수 API가 제공될수 있어야 한다.


4절. 결론

여기에 있는 내용중 동적라이브러리에 대한 내용은 솔라리스와 리눅스에서만 동일하게 사용할수 있다. Hp-Ux 혹은 윈도우에서는 사용가능하지 않는 방법이다. 이에 대한 몇가지 해법이 존재하는데, 이 내용은 나중에 시간이 되면 다루도록 하겠다. 어쨋든 솔라리스와 리눅스 상에서 코딩되고 윈도우 혹은 다른 유닉스로 포팅될 프로그램이 아니라면 위의 방법을 사용하는데 있어서 문제가 없을것이다.

 

[펌] http://www.joinc.co.kr/modules.php?name=News&file=article&sid=66#AEN22

Posted by '김용환'
,
질문: 이중 포인터 사용 용도 질문(내공있음) aerowon20 / 2004-06-16 14:01
남이 짠 소스 분석 도중에 모르는 부분이 나와서여..
사실 이중포인터 사용을 해 본적이 없어서리 이렇게 고수님의 답변을 기다립니다.
이 소스의 의도와 용도는 어디 일까여?

char ** fsc_strtok(char *src, char *deli, int *nCnt)
{
char ** buff;
char * szBuff;
int nIndex;

if((szBuff = (char*)strtok(src, deli)) == NULL) {
printf("Strtok is NULL----------------------------\n");
return NULL;
}
buff = (char **)malloc(sizeof(char *));

if(buff == NULL)
return NULL;

nIndex = 0;

*buff = szBuff;

while( (szBuff = strtok(NULL, deli)) != NULL ) {
nIndex ++;
buff = (char **)realloc( buff, sizeof(char *) * ( nIndex + 1));
*(buff+nIndex) = szBuff;
}
*nCnt = nIndex;
return buff;
}
답변: re: 이중 포인터 사용 용도 질문(내공있음) leo0502 / 2004-06-16 11:02
일단 소스의 의도와 용도는 말이죠... 어떤 문장을 scan 해서 (첫번째 파라메터) 그 문장을 두번째 파라메터의 문자로 tokenize 한 뒤에 그 tokenize 된 문장의 각각의 주소를 이중 포인터로 만들어진 a...

Posted by '김용환'
,
질문: [C언어]이 프로그램 내에서 이중 포인터를 사용하는 이유가 뭔가요? nucleus824 / 2005-01-21 22:39
링크드리스트를 작성한 프로그램입니다..

여기서 이해 안되는곳이 insertNode라는 함수에서 첫번째 인자값을

이중 포인터로 선언되어있는데 왜 그런가요??

#include
#include

struct listNode {

char data;
struct listNode *nextPtr;

};

typedef struct listNode ListNode;
typedef ListNode *ListNodePtr;

ListNodePtr createNode(void);
void destroyNode(ListNodePtr);
int insertNode(ListNodePtr *, ListNodePtr);
char deleteNode(ListNodePtr *, char);
int isEmpty(ListNodePtr);
void printList(ListNodePtr);
void instructions(void);

int main()
{
ListNodePtr startPtr = NULL;
ListNodePtr creNodePtr = NULL;
int choice;
char item;

instructions();
printf("?");
scanf("%d", &choice);

while(choice != 3) {
switch(choice) {

case 1:
if( (creNodePtr = createNode() ) == NULL ) break;
if( insertNode( &startPtr, creNodePtr) == -1) {
destroyNode( creNodePtr );
break;
}

printList( startPtr );
break;

case 2:
if( !isEmpty( startPtr ) ) {
printf( "Enter character to be deleted: ");
scanf( "\n%c", &item );
if ( deleteNode( &startPtr, item ) ) {
printf("%c deleted.\n", item );
printList( startPtr );
}
else
printf( "%c not found.\n\n", item );
}
else
printf( "List is empty.\n\n" );
break;
default:
printf( "Invalid choice.\n\n" );
instructions();
break;
}

printf( "? " );
scanf( "%d", &choice );
}

printf( "End of run.\n" );
return 0;
}


ListNodePtr createNode(void) {
ListNodePtr newPtr;

newPtr = (ListNodePtr)malloc( sizeof( ListNode ) );

if( newPtr != NULL ) {
newPtr->nextPtr = NULL;
printf( "Enter a character: " );
scanf( "\n%c", &newPtr->data );
return newPtr;
} else {
printf( "Node is not created. No memory available.\n" );
return NULL;
}
}

void destroyNode( ListNodePtr desPtr ) {
free( desPtr );
}

void instructions( void ) {
printf( "Enter your choice:\n"
" 1 to insert an element into the list.\n"
" 2 to delete an element from the list.\n"
" 3 to end.\n");
}

int insertNode( ListNodePtr *sPtr, ListNodePtr newPtr ) {
ListNodePtr previousPtr, currentPtr;
char keyValue;

previousPtr = NULL;
currentPtr = *sPtr;

keyValue = newPtr->data;

while ( currentPtr!=NULL && keyValue > currentPtr->data ) {
previousPtr = currentPtr;
currentPtr = currentPtr->nextPtr;
}

if( currentPtr != NULL && currentPtr->data == keyValue ) {
printf("\n%c is alread existed...\n", keyValue );
return -1;
}

if( previousPtr == NULL ) {
newPtr->nextPtr = *sPtr;
*sPtr = newPtr;
}
else {
previousPtr->nextPtr = newPtr;
newPtr->nextPtr = currentPtr;
}

return 0;
}

char deleteNode( ListNodePtr *sPtr, char keyValue ) {
ListNodePtr previousPtr, currentPtr, tempPtr;

if( keyValue == (*sPtr)->data ) {
tempPtr = *sPtr;
*sPtr = ( *sPtr )->nextPtr;
destroyNode( tempPtr);
return keyValue;
}
else {
previousPtr = *sPtr;
currentPtr = ( *sPtr )->nextPtr;

while( currentPtr != NULL && currentPtr->data != keyValue )
{
previousPtr = currentPtr;
currentPtr = currentPtr->nextPtr;
}

if( currentPtr != NULL ) {
tempPtr = currentPtr;
previousPtr->nextPtr = currentPtr->nextPtr;
destroyNode( tempPtr );
return keyValue;
}
}

return '\0';
}

int isEmpty( ListNodePtr sPtr)
{
return sPtr == NULL;
}

void printList( ListNodePtr currentPtr )
{
if( currentPtr == NULL )
printf( "List is empty.\n\n" );
else {
printf( "The list is:\n" );

while( currentPtr != NULL ) {
printf( "%c -> ", currentPtr->data );
currentPtr = currentPtr->nextPtr;
}

printf( "NULL\n\n" );
}
}
답변: re: [C언어]이 프로그램 내에서 이중 포인터를 사용하는 이유가 뭔가요? iindra81 / 2005-01-21 01:23
포인터는 이해하기가 어렵죠 ㅠ ㅅ ㅠ 제대로 개념을 잡지 못하면 상당히 헤메게 됩니다..;; (나는 과연 제대로 알고있을까..ㅋ) 아무튼 문제에 대한 답변을 하자면요.. 위의 프로그램에서 ListNode x 라고 ...

'c or linux' 카테고리의 다른 글

library  (0) 2005.03.13
[펌] 이중 포인터 사용 용도 질문(내공있음)  (0) 2005.03.12
assert의 응용  (0) 2005.03.09
함수의 인수를 확인하기 위해 assertion을 사용한다  (0) 2005.03.09
원형(prototype) 강화하기  (0) 2005.03.09
Posted by '김용환'
,

assert의 응용

c or linux 2005. 3. 9. 01:49

assert((ch=getchar())!=EOF);

는 잘못되었다. assert 구문은 디버그용이라서 실제 배포용 버젼에서는 assert구문이 생략된다. 그렇다면! ch=getchar()는 문제가 된다! 이를 빼고 assert(ch!=EOF)라고 하자

 

switch case 문에서 case목록에 포함이 되지 않을 경우 default에 assert(FALSE)를 추가한다!!!!

 

 

 

메모리 블럭을 항달한 뒤에 초기화하지 않고, 저장되어 있던 내용을 사용한다.

 

메모리 블럭의 소유권을 해제(free)한 뒤에도, 그 내용을 계속 사용(reference)한다.

 

블럭을 확장하기 위해 realloc을 호출하여 블럭의 내용이 이동중인데도, 이전 위치의 내용을 계속 사용한다.

 

블럭은 할당되었으나, 그 포인터를 저장하지 않아서 블럭을 읽어버린다.

 

블럭의 경계를 넘어, 읽고 쓰기를 한다.

 

에러 상황을 경고하지 못한다.

 

 

Posted by '김용환'
,
함수의 인수를 확인하기 위해 assertion을 사용한다! | Writing Solid Code 2004/12/30 16:28
http://blog.naver.com/bravedog/100008948045
  1  /* memcpy -- 겹치지 않는 메모리 블록을 복사한다. */
  2
  3  void *memcpy(void *pvTo, void *pvFrom, size_t size)
  4  {
  5      void *pbto = (void *)pvTo;
  6      void *pbFrom = (void *)pvFrom;
  7
  8      if(pvTo == NULL || pvFrom == NULL){
  9          fprintf(stderr, "bad args in memcpy\n");
 10          abort();
 11      }
 12      while(size-- > 0)
 13          *pbTo++ = *pbFrom++;
 14      return (pvTo);
 15  }
 16
 17
 18  /*
 19   * 위의 프로그램은 void * 변수들이 NULL을 가리키고 있을시의 exception 처리를 
 20   * 보여주고 있다. 헌데, 저 줄은... 낭비가 아니라고 할수가 없다.... 
 21   * 낭비이다!!!!! debug할때는 필요하지만 배포용으로 컴파일 할때는 필요가 없다
 22   * 그래서!!!!
 23   * 다음과 같이 #ifdef , #endif 를 이용하여 DEBUG가 정의되었을 때만 실행하도록
 24   * 변경하자~~~~~~~~~~
 25   */
 26
 27  void *memcpy(void *pvTo, void *pvFrom, size_t size)
 28  {
 29      void *pbto = (void *)pvTo;
 30      void *pbFrom = (void *)pvFrom;
 31
 32  #ifdef DEBUG
 33      if(pvTo == NULL || pvFrom == NULL){
 34          fprintf(stderr, "bad args in memcpy\n");
 35          abort();
 36      }
 37  #endif
 38      while(size-- > 0)
 39          *pbTo++ = *pbFrom++;
 40      return (pvTo);
 41  }
 42
 43  /* 자~ 이젠 Debug할때만 (DEBUG가 정의되어 있을때만) 저 안의 구문이 실행되도록
 44   * 바꾸어 보았다! 쉽다...  ㅋㅋㅋㅋ
 45   * 헌데~ 오늘 말하고 싶은 내용은 이 7줄의 #ifdef 와 #endif 안의 내용을 한줄로
 46   * 처리할 수 있다. assert()는 ANSI assert.h 헤더 파일에 정의되어 있는 assert라는
 47   * 이름의 매크로에 숨겨 두기 때문이다.
 48   * assert는 우리가 앞에서 보았던 #ifdef 코드를 재구성한 형태에 불과하다~~~~!!!!
 49   * 한줄로 바꾸어 볼까?
 50   */
 51
 52  void *memcpy(void *pvTo, void *pvFrom, size_t size)
 53  {
 54      void *pbto = (void *)pvTo;
 55      void *pbFrom = (void *)pvFrom;
 56
 57      assert(pvTo != NULL && pvFrom != NULL);
 58
 59      while(size-- > 0)
 60          *pbTo++ = *pbFrom++;
 61      return (pvTo);
 62  }
 63
 64
 65
 66  /*--------------------------------------------------------------------------------
 67   * assert 이용시 주의사항
 68   *
 69   * assert는 단지 집어넣기만 하면 되는 매크로는 아니다. 이것은 조심스럽게 정의해서
 70   * 프로그램의 판매용과 디버그 버전 사이에 중대한 차이를 야기하지 않도록 해야 한다.
 71   * assert는 메모리를 혼란시키지 말아야 하고 그냥두면 초기화되지 않는 데이터를 
 72   * 초기화해야 한다. 그렇지 않으면 어떤 부작용을 일으킬 수도 있다. 
 73   * 이것이 assert가 함수가 아니고 매크로인 이유이다.
 74   * assert가 함수라면, 이것을 호출하면 예기치 않은 메모리나 코드스워핑(swapping)을 
 75   * 발생시킬 수 있다. assert를 사용하는 프로그래머는, 이것을 시스템이 어떤 상태에 
 76   * 있든 안전하게 사용할 수 있는 무해한 검사로 본다는 점을 기억해야 한다.
 77   * ------------------------------------------------------------------------------*/
 78
 79
 80
 81  /* assert를 직접 정의하여 자신만의 assert를 만들어 DEBUG용으로 쓸수있다*/
 82  #ifdef DEBUG
 83      void my_assert(char *, unsigned);       /* 원형 */
 84      #define ASSERT(f)       \
 85          if (f)              \
 86              NULL;           \
 87          else
 88              my_assert(_FILE_,_LINE_)
 89  #else
 90      #define ASSERT(f) NULL
 91  #endif
 92  /* ASSERT가 실패했을 때, 이것은 선행처리기에 의해, _FILE_과 _LINE_ 매크로를 통해 
 93   * 제공되는 파일 이름과 행번호를 갖고 my_assert를 호출한다.
 94   * my_assert는  stderr에 에러 메시지를 표싷고 수행을 중단시킨다.
 95   */
 96
 97  void my_assert(char *strFile, unsigned uLine)
 98  {
 99      fflush(stdout);
100      fpinrtf(stderr, "\nAssertion failed : %s, line %u\n",
101              stdFile, uLine);
102      fflush(stderr);
103      abort();
104  }
105

 

 

⊙ 요약 ⊙

◇ 프로그램을 판매용과 디버깅용의 두 가지 버전으로 관리한다. 판매용 버전은 군더더기 코드를

    빼고 날씬하게 유지한다. 그러나 버그를 빨리 발견하기 위해 가능한 한 디버깅 버전을 사용한다.

◇ assertion은 디버깅 검사 코드를 작성하는 속성의 방법이다. 결코 발생하지 않을 규정에 어긋나

    는 조건(illegal condition)을 발견하기 위해 이를 사용한다. 이들 조건(illegal condition)과 에

    러 조건(error condition)을 혼동하지 않도록 주의한다. 익서은 최종적인 프로그램에서 처리해

    야 한다.

◇ 함수의 인자(argument)의 유효성을 검사하고, 프로그래머들이 정의되지 않은(undefined) 어

    떤 작업을 하면 경고하기 위해 assertion을 사용한다. 여러분의 함수들을 보다 엄격하게 정의할

    수록, 인자의 유효성을 검사하는 것이 보다 쉬워진다.

◇ 일단 함수를 작성하면, 그것을 재검토하고 스스로에게 "나는 무엇을 가정하는가?"라고 묻는다.

    가정한 것이 있다는 것을 발견하면, 여러분의 가정이 항상 유효한지 assert하거나, 또는 가정을

    제거하기 위해 코드를 재작성한다. 또한 "이 코드에서 가장 잘못될 만한 것은 무엇인가? 그리고

    그 문제를 어떻게 하면 자동으로 발견할 수 있는가?"를 묻는다. 가능한 한 가장 빠른 시간에 버

    그를 발견하는 검사를 구현하도록 노력한다.

◇ 교과서는 프로그래머에게 방어적으로 프로그램을 작성하도록 권고한다. 그러나 이러한 코딩 방

    식은 버그를 감춘다는 점을 기억해야 한다. 벙어적인 코드를 작성할 때는, "발생할 수 없는" 경

    우가 발생할 경우, 경고해 줄 수 있는 assertion을 사용한다.

Posted by '김용환'
,