2010년 10월 15일 금요일

ViewGroup 모서리 둥글게 둥글게

ViewGroup 모서리 둥글게

 

res -> drawable ->radius.xml

<?xml version="1.0" encoding="UTF-8"?>

<shape xmlns:android="http://schemas.android.com/apk/res/android">

    <solid android:color="#99FFFFFF"/>

    <corners android:radius="15dip"/>

    <padding android:left="0dp" android:top="0dp" android:right="0dp" android:bottom="0dp" /> 

</shape>



main.xml


<TableLayout android:layout_width="fill_parent"

android:layout_height="wrap_content"

android:background="@drawable/com_rounded_corner"

android:padding="10dip"

android:orientation="vertical">

...

</TableLayout>


출처 : http://lomohome.com/317

2010년 8월 31일 화요일

00_Mobile Internet business

Check out this SlideShare Presentation:

2010년 1월 30일 토요일

C로 개발을 할땐...

1. 컴파일시 -Wall 옵션을 사용하여 최대한 상세한 컴파일 정보를 참조하면서 개발하라.
   
gcc test.c test -o dump -Wall

2. 외부 변수 참조시 외부 전역변수대신 static으로 선언하고, 변수값을 읽는 전용함수를 사용하라.
static int value    // 내부적으로 쓸 외부값을 받을 변수

// 값을 가져 온다.
int get_value()  
{
   return (value);
}  

// 값을 셋팅 한다.
int set_value(int new_value)  
{
   return (value = new_value);
}


2010년 1월 29일 금요일

가변인수 : stdarg.h

1. 생략기호를 사용하는 함수 원형을 제공한다. ( ...을 사용한다는 말이다.)
2. 함수 정의에서 va_list형 변수를 만든다.
3. 인수 리스트에 대한 변수를 초기화하기 위해서 매크로를 사용한다.
4. 인수 리스트에 접근하는데 매크로를 이용한다.
5. 지우기 위해 매크로를 이용한다.

void f1(int n, ...);    // 유효
int f2(int n, const char *s, ...);   //유효
char f3(char 1, ..., char c2);  //무효
double f3();   // 무효

첫번째 선언의 맨오른쪽 인수인 "..."은 표준 명칭으로 parmN 이라고 한다.
parmN은 첫째는 n이 되고, 두번째는 s가 된다. 다음과 같이 사용될 수 있다.

f1(2, 200, 400);          // 2개의 추가 인수
f(4, 13, 117, 18, 23);  // 4개의 추가 인수

인수의 맨앞 숫자는 뒤 따라올 인수들의 수를 나타낸다.

double sum(int lim,...)
{
   va_list ap;               // 생략된 인수를 담을 객체를 선언한다.
   ...
}

stdargs.h에 선언된 va_start()매크로를 사용해서 선언된 va_list변수에 인수 리스트를 복사한다.

va_start(ap, lim);   // 인수 리스트에 대한 ap를 초기화

다음으로 넘겨준 인수를 가져와야 할 것이다. 그래야 지지고 볶고 할것 아닌가..
va_arg()를 사용하여 데이터에 엑세스 한다.

int toc;
double tic;
tic = va_arg(ap, double);
toc = va_arg(ap, int)

인수의 형식은 va_arg()의 두번째 인수인 규격에 정확히 맞아야 한다.

마지막으로 리스트 선언된 객체를 지운다

va_end(ap);    // 해제

인수를 해제하기전 데이터를 백업 받아야 한다면 다음 매크로를 사용하면된다.

//arg_copy();
va_list ap;
va_list apcopy;
double
double tic;
int toc;
...
va_start(ap, lim);             // 인수 리스트로 ap를 초기화한다.
va_copy(apcopy, ap);        // ap를 ap copy에 복사한다.
tic = va_arg(a, double);    // 첫 번째 인수를 검색한다.
toc = va_arg(ap, int);       // 두 번째 인수를 검색한다.

  1 #include <stdio.h>
  2 #include <stdarg.h>
  3 double sum(int, ...);
  4
  5 int main(void)
  6 {
  7    double s, t;
  8    s = sum(3,1.1,2.2,3.3);
  9    t = sum(6,1.1,2.1,3.1,4.1,5.1,6.1);
 10    printf("%g %g\n", s, t);
 11    return 0;
 12 }
 13
 14 double sum(int lim, ...)
 15 {
 16    va_list ap;
 17    double tot = 0;
 18    int i;
 19    va_start(ap, lim);
 20    for(i=0;i<lim;i++)
 21       tot += va_arg(ap, double);
 22    va_end(ap);
 23    return tot;
 24 }

va_arg는 별도의 카운터를 하지 않아도 다음 값을 가져오는 걸로 확인되었다.

memcpy(), memmove()

하나의 배열은 다른 배열에 할당할 수 없으므로 하나의 배열을 한 요소씩 다른 배열에 복사하는 루프를 사용해 왔다. 문자 배열의 경우 한 가지 예외가 있다면  ctrcpy()와 strncpy() 함수를 이용해 왔다는 것이다 memcpy()와 memmove()함수는 다른 좀류의 배열에 대해서도 편리를 제공한다. 두함 수에 대한 원현은 다음과 같다.

void * memcpy(void *restrict s1, const void *restric s2, size_t n);
void * memmove(void *s1, const oid *s2, size_t n);

두 함수 모두 s2에 의해 포인트된 위치에서 s1에 포인트된 위치로 n바이트를 복사하여 s1값으로 봔환한다. 키워드 restrict(코드 최적화)로 나타나듯이, 이 두 함수의 다른 점은 memcpy()는 두개의 메모리 범위간의 중복이 없다고 가정할 수 있다는 것이다. memmove()함수는 그런 전제를 하지 않으므로 모든 바이트가 마지막 수신지에 복사되기 전에 임시 버퍼에 먼저 복사되는 것처럼 복사가 이루어진다. 만약 중복되는 범위가 있을때 memcpy()함수를 사용한다면 어떻게 될 것인가? 이 행위는 정의 되지 않는다. 즉, 실핼할 수도 있고 안 할 수도 있다는 뜻이다. 컴파일러는 memcpy()함수를 사용하지 말아야 할 때 사용하게 되면 그것을 멈출 수 없다. 따라서, 사용시 범위가 중복되지 않도록 확인하는 것은 사용자의 책임이다. 이것이 바로 프로그래머에게는 또하나의 부담이 될것이다.

이러한 함수들은 모든 데이터형과 작동하도록 설계되어 있기 때문에, 두개의 포인터 인수는 void형 포이터이다. C는 void *의 포인터에 어떤 포인터 형식도 할당할 수 있도록 해준다. 그러나 이 폭넓은 수용은 이러한 함수들이 어떤 종류의 데이터가 복사되는지 알지 못한다는 단점이 있다. 그러므로 이런 함수들은 복사될 바이트 수를 나타내기 위해 세 번째 인수를 사용한다. 일반적으로 배열의 경우, 바이트 수는 요소의 수가 아니라는 것을 주의한다. 따라서 10 double값의 배열을 복사했다면 세 번째 인수로 10이 아닌 10*sizeof(double)를  사용해야 할 것이다.


  1 #include <stdio.h>
  2 #include <string.h>
  3 #include <stdlib.h>
  4 #define SIZE 10
  5
  6 void show_array(const int ar[], int n);
  7
  8 int main(void)
  9 {
 10    int values[SIZE] = {1,2,3,4,5,6,7,8,9,10};
 11    int target[SIZE];
 12    double curious[SIZE / 2] = {1.0, 2.0, 3.0, 4.0, 5.0};
 13    int * pi;
 14
 15    memcpy(target, values, SIZE * sizeof(int));
 16    puts("target:");
 17    show_array(target, SIZE);
 18    pi = (int *)malloc(SIZE * sizeof(int));
 19    memcpy(pi, values, SIZE * sizeof(int));
 20    puts("pi:");
 21    show_array(pi, SIZE);
 22    memmove(values + 2, values, 5);
 23    puts("values");
 24    show_array(values, SIZE);
 25    memcpy(target,curious, (SIZE /2) * sizeof(double));
 26    puts("target:");
 27    show_array(target, SIZE);
 28
 29    return 0;
 30 }
 31
 32 void show_array(const int ar[], int n)
 33 {
 34    int i;
 35    for(i = 0; i < n; i++)
 36       printf("%d ", ar[i]);
 37    putchar('\n');
 38 }
결과
target:
1 2 3 4 5 6 7 8 9 10
pi:
1 2 3 4 5 6 7 8 9 10
values
1 2 1 2 5 6 7 8 9 10
target:
0 1072693248 0 1073741824 0 1074266112 0 1074790400 0 1075052544

memcpy()에 대한 마지막 호출은 double형에서 int형 배열까지의 데이터를 복사한다. 이것은 memcpy()가 데이터형을 알지 모사며 그것에 대해 상관하지도 않는다는 것을 본여준다. 즉, 하나의 위치에서 다른 위치로 바이트를 복사할 뿐이다(예를들어, 구조체에서 문자 배열로 바이트를 복사할 수 있다.) 데이터 변환 또한 없다. 요소마다 지정하는 루프를 가지면 할당하는 동안 double형 값은 int형으로 변환될지도 모른다. 이경우에 프로그램은 바이트를 있는 그대로를 복사한 다음 그것이 int형인 것처럼 비트 패턴을 번역한다.














디버깅에는 assert()!!!

assert.h헤더파일이 지원하는 assert라이브러리는 디버깅 프로그램을 지원하는 작은 라이브러리이다. 인수는 정수식 표현을 나타낸다. assert()매크로는 표준 오류 스트림(stderr)에 오류 메시지를 작성하고, 프로그램을 종료하는 abort() 함수를 호출한다. abort()함수는 stdlib.h 헤더에 선언된다.
다음 예제를 보자
#include <stdio.h>
#include <math.h>
#include <assert.h>

int main()
{
   double x, y, z;
   puts("Enter a pair of numbers (0 0 to quit): ");
   while(scanf("%lf%lf", &x, &y) == 2 && (x != 0 || y != 0))
   {
      z = x * x - y * y;
      assert(z >= 0);
      printf("Answer is %f\n", sqrt(z));
      puts("Next pair of numbers: ");
   }
   return 0;
}

결과
Enter a pair of numbers (0 0 to quit):
4 3
Answer is 2.645751
Next pair of numbers:
5 3
Answer is 4.000000
Next pair of numbers:
3 5
assert: assert.c:12: main: Assertion `z >= 0' failed.

일반적으로 인수는 관계식이거나 논리식이다. assert()가 프로그램을 강제로 중지시키면, 먼저 실패한 테스트를 출력하고, 다음으로 테스트를 포함하는 파일명, 라인 번호를 표시나다. 관계식이 false가되면 해당 코드와 경로 및 파일명, 그리고 라인번호를 같이출력한다.
더 멋진 장점은 선언부분에  아래의 정의를 추가한다.

#define NDEBUG


위 의 선언을 정의하면 모든 asert()함수가 비활성화 된다. 당연히 디버깅이 더 필요할시에는 주석처리하고 다시 컴파일 하면 된다. 디버깅용으로 아주 좋을 것이다.




exit(), atexit()

#include <stdio.h>
#include <stdlib.h>
void sign_off(void);
void too_bad(void);
int main(void)
{
   int n;
   atexit(sign_off);
   puts("Enter an integer:");
   if(scanf("%d",&n) != 1)
   {
      puts("That's no integer!");
      atexit(too_bad);
      exit(EXIT_FAILURE);
   }
   printf("%d is %s.\n", n, (n % 2 ==  0) ? "even" : "odd");
   return 0;
}
void sign_off(void)
{
   puts("Thus terminates another magnificent program from");
   puts("SeeSaw Software!");
}
void too_bad(void)
{
   puts("SeeSaw Sftware extends its heartfelt condolences");
   puts("to you upon the failure of your program.");
}
결과
Enter an integer:
1                        // 숫자 입력시
1 is odd.
Thus terminates another magnificent program from
SeeSaw Software!

Press ENTER or type command to continue
Enter an integer:
q                        // 문자 입력시
That's no integer!
SeeSaw Sftware extends its heartfelt condolences
to you upon the failure of your program.
Thus terminates another magnificent program from
SeeSaw Software!

- atexit
: 이 함수는 exit()가 호출될때 실행되는 함수 리스트에 해당 함수를 등록한다. atexit()에 등록되는건 함수 포인터 형식이다. 함수명 자체가 주소값이므로 그냥 함수명을 인수로 사용하면 된다. ANSI에서는 32개까지의 등록을 보장한다. 스택처럼 맨처음 추가된 함수가 맨 나중에 호출이 된다. FILO(First In Last Out). 보통 프로그램 모니터링 파일을 업데이트 하거나 환경 변수를 재설정하는 등의 업무를 실행한다. exit()는 별도로 호출하지 않더라도 프로그램이 종료될때 exit()함수를 무조건 호출하므로 위와 같은경우 적어도 sign_off함수는 반드시 출력된다. 이 함수는 exit()가 호출된 후 실행된다.

- exit()
: atexit()로 지정된 함수는 exit() 이후에 실행한 후 스스로 정리한다. exit()는 모든 출력 스트림을 플러쉬(flush)하고,  개방된 모든 스트림을 닫고, 표준 I/O함수 tmpfine()의 호출로 만들어진 임시 파일을 닫는다. 그런 다음 exit()는 호스트 환경으로  제어를 반환하고, 가능하면 그 환경에 종료 상황을 보고한다. 전통적으로 UNIX프로그램은 성공적인 종료를 표시하는데 0을 사용하고 실패를 표시하는데 0이아닌 수를 사용하고 있다. exit()함수는 return문을 사용하는 것과 동일하다. 다른점은 exit()는 mail()함수외의 다른 함수에서 사용되면 종료가 된다. return은 값을 반환하겠지만 말이다..

인라인 함수

보통의 함수 호출은 오버헤드를 갖는다. 이것은 호출을 설정하고 인수를 전달하며 함수로 들어가고 반환되는 함수 프롤로그과정 때문에 시간이 걸린다는 의미이다.
C99에서는 함수를 인라인 함수로 만드는 것이 가능한 빠른 함수호출을 요청한다고 한다.

#include <stdio.h>
inline void eatline()  //인라인 함수 정의
{
   while(getchar() != '\n')
      continue;
}
int main(void)
{
   ...
   eatline();
   ...
}
인라인  선언을 보면서, 컴파일러는 eatline()함수 호출을 함수 보디로 대체할 수 있다. 다시 말해서, 다음과 같은 코드를 쓰는 것과 동일한 효과가 나타난다.
#include <stdio.h>
inline void eatline()  //인라인 함수 정의
{
   while(getchar() != '\n')
      continue;
}
int main(void)
{
   ...
    while(getchar() != '\n')  // 대체된 함수 호출
      continue;
   ...
}

인라인 함수는 이 함수르 위해 따로 설정된 개별 코드 블록을 갖지 않기 때문에, 함수의 주소를 가질수없다
(주소를 가질수 있으나 컴파일러가 비 인라인 함수를 생성할 것이다.) 또한 인라임 함수는 디버거에 나타나지 않는다.
인라인 함수는 짧아야한다. 긴 함수의 경우 함수 호출하는 시간이 함수 바디를 실행하는것 보다 짧다.
인라인 함수는 컴파일러가 함수 정의 내용을 알아야하기 때문에 동일한 파일 내에 있어야하는 내부 링키지를 가져야 한다. 다만 헤더파일에는 포함될수 있다. 사용자 정의된 헤더를 파일에 include시키면 되기 때문이다

//file1.c
inline double square(double);    // 인라인 함수선언
double square(double x){return x * x;}   // 외부 링키지 선언

//file2.c
extern double square(double);    // 외부 함수 선언
double square(double x) {it y; y = x*x; return y;}

//file3.c
extern double square(double);   // 외부 함수 선언

위와 같은 경우 file1c는 인라인버전을 사용할 수 있으나 file2.c, file3.c는 외부 함수 정의를 사용한다.

extern double square(double);    // 외부함수로 선언
inline double square(double);     // 인라인 함수로 선언
double square(double x){return x * x;}  // 외부링키지로 선언
int main()
{
   double q =  square(1.3) + square(1.5);  // 어떤 square를 써야하나??
   return 0;
}

컴파일러는 이중 하나로 자유롭게 사용을 한다. 그렇다면 이런 구문은 쓰지 않아야 마땅하다.

2010년 1월 28일 목요일

전처리문(펌)

실질적인 컴파일 이전에 미리 처리되는 문장으로 선행처리기라고도 한다.
컴파일러는 사용자가 작성한 코드를 컴파일하기에 앞서 전처리문에서 정의해 놓은 작업들을 먼저 수행한다.

종류로는 #define, #if, #ifdef, #ifndef, #defined, #undef 등이 있다.
이것은 기존에 있는 방대한 소스 코드를 지우지 않고 활성화와 비활성화하는 데에 가장 많이 이용된다.
즉, 기존에 있는 소스 코드를 건드리지 않고 부분적인 컴파일을 하는 것이다.

C의 전처리문이 오는 줄(Line)의 첫 문자는 항상 '#'으로 시작한다.

ANSI 표준에 따른 C의 전처리문의 종류
- 파일 처리를 위한 전처리문 : #include
- 형태 정의를 위한 전처리문 : #define, #undef
- 조건 처리를 위한 전처리문 : #if, #ifdef, #ifndef, #else, #elif, #endif
- 에러 처리를 위한 전처리문 : #error
- 디버깅을 위한 전처리문 : #line
- 컴파일 옵션 처리를 위한 전처리문 : #pragma

조건 처리를 위한 전처리문은 어떤 조건에 대한 검사를 하고 그 결과를 참(0 이 아닌 값) 또는 거짓(0)으로 돌려준다.
#if : ...이 참이라면
#ifdef : ...이 정의되어 있다면
#else : #if나 #ifdef에 대응된다.
#elif : 'else + if'의 의미
#endif : #if, #ifdef, #infdef이 끝났음을 알린다.

#include
헤더 파일과 같은 외부 파일을 읽어서 포함시키고자 할 때 사용된다. 이때의 파일은 이진 파일(Binary file)이 아닌 C의 소스 파일과 같은 형태의 일반 문서 파일을 말한다:
#include <stdio.h>        /* 이 위치에 stdio.h라는 파일을 포함시킨다. */
#include "text.h"           /* 이 위치에 text.h라는 파일을 포함시킨다. */

<...> 을 사용할 때와 ...을 사용할 때의 차이점은 <...>은 컴파일러의 표준 포함 파일 디렉토리(또는 사용자가 별도로 지정해 준)에서 파일을 찾는 것을 기본으로 한다. 그리고 ...을 사용했을 때는 현재의 디렉토리를 기본으로 파일을 찾게 된다. 아예 디렉토리를 같이 지정할 수도 있다:
#include <C:\MYDIR\MYHEAD.H>
#include "C:\MYDIR\MYHEAD.H"

#define
상수 값을 지정하기 위한 예약어로 구문의 상수로 치환한다. 또한 #define은 함수 역활과 비슷하게 아래와 같이 쓰일 수 있다:
#define SUM(x) ((x) = (x) + (x))

#define으로 정의할 수 있는 것은 숫자만이 아니다:
#define MYNAME "Young Hee"

이렇게 #define으로 정의된 것은 일반적인 변수와는 다르다. 그 차이는 명백하다:
#define MYNAME "Turbo"
char my_name[] = "Turbo"

MYNAME은 전처리문으로 my_name은 문자형 배열 변수로 정의되었다:
printf(MYNAME);
printf(MYNAME);
printf(my_name);
printf(my_name);

이것을 전처리한 상태는 다음과 같이 될 것이다:
printf("Turbo");
printf("Turbo");
printf(my_name);
printf(my_name);

이 런 결과에서 우리가 유추해 볼 수 있는 것은 전처리 명령을 사용했을 경우 "Turbo"라는 동일한 동작에 대해서 두개의 똑같은 문자열이 사용됐고, 변수를 사용했을 경우에는 하나의 문자열을 가지고 두번을 사용하고 있다는 것이다. 결과적으로 이런 경우에는 전처리문을 사용했을 경우 메모리 낭비를 가져 온다는 것을 알 수 있다.

#undef
#define으로 이미 정의된 매크로를 무효화한다:
#define ADD(a, b) (a + b)
#undef ADD(a, b)

앞으로 사용되는 ADD(...)는 undefined symbol이 되어 에러 처리된다.

#if ~ #endif
#if 구문은 if랑 아주 비슷하다. 이것은 어떠한 구문을 컴파일 할지 안할지를 지정할 수 있다:
#define A 1
#if A
source code ...
#endif

위 source code 부분은 컴파일이 된다. if문에서와 같이 참, 거짓을 구분하여 컴파일이 된다. 위에서 A값은 1 즉 0보다 큰 수이기 때문에 참인 것이다. 직접 아래와 같이 하면 거짓이기 때문에 source code 부분은 컴파일이 되지 않는다:
#if 0
source code ...
#endif

#ifdef ~ #endif
컴파일 할 때
#define MYDEF                /* MYDEF는 값은 가지지 않았지만 어쨋든 정의는 되었다 */

#ifdef YOURDEF              /* 만약 YOURDEF가 정의되어 있다면... */
#define BASE 10             /* BASE == 10 */
#elif MYDEF                    /* 그외에 MYDEF가 정의되었다면... */
#define BASE 2               /* BASE == 2 */
#endif

BASE는 상수 2로 치환되어 전처리기가 컴파일러에게 넘겨준다.

#ifndef 헤더명_H__ ~ #endif
헤 더 파일이 겹치는 것을 막기 위한 일종의 매크로이다. 예를 들어, 헤더 파일에 어떤 클래스의 인터페이스 선언을 넣었다고 하자. 이 클래스 인터페이스에서 다른 파일의 프로토타입이 필요해서 다른 A 파일을 include 하고 있는데 이 헤더 파일을 include 하는 파일에서 A라는 헤더 파일을 이미 include 하고 있다면 두번 define한 것이 된다. 그러면 SYNTEX 에러가 난다. 그래서 그런 것을 막는 방법의 하나로 #ifndef을 사용한다. 이전에 include되어 있으면 #endif 쪽으로 점프해 버려 결국 한번 선언되는 것이다:
#include  <stdio.h>    ------ (a)
#include  <stdio.h>    ------ (b)

이렇게 두번 썼다고 하자. 그런데 앞에 이미 include를 했는데 밑에 또 한다면 문제가 된다. 컴파일러가 검사해야할 코드량도 많아진다. 그래서 stdio.h에는
#ifndef STDIO_H__
#define STDIO_H__
가 선언되어 있다. 만약 STDIO_H가 선언되어 있지 않다면 선언한다는 뜻이다. 그 뒤 (b)에서는 이미 (a)쪽에서 STDIO_H__ 을 선언한 상태이기 때문에 전처리기 쪽에서 무시해버린다. 그러므로 컴파일러는 (a)만 검사한다.

#defined
define이 여러 개 되어 있는지를 검사할 때 쓴다. 이것은 여러 개를 동시에 검사 할 수 있다:
#if (defined A) || (defined B)

#ifdef와 #if defined의 차이
#ifdef은 정의가 되어 있는지를 테스트 하기 때문에, 한번에 여러 개를 사용할 수 없다:
#ifdef name

여러 개가 정의되어 있는지를 테스트 하기 위해서 #if defined를 사용할 수 있다:
#if defined(MACRO1) || defined(MACRO2)

#if는 ||로 중첩해서 사용할 수 있다. 형식이, #if expression이므로 C 표현이 올 수 있다:
#if (MACRO1) || (MACRO2)

#error
소스 라인에 직접 에러 메세지를 출력한다. 전처리기가 #error 문을 만나면 그 즉시 컴파일을 중단하고 다음과 같은 에러 메시지를 출력한다:
ERROR : XXXXX.c ########: Error directive: 내용
- XXXXX.c --> 현재 컴파일 중인 파일 명
- ####### --> 전처리기가 #error 문을 만난 시점에서의 행 번호(헤더 포함)

#ifdef __LARGE__
#error This program must be compiled in LARGE memory model!
#endif

이 내용은 만일 프로그램이 LARGE 모델이 아니라면 "#error" 뒤에 표시된 메세지를 출력하고 컴파일을 중지하게 된다.

#line
이 명령은 소스 코드의 행 번호를 지정하기 위한 것으로 주로 컴파일러에 의해 미리 정의된 __LINE__과 함께 사용된다.
__LINE__과 __FILE__을 각각 행 번호와 파일 명으로 변경한다:
#include <stdio.h>
#define DEBUG

void main(void)
{
        int count = 100;

        #line 100               /* 다음 줄번호를 100으로 설정한다 */
                                   /* <-- 이 줄의 번호가 100이다 */
        #ifdef DEBUG        /* <-- 이 줄의 번호가 101이다 */
        printf("line:%d, count = %d\n", __LINE__, count);
        #endif

        count = count * count - 56;
        #ifdef DEBUG
        printf("line:%d, count = %d\n", __LINE__, count);
        #endif
        count = count / 2 + 48;
        #ifdef DEBUG
        printf("line:%d, count = %d\n", __LINE__, count);
        #endif
}

#pragma
컴 파일 옵션의 지정. 컴파일러 작성자에 의해서 정의된 다양한 명령을 컴파일러에게 제공하기 위해 사용되는 지시어이다. 컴파일러의 여러 가지 옵션을 명령행에서가 아닌 코드에서 직접 설정한다. #pragma는 함수의 바로 앞에 오며 그 함수에만 영향을 준다.
Turbo C는 9개의 #pragma 문(warn, inline, saveregs, exit, argsused, hdrfile, hdrstop, option, startup)을 지원하고 있다:

#pragma inline
컴파일할 때 어셈블러를 통해서 하도록 지시한다. 즉, 인라인 어셈블리 코드가 프로그램에 있음을 알려준다(명령행 상에서 '-B' 옵션).

#pragma saveregs
이 홉션은 휴즈 메모리 모델에 대해 컴파일된 함수에게 모든 레지스터를 저장하도록 한다.

#pragma warn
이 지시어는 Turbo C에게 경고 메시지 옵션을 무시하도록 한다.

#pragma warn -par
이는 매개 변수(parAMETER)가 사용되지 않았다고 경고(warnING)를 내지 못하도록 한다. 이와 반대되는 표현은
#pragma warn +par

경 고의 내용 앞에 (+)를 사용하면 경고를 낼 수 있도록 설정하고 (-)를 사용하면 경고를 내지 못하도록 하는 것은 모든 경고에 대해 동일하다. 명령 행에서는 "-wxxx"로 경고를 설정하고 "-w-xxx"로 경고를 해제한다. 경고의 종류는 무척 많은데 자주 사용되는 것을 아래에 나타냈다. 모든 것을 알고 싶다면 컴파일러 User's Guide의 명령행 컴파일러 부분을 참고하기 바란다:
par : 전해진 파라미터가 사용되지 않음
rvl : void 형이 아닌 함수에서 리턴 값이 없음
aus : 변수에 값을 할당했으나 사용하지 않았음
voi : void 형 함수에서 리턴 값이 사용되었음
sig : 부호 비트(MSB)가 고려되지 않은 형 변환(type-castion)에서 부호 비트를 소실할 수 있음

Standard C pre-defined symbols

__FILE__ a string that holds the path/name of the compiled file
__LINE__ an integer that holds the number of the current line number
__DATE__ a string(Mmm dd yyyy) that holds the current system date
__TIME__ a string(hh:mm:ss) that holds the current system time
__STDC__ defined as the value '1' if the compiler conforms with the ANSI C standard
__cplusplus determines if your compiler is in C or C++ mode. Usually used in headers

#include <stdio.h>

void main(void)
{  
        printf("The path/name of this file is %s\n", __FILE__);  
        printf("The current line is %d\n", __LINE__);  
        printf("The current system date is %s\n", __DATE__);  
        printf("The current system time is %s\n", __TIME__);  

        #ifdef __STDC__  
        printf("The compiler conforms with the ANSI C standard\n");  
        #else  
        printf("The compiler doesn't conform with the ANSI C standard\n");  
        #endif  

        #ifdef __cplusplus  
        printf("The compiler is working with C++\n");  
        #else  
        printf("The compiler is working with C\n");  
        #endif 


프 로그래머들 마다 코딩 스타일(암시적 약속)이 있다. 보통 매크로, const 변수는 대문자로 적는 것이 원칙이다. 매크로 함수와 일반 함수, 매크로 대상체(object-like macro)와 일반 변수를 구분하기 쉽게 해주는 것이기 때문이다.

#define STDIO_H_
왜 뒤에 _를 붙였을까? 이것도 하나의 암시적 약속이다. 컴파일러 제작 회사는 매크로를 정의할 때 사용자들과 이름이 충돌이 나지 않게 하기 위해서 대부분 _를 뒤어 덧붙인다. 또한 _를 하나 혹은 두 개 연속으로 시작하는 것은 컴파일러 내부에서 사용하는 매크로라는 성격이 강하다. 물론 강제적인 뜻은 없으며 단지 관습상 그렇다. 왜 이것이 관습이 되었나 하면 보통 매크로 변수 이름이나 함수 이름을 지을 때 뒤에 _를 붙이지 않기 때문이다. 그래서 함수 제작자들이 _를 단골로 붙였다.

매크로 상수

- 매크로 정의
: 매크로는 절대!! 연산이 아닌 단순한 문자열 치환일 뿐이다!!!
: #define PX printf("x is %d.\n",x) //공백으로 구분됨.

* #define : 선행처리 지시문
* PX         : 매크로
*
printf("x is %d.\n",x) : 보디

- 상수의 토큰

: 상수 정의시 토큰사이에 여러공백들이 있을시 선행처리기는 하나의 토큰으로 바꿔버린다. 문자열로 지정된 상수는 모든 공백도 값이로 취급한다.

#define FOUR 2*2   // 2*2는 사이 공백이 없이 붙어있는 하나의 토큰인 경우.
#define SIX 2 * 3    // 2 * 3은 사이 공백으로 구분된 토큰이 3개인 경우.

- 상수 재정의
: 새로운 정의가 기존의 정의와 동일한 순서와 토큰을 가져야한다.

#define SIX 2 * 3
#define SIX 4        *         5

위 와 같은 경우는 상수 재정의가 가능하다.
그러나 다음의 경우는 해당되지 않는다.

#define SIX 4*5  // 토큰이 하나이고 위는 토큰이 3개이므로 재정의불가.

- #define에서의 인수 사용
: 함수같은 매크로 생성 가능
#define MEAN(X,Y) (((x)+(Y))/2)

* MEAN() : 매크로
* X,Y       : 매크로 인수
*
(((x)+(Y))/2) : 대체 보디

#define SQUARE (X) X * X  // 다음과 같이 호출이 가능하다.
z = SQUARE(2);                //인수로 넘겨진 값의 제곱을 구하는 함수 매크로

ex) 매크로는 문자열을 치환한다.
#define SQUARE(x) x*x
int x = 4;
int z;
z= SQUARE(x + 2) // 이경우 6이란 인수가 넘어가는게 아니다.
                          // 인수로 x+2가 넘어가서 결과적으로 x+2*x+2가 계산되어
                               14라는 값을 리턴하게 된다.
일반 함수는 실행되는 동안 처리 되고 매크로 호출은 컴파일 하기 전에 인수 토큰을 프로그램으로 보낸다. 위 의 경우 36이란 값을 얻을려면
#define SQUARE(x) (x)*(x)로 하면 된다. 그러면 (x+2)*(x+2)가 되므로 괄호연산자에 의해 원하던 결과값을 얻을 수 있을 것이다.

또 다른 경우
100/SQUARE(2)라고 되어있으면 치환된값은
100/2*2가 된다. /와 *는 연산자 우선순위가 같으므로 왼쪽에서 오른쪽으로 계산이 될것이다. (100/2)*2가 되는 셈이다. 이경우 100/(2*2)가 되어야 원하는 결과를 얻을 수 있을것이다.
#define SQUARE(x) (x*x)와 같이 선언이 되어 있어야 한다.
위의 두 경우를 모두 커버 하기 위해서는
#define SQUARE(x) ((x)*(x)) 라고 선언해야 된다.

또 SQUARE(++x)라고 함수 호출이 되면
++x*++x  == 5*6  (연산자 우선순위가 높은 ++연산 부터 수행 되어 * 앞에서한번 * 뒤에서 한번하여 4라는 값이 위와같이 변경되는 경우이다.

결론적으로 #define 매크로 함수 호출을 사용하여야 하는경우 증감연산자는 인수로 사용하지 않는것이 좋다.

- 매크로 인수에서 문자열 생성하기 : #연산자
#include <stdio.h>
#define PSQR(x) printf("The square of " #x " is %d.\n", ((x)*(x)));
int main(void)
{
   int y = 5;
   PSQR(y);
   PSQR(2+4);
   return 0;
}

위 의 결과값은
The sqare of y is 25.
The sqare of 2 + 4 is 36.
즉, 넘겨지는 파라메터가 수치가 아닌 문자값 그대로 복사가 된다.
여기서 #x는 정해져 있는 값이 아니라 매크로 함수의 인자 파라메터와 같으면 된다.

- 선행처리기 결합 : ## 연산자
#include <stdio.h>
#defie XNAME(n) x ## n
#define PRINT_XN(n) printf("X" #n " = %d\n" x ## n);
int main(void)
{
   int XNAME(1) = 14;  // int x1 = 14
   int XNAME(2) = 20;  // int x2 = 20
   PRINT_XN(1);          // printf("x1 = %d\n", x1)
   PRINT_XN(2);          // printf("x2 = %d\n", x2)
   return 0;
}
// 실행 결과 값은 아래와 같다.
x1 = 14
x2 = 20
결과에서 보다시피 문자열을 더하고 그 결과값이 마치 변수를 생성하는것 같다.

- Variadic 매크로 : ...과 __VA_ARGS__
#include <stdio.h>
#include <math.h>
#define PR(X, ...) printf("Message " #X ":"  __VA_ARGS__)
int main(void)
{
   double x = 48.0;
   double y;

   y = sqrt(48.0);  // x변수가 할당되니 컴파일러 오류가 났다. 구문문제는 없는뎅 ㅡㅡa
   PR(1, "x = %g\n", x);
   PR(2, "x = %.2f, y = %.4f\n", x, y);
   return 0;
}
// 결과값
Message 1:x = 48
Message 2:x = 48.00, y = 6.9282

인수의 갯수는 ... 이라는 생략부호가 선언 되어 있다. 인수갯수는 무관하다.
PR(1, ... ) 에서 x는 1의 값을 가지며 따라서 #1은 "1"이 된다. 이숫자는 파라메터의 갯수를 지정하는게 아니므로 어떤 문자가 와도 상관없다. (테스트 다 해봤거덩 ^^;;)

- 매크로 쓸때 조심해라
: 매크로는 문자열 치환이므로 함수와 같은 연산이 일어나지 않으므로 항상 쓸때는 조심해야한다. 어떤 컴파일러는 하나의 라인으로 매크로 정의를 제한하기도 한다. 그렇지 않더라도 하나의 라인을 준수하는게 최선의 방법일 것이다.

매크로대 함수의 선택은 시간과 공간의 맞교환을 나타낸다. 매크로는 인라인 코드를 생성하는데 이건 프로그램 내에서 문장을 갖는다는 의미이다. 매크로를 20번 사용했다면 20개의 코드라인을 갖게 된다. 함ㅅ를 20번 사용하면 함수는 한번만 복사를 하므로 공간을 덜 차지하게 된다.
반면 프로그램 제어는 함수가 있는곳으로 이동해서 호출하고 반환을 한다. 함수는 인라인 코드에 비해 시간이 오래 걸린다. 매크로는 인수 타입에 신경쓰지 않아도 되서 함수보다 쉽게 사용할 수 있는 점이다.

* 매크로 사용시 주의해야 할 사항
1. 매크로 이름에는 공백이 없으나 대체 문자열에서는 나타난다는것을 기억해야한다.
2. 각각의 인수 정의에 전체적으로 괄호를 사용해라. 원하지 않는 결과값이 나올 수 있다.
3. 매크로 함수 이름은 대문자를 사용한다. 가능성있는 부작용을 방지하도록 도와준다.
4. 일반함수와의 속도면에서의 차이가 어느정도 있는지 파악하고 쓰도록해라.
     중첩된 루프의 내부에서의 매크로 함수 사용은 속도를 훨씬 빠르게 향상시킨다.
     많은 시스템은 프로파일러를 제공하여 어느 부분에서 시간이 소비되는지 파악하여 해당
     코드의 속도를 빨리 처리하도록 도와준다.

비트필드 구조체(펌)

구조체 멤버를 비트단위로 선언할 수 있는 구조체 입니다.

struct st
{
   unsigned int a : 2;
   unsigned int b : 3;
};

이런 식으로 표현합니다. 즉 일반 구조체의 멤버 산언과 동일하나 멤버명뒤에 : n 으로 비트수를 지정합니다.
예제? 위에 선언한 구조체를 이용하겠습니다.

struct st x = {1,5};

이렇게 하면 x.a는 1, x.b는 5 가 들어갑니다.
즉, a는 2비트인데 1이 들어가므로 2진수로 보면
01 이 들어가게 됩니다.
b는 3비트인데 5가 들어 가므로 2진수로 보면
101 이 들어가게 됩니다
즉, 일반 구조체 사용하듯이 사용하면 되는 겁니다.
사용하는 경우? 뭐 다양하게 사용되겠지만
flag선언때 많이 사용합니다. (이걸 어떻게 설명드리지요?)
또한 어떤 데이터 스트림(헤더정도 등)의 parsing에도 많이 쓰입니다.
예를들어 총 4바이트 헤더에서 처음 3비트는 어떤 정보이고 다음 8비트는 뭐고
그다음 몇비트는 뭐고 하는 식으로 비트들로 필드가 구분되어 있다면
이들 필드들만 억세스 하기 위하여 비트필드를 선언하여 사용합니다.

장점? 아무래도 변수를 비트로 사용하니까 메모리가 줄어들겠죠
예를들어 int a = YES; a = NO; (여기서 YES, NO는 1과 0으로 사전 define된것으로 가정)
이렇게 a가 사용된다면 a는 4바이트 변수인데 실제 저장하는 값은 0 아니면 1 혹은
YES, NO 등 입니다. 따라서 이런 경우는 1비트면 충분하지요
만약 이런식의 변수들이 여러개 있다면 이들을 묶어서 하나의 구조체로 만들고
그 내부 필드를 비트필드로 하는 거지요 그럼 메모리 총량이 감소되어 절약이 가능합니다.

단점? 전체 32비트 메모리(비트필드 이지만 구조체의 최소크기는 int가 됨)에서
특정 비트들만 값을 변경하면 즉 위의 구조체에서 x.a = 1을 하면 실제 이 코드는
1을 그냥 a에 대입하는 것이 아니라 실제 코드는 (x & 3  | 1) 로 처리되어 비트 연산을
수행하게 됩니다. 결국 메모리는 아끼지만 속도는 느려지는 거지요

메모리는 먼저 선언된 멤버가 낮은비트(0번지크)부터 채워집니다.
위의 구조체는 32비트(int)로 총 사이즈가 잡힙니다.

그 32비트에서 하위 두비트 0,1 비트는 a가 그 위의 3비트 3,4,5 비트는 b가 위치합니다.
나머지 비트들은 사용되지 않습니다.

이해 되실런지요?

함수 포인터를 활용한 메뉴처리(소스)

#include <stdio.h>
#include <string.h>
#include <ctype.h>
char showmenu(void);
void eatline(void);
void show(void (*fp)(char *), char * str);
void ToUpper(char *);
void ToLower(char *);
void Transpose(char *);
void Dummy(char *);

int main(void)
{
   char line[81];
   char copy[81];
   char choice;언
   void(*pfun)(char *);   // 함수 포인터 선언
   puts("Enter a string (empty line to quit):");
   while (gets(line) != NULL && line[0] != '\0')
   {
      while((choice = showmenu()) != 'n')
      {
         switch(choice)
         {
            case 'u' : pfun = ToUpper; break;
            case 'l' : pfun = ToLower; break;
            case 't' : pfun = Transpose; break;
            case 'o' : pfun = Dummy; break;
         }
         strcpy(copy, line);
         show(pfun, copy);
      }
      puts("Enter a string (empty line to quit):");
   }
   puts("Bye!");
   return 0;
}
char showmenu(void)
{
   char ans;
   puts("Enter menu choice:");
   puts("u) uppercase        l) lowercase");
   puts("t) transposed case  o) original case");
   puts("n) next string");
   ans = getchar();
   ans - tolower(ans);
   eatline();
   while(strchr("ulton", ans) == NULL)
   {
      puts("Please enter a u, l, t, o, or n:");
      ans = tolower(getchar());
      eatline();
   }
   return ans;
}

void eatline(void)
{
   while(getchar() != '\n')
      continue;
}

void ToUpper(char *str)
{
   while(*str)
   {
      *str = toupper(*str);
      str++;
   }
}

void ToLower(char *str)
{
   while(*str)
   {
      *str = tolower(*str);
      str++;
   }
}
void Transpose(char *str)
{
   while(*str)
   {
      if(islower(*str))
         *str = toupper(*str);
      else if(isupper(*str))
         *str = tolower(*str);
      str++;
   }
}

void Dummy(char * str)
{
}

void show(void (*fp)(char *), char * str)
{
   (*fp)(str);
   puts(str);
}

구조체 데이터 파일 저장 (소스)

#include <stdio.h>
#include <stdlib.h>
#define MAXTITL 40
#define MAXAUTL 40
#define MAXBKS 10
struct book{
   char title[MAXTITL];
   char author[MAXAUTL];
   float value;
};
int main(void)
{
   struct book libry[MAXBKS];
   int count = 0;
   int index, filecount;
   FILE * pbooks;
   int size = sizeof(struct book);

   if((pbooks = fopen("book.dat", "a+")) == NULL)
   {
      fputs("Can't open book.dat file\n", stderr);
      exit(1);
   }
   rewind(pbooks);
   while(count < MAXBKS && fread(&libry[count], size, 1, pbooks) == 1)
   {
      if(count == 0)
         puts("Current contents of book.dat:");
      printf("%s by %s: $%.2f\n", libry[count].title,libry[count].author, libry[count].value);
      count++;
   }
   filecount = count;
   if(count == MAXBKS)
   {
      fputs("The book.dat file is full.", stderr);
      exit(2);
   }
   puts("Please add new book titles.");
   puts("Press [enter] at the start of a line to stop.");
   while(count < MAXBKS && gets(libry[count].title) != NULL && libry[count].title[0] != '\0')
   {
      puts("Now enter the author.");
      gets(libry[count].author);
      puts("Now enter the value.");
      scanf("%f", &libry[count++].value);
      while(getchar() != '\n')
         continue;
      if(count < MAXBKS)
         puts("Enter the next title.");
   }
   puts("Here is the list of your books:");
   for(index = 0; index < count; index++)
      printf("%s by %s: $%.2f\n", libry[index].title, libry[index].author, libry[index].value);
   fwrite(&libry[filecount],size, count - filecount, pbooks);
   fclose(pbooks);
   return 0;
}

함수 재귀호출 (binary변환 소스 예제)

#include <stdio.h>
void to_binary(int n);

int main(void)
{
   int number;
   printf("Enter an integer (q to quit):\n");
   while(scanf("%d", &number) == 1)
   {
      printf("Binary equivalent: ");
      to_binary(number);
      putchar('\n');
      printf("Enter an integer (q to quit):\n");
   }
   printf("Done.\n");
   return 0;
}

void to_binary(int n)
{
   int r;
   r = n % 2;
   if(n >= 2)
      to_binary(n / 2);  //재귀호출
   putchar('0' + r);
   return;
}

비트결과값 문자 변환(binary변환 소스 예제)

dd

#include <stdio.h>

char * itobs(int, char *);

int main(void)
{
   char bin_str[8 * sizeof(int) + 1 ];
   int number;
   puts("Enter integers and see them in binary.");
   puts("Non-numeric input terminates program.");
   while(scanf("%d", &number) == 1)
      printf("%d is %s\n", number, itobs(number, bin_str));
   return 0;
}

char *itobs(int n, char * ps)
{
   int i;
   static int size = 8 * sizeof(int);
   for(i = size - 1; i >= 0; i--, n >>= 1)
      ps[i] = (01 & n) + '0';
   ps[size] = '\0';
   return ps;
}

위 소스에서 밑줄 쳐 있는 부분이 비트결과값에 '0'를 더함으로 해서 결과값이 정수가 아닌 ASCII값으로 변환시키는 부분이다.

비트연산

- 1의 보수와 비트단위 부정 : ~
   : 단항 연산자 ~는 다음 예제처럼 1을 0으로 0을 1로 바꾼다. 원래 값은 변하지 않는다.

~(10011010) // 표현식
  (01100101) // 결과값

- 비트단위 AND: &
   : 이항 연산자이고 두 개의 피연산자간의 비트끼리 비교한다. 같은 위치의 두 수다 1이어야지만 결과값이 1이고 둘중 하나 또는 둘다 0일때는 결과값은 0이된다.

   (10010011)
& (00111101) // 표현식
-------------------------
   (00010001) // 결과값

val &= 0377;  // 결과값을 할당한다.

- 비트 단위 OR : |
   : 이항 연산자이고 두 개의 수중 하나라도 1이면 결과값은 1이다. 즉, 둘다 0 이 아닌 이상 결과값은 1이 되는것이다.

  (10010011)
| (00111101) // 표현식
-------------------------
  (10111111) // 결과값

val |= 0377; // 결과값을 할당한다.

- 비트단위 Exclusive OR: ^
   : 이항 연산자이고 두 개의 수가 서로 달라야지만 1이 된다. 1,1 또는 0,0은 서로 같으므로 결과값은 0이 되고 1,0 또는 0,1 은 값이 서로 다르므로 결과값은 1이 된다.

  (10010011)
^ (00111101) // 표현식
-------------------------
  (10101110) // 결과값

val ^= 0377; //결과값을 할당한다.

마스크
: 어떤 값에서 특정값에 대한 결과값을 확인할  수 있다. 마스크 값과 플래그값을 &연산수행.

  MASK : (00000011)
& FLAG : (10010110)
-------------------------
              (00000010)

위와 같은 경우 MASK 값의 나머지 값들을 0으로 초기화 한다. &연산시 0과 결합하는 비트는 모두 0이 되기 때문이다. 두번째 비트가 같다는걸 확인할 수 있다.

- 비트 on
: 나머지 비트는 변경하지 않고 특정값만 on(1)시킬때 사용 된다. 해당 값에 | 연산을 수행한다. 그때 비트 on 하고자 하는 값은 1로 설정한다. or연산시 둘중 하나만 1이여도 결과값은 1이 되기 때문이다. 결과적으로 특정 비트만 on시킬 수 있게 된다.

- 비트 off
: 비트 on과 반대로 나머지 비트는 건드리지 않고 특정비트만 off(0)시킬때 사용된다.
예제를 보자

FLAGS = FLAGS & ~MASK   ==  FLAGS &= ~MASK

~MASK의 경우 비트 1은 0으로 비트 0은 1로 바뀐다 . 그 값에서 &연산을 사용해서 처리한다

MASK : ~(00000010)  // 우선 비트 off해야할 값을 xor한다.
-------------------------
              (11111101) // xor한 결과값이다.

위의 결과값을 FLAG값과 &연산을 수행 한다.

  MASK : (11111101) // xor한 결과값
& FLAG : (00001111)  //
-------------------------
              (00001101) // 결과값

처음 (00001111) 이던 값이 변경하고자 했던 두번째 비트 값을 0으로 바꿔버렸다.
결론적으로 특정비트를 off(0)시킨 결과가 나오게 된다. :)

- 비트 토글링
: on(1)을 off(0)로 off(0)를 on(1)으로 토글할 수 있는 것이다.
번뜩 스쳐지나가는게 있을 것이다. 단항연산자인 ~연산자. 이는 모든 비트를 토글 시켜버린다. 지금 하고자하는건 특정 비트만 토글 시키고자 하는 것이므로 ~는 제외한다.
XOR 연산을 하면 되는 것이다.

00001111 ^ 00000010 하면 결과는 00001101로 두번째 비트만 off(0)시키는 결과가 나온다. ;)

비트값 점검
: 말그대로 비트가 제대로 변경되었는지 점검하고자 하는것이다.

if(flag == MASK)
   puts("Oops!");   //특정비트가 같더라도 다른비트 때문에 정확한 결과가 나오지 않는다.

if((flag & MASK) == MASK)
   puts("Wow!");

위와 같이 해 주면 되는것이다. :)




구조체 멤버 동적할당(C99)

C99는 탄력적인 배열 멤버라고 불리는 새로운 기능을 가지고 있다. 탄력적인 배열 멤버 기능을 사용해서 프로그래머는 마지막 멤버가 특별한 속성을 가지고 있는 배열인 구조체를 선언할 수 있다. 하나의 특별한 속성은 최소한, 즉각적이지 않은 배열이 존재하지 않는다는 것이다.
두번째 특별한 속석은 정확한 코드를 사용할 경우, 개발자는 탄력적인 배열 멤버가 존재하고 필요한 요소의 ㅅ를 가지고 있는 것처럼 멤버를 사용할 수 있다. 이는 다소 이상하게 들릴수도 있을 것이므로 탄력적인 배열 멤버를 가지고 있는 구조체를 생성하고 만들어보자.

다음은 탄력적인 배열 멤버를 선언하는 규칙이다.

- 탄력적인 배열 멤버는 배열의 마지막 멤버여야 한다.
- 적어도 하나 이상의 다른 멤버가 있어야 한다.
- 탄력적인 배열은 []가 비어있다는 것을 제외하면 일반 배열처럼 선언된다.

※ 예전 C9x를 지원하던 컴파일러들은 지원이 되지 않는다. C99를 지원하는 컴파일러에서 가능하다. "struct hack"라는 90년대 초반에 나타났던 해킹 기법은 배열을 '1'로 초기화 하고 있었다. 그리고 동적 배열 길이를 사용 자로 부터 입력을 받아 처리하던 부분은 문제가 있다.

다음 예제를 보자.

struct flex
{
   int count;
   double average;
   double scores[];    // 이게 바로 탄력적인 배열 멤버
};

struct flex 형식의 변수를 선언한다면, scores에 대해 메모리 공간이 할당되어 있지 않기 때문에 scores를 어떤 것에도 사용할 수 없다. 사실, struct flex형의 변수를 선언하는 것을 의도한 적도 없다. 대신, struct flex형에 대한 포인터를 선언하고 난 후, malloc()을 사용해서 struct flex의 보텅 내용에 충분한 저장 공간을 할당하고 탄력적인 배열 멤버에 원하는 공간을 추가로 할당한다. 예를 들어, scores가 다섯개의 double값이 있는 배열을 나타내 주길 원한다고 가정해 보자. 이경우  다음과 같이 코드를 작성한다.

struct flex * pf;    //포인터를 선언한다.
pf = malloc(sizeof(struct flex) + 5 * sizeof(double));


이제 count, average와 다섯 개의 double값을 저장할 수 있는 충분한 양의 메모리를 갖게 되었다. pf포인터를 사용해서 멤버에 접근할 수 있다.

pf->count = 5;
pf->scores[2] = 19.

멋지군!! ok 다 했으면 반드시 free(pf)를 해줘야 한다는걸 잊지 말길 바란다.

2010년 1월 27일 수요일

vim 명령어

* 16 진수 보기 : %!xxd  ,    원래 대로 : %!xxd -r

2010년 1월 21일 목요일

파일, 스트림, 키보드 입력

파일은 정보가 저장되는 메모리의 일부분이다. 일반적으로 파일은 플로피 디스크, 하드 디스크 또는 자기 디스크와 같은 영구 메모리에 보관된다. 컴퓨터 시스템에서 파일의 존재가 얼마나 중요한지는 이미알고 있을 것이다. 예를 들어, C로 작성한 프로그램과 그 프로그램을 컴파일 시켜 주는 프로그램은 모두 파일로 보관된다. 따라서 몇몇 프로그램들은 파일에 접근할 수 있어야 한다. 예를 들어, echo.c라는 파일에 저장된 프로그램을 컴파일하는 경우 컴파일러는 echo.c파일을 열고 그 안에 있는 내용을 읽는다. 그리고 컴파일이 끝나면 해당 파일을 다시 닫고 워드 프로세서와 같은 프로그램은 파일 열기, 읽기 및 닫기 뿐만 아니라 파일 쓰기도 한다.

강력하면서도 융통성이 뛰어난 C는 파일 열기, 읽기, 쓰기 및 닫기를 위한 라이브러리 함수들을 많이 제공하고 있다. 일차적으로 C는 해당 시스템에서 제공하는 기본 파일 도구들을 이용해 파일을 처리한다. 이것을 저급 입츨력이라고 한다. 하지만 시스템마다 차이가 많아 모든 시스템에 적용되는 저급 입출력에 대한 표준을 정하기가 어렵기 때문에 ANSI C는 이것을 시도 하지 않는다. 또한 C는 표준 입출력 패키지라는 고급 파일 처리함수들을 제공한다. 이를 위해 파일 처리를 위한 표준 모델과 표준 입출력 함수셋이 제공된다. 고급 입출력에서는 특정 C 시스템 구현이 이러한 시스템 간의 차이점을 처리해 주기 때문에 프로그래머는 공통된 인터페이스만 처리하면된다.

 

그렇다면 시스템 간의 차이점이란 무엇을 말하는 것일까? 우선, 각 시스템은 서로 다른 파일 저장 방식을 가지고 있다. 어떤 시스템에서는 파일 내용과 파일 정보를 서로 다른 장소에 보관시키는가 하면, 또 어떤시스템에서는 단일 파일 안에 이를 모두 보관한다. 텍스트를 다루는 방법에 있어서도 한 라인의 끝을 표시하기 위해 개행 문자만을 사용하는 시스템이 있는 반면 캐리지 리턴 문자와 라인피드 문자를 혼합해서 사용하는 시스템도 있다. 파일 크기의 측정도 바이트 단위, 블록 단위 등 그 방법이 다양하다

 

표준 입출력 패키지를 사용하면 이러한 차이점에 영향을 받지 않는다. 따라서 개행 문자를 검사하기 위해 if(ch = '\n')을 사용할 수 있다. 만약 해당 시스템이 캐리지 리턴/라인피드 조합을 사용하는 경우 입출력 함수는 해당 시스템에 맞게 자동 변환을 해준다.

 

개념적으로 볼때 C프로그램은 하나의 파일을 직접 다 처리하는 것이 아니라 스트림 단위로 처리한다. 하나의 스트림은 데이터의 실제 매핑 시의 이상적인 데이터의 이상적인 양을 말한다. 따라서 특성이 서로 다른 다양한 종류의 입력이 있어도 단일한 특성을 가진 스트림으로 대표될 수 있다. 결국 파일을 여는 고정은 하나의 파일에 하나의 스트림을 연상시키는 과정이 된다. 즉, 파일의 읽기와 쓰기는 스트림을 통해 발생한다.

 

C의 입출력 장치 처리 방법은 저장 장치의 일반적인 파일 처리 방법과 같다고 생각하면된다. 특별히, 키보드나 모니터 장치 등을 C프로그램이 자동으로 여는 파일로 생각하면 된다. 이때 키보드 입력은 stdin이라는  스트림으로 대표되고 모니터로 출력되는  것은 stdout라는 스트림으로 대표된다. getchar(), putchar(), printf(), scanf()함수 들은 모두 이 두 종류의 스트림을 처리해 주는 표준 입출력 패키지에 속한다.

 

지금 까지 설명한 바에 따르면 키보드 입력 기법을 파일 처리에서도 마찬가지로 사용할 수 있다. 예를 들어 파일을  읽는 프로그램은 파일 종료를 알 수 있는 방법이 필요하다 그래야 언제 읽기를 중단할지 판단할 수 있기 때문이다. C입력 함수들은 EOF(End of File)를 자동으로 검색해 주는 기능을 모두 내장하고 있다. 키보드 입력이 파일처럼 취급되기 때문에 EOF검색 기능을 키보드 입력 종료를 위해 사용할 수 있다.

 

EOF

컴퓨터 운영체제는 파일이 어디서 시작하고 끝나는지를 알아야 한다. EOF를 아는 방법하나는 그 끝에 특수 문자를 위치시키는것이다. 이 방법은 CP/M, IBM-DOS 및 MS-DOS파일 등에서 사용되었다. 하지만 오늘날 이 시스템에서는 EOF를 표시하기 위해 Ctrl+Z문자를 사용하고 있다. 한때 이방법이 유일한 방법이었지만 현재는 파일 크기를 추적하는 방법등, 새로운 방법들이 사용되고 있다. 그래서 최근에 작성된 텍스트파일안에는 Ctrl+Z문자가 숨겨져 있을 수도 있고 없을 수도 있다. 하지만 만약 있다면 운영체제는 이것을 EOF표시고 간주할것이다.

 

ex)Ishphat the robot slid open the hatch and shouted his challenge.\n^Z

 

다음은 파일 크기에 대한 정보를 운영체제가 저장하는 방법이다. 3000바이트의 크기를 가진 파일에 대해서 프로그램이 3000바이트까지 읽었다면 그 끝에 다다른 것을 의미한다. MS-DOS와 비슷한 운영체제에서는 2진파일에 대해서 이방법을 사용한다. 왜냐하면 2진파일에서는 Ctrl+Z를 포함한 모든 문자들을 파일내용에 포함 시킬 수 있기 때문이다. DOS의 초신 버전들에서는 텍스트 파일에 대해서도 이방법을 사용하기시작했다 한편 UNIX는 모든 파일애 대해 이 방법을 적용한다.

C는 파일 끝에 다다랐을 때 getchar()함수가 특별한 값을 반환하도록 함으로써 앞에서 소개한 다양한 방법들을 처리한다. 따라서 각 운영체제가 파일 끝을 검새하는 방법과는 상관없으며 이렇게 반환되는 값을 EOF라고 한다. 따라서 파일 끝을 검색했을 때getchar()함수의 리턴값이 EOF가 되며 scanf()함수도 파일 끝을 검색했을때 EOF값을 반환한다. 일반적으로 EOF는 stdio.h 헤더 파일에 다음과 같이 정의 되어 있다.

 

#define EOF (-1)

 

그렇다면 왜 -1 값으로 정의 했는가? 보통 getchar()함수는 -0에서 127 사이의 값을 되돌린다. 왜냐하면 이것이 표준 문자셋에 들어있는 문자들에 대한 코드값들이기 때문이다. 그리고 해당 시스템이 확장문자셋을 인식하는 경우에는 0~255사이의 값이 반환된다. 이렇게 -1값이 어느 문자의 코드값도 되지 않기 때문에 파일 끝을 표시하는데 사용될 수 있다.

EOF의 값을 -1이 아닌 다른 값을 갖도록 정의하는 시스템도 있다. 하지만 이 경우에도 일반적인 입력 문자에 발생된 리턴값과는 항상 다르다. 프로그램에 stdio.h 헤더 파일과 EOF기호를 사용하면 어떤 값이 정의에 사용되었는지에 대해서 걱정하지 않아도 된다. 여기서 기억해야 할 것은 EOF가 파일 안에서 실제로 찾아지는 기호가 아니라 파일이 어디서 끝나는지를 알리는 신호값을 대표한다는 것이다.

 그렇다면 프로그램 내에서 EOF를 어떻게 사용하는 것일까? 이것은 getchar()의 리턴값과 EOF를 비교하는 방법으로 사용할 수 있다. 여기서 서로 값이 다르면EOF가 아직 나오지 않았다는 것인데, 구체적으로 다음과 같이 표현식을 만들어 사용할 수 있다.

 

while((ch = getchar()) != EOF)

 

만약 파일이 아닌 키보드로 입력 받는 경우는 어떠할까? 대부분의 시스템에서는 파일 끝 조건과 키보드 입력 상황을 일치시키는 방법을 가지고 있다. 따라서 다음 예제와 같이 입력을 읽고 에코시켜 주는 프로그램을 작성할 수 있다.

 

#include <stdio.h>

int main(void)

{

   int ch;

   while((ch = getchar()) != EOF)

      putchar(ch);

   retur 0;

}

 

이 프로그램과 관련해서 다음 사항을 주의 하도록한다.

 

- EOF는 stdio.h파일 안에 정의 되어 있기 때문에 정의에 신경 쓰지 않아도 된다.

 

- stdio.h 헤더 파일 안에 있는 #define 정의로 해서 EOF기호를 사용할 수 있기 때문에 EOF의 값이 얼마인지에 대해서는 걱정하지 않아도 된다. 따라서 EOF를 위해 특정 값을 직접 사용할 필요가 없다.

 

- ch변수는char형에서 int형으로 자동 변환된다. 그 이유는 char변수가 0 에서 255 사이의 unsigned정수형 값을 가질 수도 있지만 EOF가 -1값을 갖기 때문이다.

이 값은 unsigned char 변수가 갖기에는 불가능한 값이지만 int형을 이 값을 가질 수 있다. 그리고 getchar()는 원래 int형이기 때문에 EOF문자를 읽을 수 있다. 만약 singed char형을 사용하는 시스템이라면 ch를 char형으로 선언해도 문제가 없을 수도 있다. 하지만 이식성을 높이기 위해 일반적인 형식을 사용하는 것이 좋다.

 

- ch 변수가 정수형이라고해서 putchar()가 정수형을 출력시키는 것은 아니다. 여전히 해당 문자를 출력시켜 준다.

 

- 이 프로그램을 키보드 입력에 사용할 때에는 EOF 문자를 입력시클 수 있는 방법이 필요하다. 이때 E,O,F식으로 문자를 입력하거나 또는 리턴값인 -1을 입력할 수는 없다.(-1은 하이픈과 1 두문자로 인식된다.)대신 이를 위해 각 시스템이 요구하는 사항을 알아보아야한다. 예를 들어 대부분의 UNIX시스템에서는 새로운 라인의 시작점에서 Ctrl+D를 입력하면 EOF신호가 전송된다. 그리고 많은 마이크로 컴퓨터 시스템은 라인 어느곳에서나 Ctrl+D를 입력하면 EOF로 간주한다.

 

아래는 위 소스를 컴파일 하여 실행시킨 예이다.

 

She walks in beauty, like the night

She walks in beauty, like the night

   Of cloudless climes and starry skies...

   Of cloudless climes and starry skies...

 

[Ctrl + D]

 

엔터키를 누를 때마다 버퍼에 저장되어 있던  문자들이 처리되어 한 라인에 출력된다.

UNIX시스템에서는 파일 끝상황이 일어날때까지 계속된다.  그리고 PC에서는 Ctrl+Z가 눌러질 때 까지 계속된다.

 

잠깐 위 소스의 발전 가능성에 대해서 살펴보자.

이 프로그램은 입력한 것은 무엇이든 모니터로 출력시켜 준다. 만약 파일을 입력하면 어떻게 될까? 그러면 이 프로그램은 파일의 내용을 모니터 스크린으로 출력시켜준다. 이때 파일의 EOF신호를 찾으면 출력을 끝낸다. 여기서 이 프로그램의 출력을 파일로 전송시키려면 어떻게 할까? 이럴 경우 데이터를 키보드로 부터 입력시킨다음 다음에 위소스가 이 데이터를 파일로 저장시키도록 하면 된다. 그러면 파일의 입력과 출력을 동시에 할 수 있다면 어떻게 될까? 이 경우에는 소스로 하여금 파일을 복사하도록 만들 수 있다 이 프로그램은 파일의 내용을 읽고 새로운 파일을 생성하며 파일을 복사하는 잠재성을 가지고 있다. 이러한 기능들은 조그만 프로그램에 있어서는 놀라운 기능이라고 할 수 있다. 여기서 중요한점은 입력과 출력의 흐름을 어떻게 통제 하는가이다.

 

방향 재설정

입출력엔ㄴ 함수, 데이터 및 주변 장치가 필요하다. 예를 들어 상기 소스를 생각해 보면 이 프로그램은 입력함수인 getchar()를 사용한다. 그리고 입력장치로는 키보드를 사용하고 입력 데이터 스트림은 개별 문자들로 구성된다. 이때 똑같은 입력 함수와 데이터를 사용하면서 프로그램이 데이터를 찾는 장소를 다르게 하려면 어떻게 할까? 이 질문은 프로그램의 입력이 어디에 있는지 어떻게 아는 것일까? ㄹ는 의구심을 불러일으킨다.

 

기본적으로 푲ㄴ 입출력 패키지를 사용하는 C프로그램은 입력 소스로 표준 입력을 찾는 다. 이것은 stdin으로 정의된 스트림인데, 여기에 컴퓨터에 데이터가 입력되는 방법이 정의되어있다. 이 정의는 자기 테이프와 같은 오래된 장치나 펀치 또는 전신기나 키보드 아니면 아직 개발 단ㄱ에 있는 음성 입력 장치를 지정할 지도 모른다. 하지만 현대 컴퓨터는 신축적이어서 사용자가 컴퓨터에 명령을 내려 다른 곳에서 입력을 찾으라고 설정할 수도 있다. 즉, 특별히 프로그램으로 하여금 키보드가 아닌 파일로 부터 입력을 찾으라고 요구할 수 있는 것이다.

프로그램을 ㅏ일과 작동하도록 하는 방법은 두가지가 있다. 첫 번째 방법은 파일 열기, 다디, 읽기 및 쓰기가있다. 두번째 방법은 키보드와 모니터로 입력과 출력이 작동되는 프로그램에 대해서, 다른 채널을 통해서 이루어지도록 방향 재설정을 하는 것이다. 일례로 파일을 통해 입ㅊㄹ력을 하도록 재설정할 수 있다. 즉, stdin스트림을 파일로 재지정하는 것이다. getchar() 프로그램은 데이터를 스트림으로 부터 계속 받지만 그 스트림이 어딩서 오는지에는 관심이 없다. 이러한 방향 재설정 방법은 첫 번째 방법보다 몇가지 면에서 제한적이지만 사용하기가 간편하면서도 일반적인 파일 처리 기술에 친숙해 질수 있다는 장정이있다. 방향 재설정 방법의 주요 문제점은 이 방법이 C가 아닌 운영체제와 관련되어 있다는 것이다. 하지만 UNIX, LINUX 및 MS-DOS 드으이 다양한 C환경에서 방향 재설정 기능을 제공하고 있으며 이러한 기능이 제공되지 않는 시스템에서도 C구현으로 시뮬레이션 할 수 있다.

 

방향재설정의 예(unix, linux, ms-dos)

 

$ ls -al > text

 

ls -al의 결과내용이 text라는 파일명으로 저장이 되는것이다.

즉, ls -al의 결과가 입력이 되므로 입력방향의 재설정이 되는것이고 출력은 화면이 아닌 text라는 파일로 되는것이다.

 

$ ./echo_eof < text    /* echo_eof는 위 소스 컴파일한 실행파일명임. */

 

위와같이 실행했을시 text의 내용이 echo_eof의 표준입력값이 된다.

그러면 프로그램의 에코기능상 입력된 값에서 EOF를 만날때 까지 표준출력인 화면으로 text의 내용을 화면으로 출력해 줄것이다.

 

그리고 UNIX, Linux 및 DOS는 >>연산자도 제공한다. 이 연사나를 사용하면 새로운 데이터를 이미 존재하는 파일의 데이터 뒤에 붙여 줄 수 있다. 그리고 | 연삱를 사용하면 한 프로그램의 출력을 두번째 프로그램의 입력에 붙여 줄 수 있다.

Do you understand?

2010년 1월 20일 수요일

문자 입력 버퍼

#include <stdio.h>

int main(void)
{
   char ch;

   while((ch = getchar()) != '#')
      putchar(ch);
   return 0;
}

1. 상황별 버퍼의 출력
 : 버퍼는 임시 저장소이다. 위 예제의 경우 어떤 시스템에서는 키보드 입력시 에코가 바로 되고 또 어떤시스템에서는 엔터값 입력시 프로그램에 제공(출력) 되는경우가 있다. 입력 문자가 즉시 에코되는 상황은 입력된 문자가 버퍼에 저장되지 않는경우에 발생하고 엔터가 입력되어야 에코가 되는 상황은 먼저 버퍼에 저장될 때 발생한다.

2. 버퍼는 왜 있나?
 : 첫째, 문자의 전송을 한 문자씩 해 주는 것보다 여러 문자를 동시에 전송시키는 것이 더 빠르기 때문이고, 둘째, 입력을 잘못 하더라도 키보드에서 쉽게 고칠 수 있기 때문이다. 그래서 엔터키를 누르면 수정된 데이터가 입력되는 것이다.

어떤 경우는 버퍼를 사용하지 않아야 적합한 경우가 있다. 바로 게임인것이다. 입력시 키보드 값이 즉시적용이 되어야 하기 때문이다.

3. 버퍼의 종류
 - 전체 버퍼 입출력
   : 버퍼가 꽉찼을 때 비워지는데 이러한 종류의 버퍼링은 주로 파일 입.출력시 주로 발생한다.  이 때 버퍼의 크기는 시스템 마다 다르지만 보통 512byte나 4096byte가 사용된다.

 - 라인 버퍼 입출력
   : 개행 문자가 입력되면 버퍼가 비워진다. 키보드로 입력하는 경우 주로 라인 버퍼 입력 방식이 사용된다. 그래서 엔터를 입력해야 버퍼가 비워진다.

4. 버퍼 종류의 결정 방법
 : ANSI C 표준에 따르면 입려은 버퍼가 되어야 한다고 규정하고 있다. 하지만 K&R C에서는 컾일러 설계자가 선택할 수 있었다. 자신의 시스템이 어떻게 작동하는지는 맨위 예제 소스를 실행하면 알수있다.
ANSI C에서 입력 버퍼링을 표준으로 지정한 이유는 버퍼되지 않는 입력 자체를 허용하지 않는 컴퓨터 시스템들이 있기 때문이다. 만약 시스템이 입력하면 버퍼되지 않는 것을 허용한다면 컴파일러가 이것을 선택사항으로 허용할지도 모른다. IBM호환 기종의 컴퓨터에서 사용되는 컴ㅍ일러는 보통 입력해서 버퍼되지 않아도 되는 기능을 지원하기 위해 conio.h 군의 함수들을 제공 하고 있다.이 중에 버퍼링이 발생하지 않은 상태에서 에코를 해주는 함수는 getche()함수이고 버퍼링도 되지 않고 에코도 되지 않도록 해 주는 함수는 getch() 이다. 여기서 에코가 되지 않는 다는 것은 입력된 문자가 모니터에 나타나지 않는다는것이다.  UNIX 시스템에서는 다른 저븐 방식을 사용하는데, 입력 문자를 지정하기 위해서 ioctl()함수를 사용한다. 이 함수는 UNIX 라이브러리에는 포함 되어 있지만 표준 C의 일부는 아니다. 이 목적을 위해 getchar()도 사용될 수 있다. ANSI C 에서는 버퍼링 통제를 위해 setbuf()함수와 setvbuf()함수를 사용하지만 시스템에 따라서 제약을 받을 수도 있다. 즉, 표준 ANSI에서는 버퍼되지 않은 입력을 불러올 방법이 존재하지 않는다. 이는 컴퓨터 시스템에 의존한다는 것을 뜻한다.

2010년 1월 17일 일요일

vim 환경설정

ubuntu vim컬러 변경

1. vim최신을 설치한다.
 : sudo apt-get install vim
2. whereis vim 으로 설치된 위치를 확인한다.
 : /usr/share/vim/vim72/colors/
3. 아래 링크에서 해당 컬러를 복사하여 2번항목 위치에 저장한다.
 : http://www.cs.cmu.edu/~maverick/VimColorSchemeTest/index-c.html
4. vim이 설치된 경로 에서 vimrc파일을 열어 수정한다.
 : vi /usr/share/vim/vimrc
5. 열어진 파일 맨 아래에 다음 내용을 추가한다.
 : ":colo 파일명"  ==> 여기서 확장자 .vim은 제외이다.

 : ex):colo fruidle   ==>fruidle 파일 아래 첨부

 



VIM 입력환경 설정

vi의 설정 파일은 /etc/vimrc 파일과 홈 디렉토리의 .vimrc 파일이다

우분투는 /usr/share/vim/vimrc


vi ~/.vimrc

set cindent " C 프로그래밍을 할때 자동으로 들여쓰기를 한다.

set shiftwidth=4 " 자동 들여쓰기를 할때 3칸 들여쓰도록 한다. 3칸이 적당함.

set smartindent " 좀더 똑똑한 들여쓰기를 위한 옵션이다.

set autoindent " 자동으로 들여쓰기를 한다.

set textwidth=79 " 만약 79번째 글자를 넘어가면
set wrap " 자동으로 를 삽입하여 다음 줄로 넘어간다.
set nowrapscan " 검색할 때 문서의 끝에서 다시 처음으로 돌아가지 않는다.
set nobackup " 백업 파일을 만들지 않는다.
set visualbell " 키를 잘못눌렀을 때 삑 소리를 내는 대신 번쩍이게 한다.
set ruler " 화면 우측 하단에 현재 커서의 위치(줄,칸)를 보여준다.
set tabstop=4 " Tab을 눌렀을 때 8칸 대신 4칸 이동하도록 한다.

아래는 한글을 제대로 처리하기 위해 필요하다.
if $LANG[0] == `k' && $LANG[1] == `o'
set fileencoding=korea
endif

터미널이 xterm-debian 혹은 xterm-xfree86일 경우 컬러를 사용한다.
if &term =~ "xterm-debian" || &term =~ "xterm-xfree86"
set t_Co=16
set t_Sf=^[[3%dm
set t_Sb=^[[4%dm
set t_kb=^H
fixdel
endif

"문법 강조 기능을 사용한다.
if has("syntax")
syntax on " Default to no syntax highlightning
endif