[C언어] goto 문 과 labeling

COM2008. 2. 23. 23:02 |

어느책을 보더라도, GOTO 문은 프로그래밍의 금기처럼 묘사된다. 그런데, 옛날기억을 더듬어 보면 GW-BASIC 베우던 시절에 GOTO 문은 약방의 감초처럼 사용됐었다. 구조적이고 모듈화된 프로그래밍을 무너뜨린다는 무조건분기문 GOTO. 어떤 책들은 GOTO문에 대한 언급은 딱 한줄. 유해하다는 것으로 끝내고, 구문의 설명등은 아예 생략하는 경우도 있다.

물론, GOTO 문이 없어도 원하는 기능은 다른 명령어들을 이용해서 구현할수 있고, 또한 그것이 바람직한 것으로 받아들여진다. 하지만, 때때로 GOTO 문은 복잡한 프로그램을 간결하게 해주기도 하고, 각각의 모듈내에서 독립적으로 쓰여 전체 프로그램의 틀을 해치지 않는다면, 적절하게 사용하는것은 절대 독이 아니라고 생각한다.

GW-BASIC 에서는 코드의 각 라인마다 번호를 붙여서 어느 라인으로 가라는 형식으로 사용했었다.
예)   GOTO 110    ( 참고 : Fortran 도 같은 형식을 갖는다. )

그런데 C 코딩에서는 일반적으로 라인마다 넘버링을 하지 않는다. 그럼 어떻게 GOTO 문을 사용하는가?
바로 필요한 문장만 레이블을 붙여서 사용하면 된다.

레이블을 붙인 문장의 구문형식은 다음과 같다.

labeled_statement   ::=  label   : statement
label 
::=  identifier

즉, 문장앞에 콜론(:) 을 이용하여 label을 붙이며, 이때 레이블의 형식은 identifier 와 동일하다.
( 참고 : 0-002. Tokens of C )

GOTO 문은 제어를 레이블(label)된 문장으로 넘긴다. 형식은 다음과 같다.

goto label ;


레이블은 중복해서 붙여도 상관없다.

즉, label1 : label2 : label3 : statement ;   처럼 붙여도 된다.

이제 goto 문이 들어간 예제를 보자. 다음의 예제는 while , for 의 반복문과 if , switch  등의 조건분기문을 이용하여 goto 없이 만들수 있다는것을 염두에 두자.

#include<stdio.h>

void main(void)
{
      int n;
input:     printf("Input a non-negative integer : " );
             scanf("%d",&n);
 
            if(n<0)
                 goto error;
 
            printf("\nThe square of %d = %d \n\n",n,n*n);
                  return;

error:     printf("\nAre you nuts? Try again -_-;\n\n");
                  goto input;
}

[C언어] remove() , rename()

COM2008. 2. 15. 01:06 |

remove() 는 파일을 삭제한다.
int remove ( const char * )

rename() 는 파일명을 변경한다.
int rename ( const char * , const char * )

앞쪽의 인자는 변경할 파일이고, 두번재 인자는 바꿔줄이름이다.

fread() 와 fwrite() 는 이진파일을 읽고 쓰기 위해 사용된다. 이때 변환은 수행되지 않는다. 포함된 헤더파일은 stdio.h 이다.


fwite()

프로토타입은 다음과 같다.

size_t fwrite( const void* , size_t , size_t , FILE* );

(  size_t 형은 보통 4바이트 워드 컴퓨터에서 unsinged 이다. )


사용구문

fwrite( ptr , el_size , el_number , fp );

ptr 의 주소로가서, el_size 바이트를 단위로 해서, el_number 개 만큼 읽어서 fp 에 쓴다.
리턴값은 성공적으로 기록한 원소의 수이다.






fread()

프로토타입은 다음과 같다.

size_t fread( void* , size_t , size_t , FILE* );


사용구문

fread( ptr , el_size , el_number , fp );

인자는 fwrite() 와 동일하다. 대신 반대로 fp 에서 el_size 단위로, el_number 읽어서, ptr 에 넣는다.




getw()  와  putw() 는 이진 정수 데이터의 입출력 함수이다.

헤더: stdio.h

프로토 타입은 다음과 같다.

int getw( FILE* )
해당 파일에서 정수하나를 받아서 리턴한다.

int putw( int , FILE* )

해당 파일에, 첫번째 인자, 이진 정수를 출력한다. 성공시 리턴값도 이와 같다.


fprintf()

fprintf() 는 출력(print)함수이다. 첫번째 f 의 의미는 file 이고, 마지막 f의 의미는 formatted 이다.

conversion specification (또는 format) 을 통해 출력을 제어할 수 있다. 이러한 문자열을 제어 문자열이라고 한다. 또 이함수는 임의의 개수의 인자를 출력할수 있다.

다음은 fprintf() 의 함수 프로토타입이다.

int fprintf( FILE* , const char* , ... );

리턴값은 출력된 문자의 수이다. 에러시 EOF 를 리턴한다.


구문형식은 다음과 같다.

fprintf ( file_pointer , control_string , other_arguments ) ;


첫번째 파라미터인 파일포인터는 출력장소를 가리킨다. 출력방식은 텍스트 형식이다.

예. fprintf( fp, "She sells %d %s for $%f", 99, "sea shells", 3.77);

여기서 control_string 은  "She sells %d %s for $%f" 이고,
other_arguments 는 99, "sea shells", 3.77이다.

ohter_arguments의 수식은 평가되고, 제어 문자열의 대응되는 변환명세에 따라 변환되어 출력스트림에 놓여진다.

% 기호는 변환 명세의 시작을 나타내고, 변환문자로 끝난다. %와 변환 문자 사이에는 그밖에 부가적인 정보들이 올 수 있다.


fprintf() 의 변환문자

c          char 형
d, i       int 형 , 10진수
u          unsigned 형, 10진수
o          unsigned  형, 8진수
x, X      unsigned 형, 16진수
e, E      지수형 e 또는 E
f           float 형
g, G      e 형식과 f 형식중 또는 E 형식과 f 형식중 짧은 쪽
s          문자열
p          void* 형, 16진수
n          정수형 포인터, 현재까지 출력된 문자의 개수.
%         %%형식으로 %을 출력스트림에 쓴다. 대응인자 없음


변환명세의 시작인 % 와 끝인 변환문자 사이에 다음과 같은 것들이 순서대로 올 수 있다.

flag 문자

-            좌측정렬,  -가 없다면 디폴트는 우측임
+            singned 변환에서, 음이 아닌수 앞에 + 기호 붙임
공백        signed 변환에서, 음아닌 수앞에 공백을 붙임, + 플래그와 동시에 사용되면 공백플래그는 무시됨
#            변환문자에 따라 선택적인 형태로 변환시킨다.
              #o(8진수)   :   앞에 0 을 붙인다.
              #x(16진수)  :   앞에 0x 를 붙인다.
              #g, 또는 #G          :   소숫점 뒤에 0 들을 출력시킨다.
0            공백대신 0 으로 채운다. 0x 나 0X 는 이러한 0 앞에 출력된다.
*            필드폭과 정밀도를 대응인자로 받을 수 있다.
             *.* 라고 하면, 첫번째 * 플래그 는 필드폭과 정렬을 받는다. 음수가 오면 - 플래그를 받은것과 같다.
             두번째 * 플래그는 정밀도이다. 양수가 입력되어야 하고, 음수이면 정밀도를 명시하지 않은것과 같다.
             예.
fprintf(fp,"%*.*d",-4,10,n);  라고 하면,  이것은  fprintf(fp,"%-4.10d",n); 와 같다.


field width (필드폭)
필드폭은 양의 정수로 입력한다.

precision   (정밀도)
정밀도는 점(.) 과 양의정수로 입력된다.
정수형 변환에서는, 최대자릿수이고,
실수형 변환에서는, 소숫점 이하 자리수를 표시한다

h 또는 l 또는 L
h는 short , l 은 long  , L 도 long 인데 실수형에만 쓰인다.
참고로 long float 는 double 을 나타냄



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

fscanf()

fscanf()는 fprintf() 에 대응되는 입력함수로, 상당히 유사하다.

다음은 fscanf() 의 함수 프로토타입이다.
int fscanf( FILE* , const char* , ... );

리턴값은 성공적으로 입력받은 개수이다. 파일 끝을 만나면 EOF 를 리턴한다.


구문형식은 다음과 같다.
fscanf ( file_pointer , control_string , other_arguments ) ;

변환명세의 시작은 fprintf() 와 마찬가지로 %로 시작한다. 이때, 변환명세의 대응인자들은 포인터가 온다.


fscanf() 의 변환문자

c           char 형
d, i        int 형,  10진수
u           unsigned 형,  10진수
o           unsigned 형,  8진수
x, X       unsigned 형,   16진수
f            float 형,    e,E,g,G 도 마찬가지

s           공백없는문자열, 공백이 있으면 멈추고, 공백전까지 끊어서 한 문자열로 받음
             단, 처음에 나오는 공백은 무시한다. 즉, 시작은 공백이 아닌 문자가 나올때 부터 시작한다.

p           void* 형, 보통 부호없는 16진수로 받음

n           Nothing is expected; instead, the number of characters  consumed thus  far  from  the  input
             is stored through the next pointer, which must be a pointer to  int.
             This  is  not  a  conversion, although  it  can be suppressed with the * flag.
             The C standard says: `Execution of  a  %n  directive  does  not  increment  the assignment
             count  returned  at the completion of execution' but the Corrigendum seems to contradict this.
             Probably  it  is  wise not  to  make any assumptions on the effect of %n conversions
             on the return value.

             %n 은 입력스트림으로 부터 받는것이 아니라, 지금까지 읽어드린 문자의 개수를 저장한다.
             대응되는 인자는 정수형 포인터이다.
            ( scanf 의 인자는 어차피 포인터이니, 실제적으로는 정수형 변수와 대응된다고 생각하면 된다. )


%         입력스트림에서 하나의 %와 짝을 이룬다. 대응인자는 없다.

[...]      조건 문자열.
            %[ ] 에서, [ ] 안에 들어가는 문자들을 scan set 이라고 한다. 스캔 집합 내의 문자들만 받아들인다.
            반대로, ^ 로 시작하면, 스캔 집합 내의 문자들 외의 문자들만 받아들인다. 이 규칙을 벗어나면 멈추고
            그 전까지 끊어서 문자열을 받는다.

            가령 %[AB \n\t] 라고 하면, 'A', 'B', 공백, 줄바꿈, 탭 만 받아들이고, 이를 어겨야 중단된다.



사용 가능한 flag들. %와 변환문자 사이에 온다.

h 또는 l 또는 L          h 는 short 를 l 과 L 은 long 인데, L은 실수형에만 가능하다.
scan filed 폭            %와 변환문자 사이에 양의 정수를 넣으면, 최대스캔폭을 지정할 수 있다.
                              폭을 넘어가는 입력은 무시한다.

*                            입력무시
                              예. scanf("%*d%s",str);


제어문자열이 포함하는 문자.

여백                                            입력스트림에 있을수 있는 공백과 대응된다.
문자 ( 공백아니고, %도 아닌)          입력스트림의 문자와 대응되어야 한다.


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

stdio.h 에는 다음과 같은 파일 포인터가 정의되어있다. 모두 FILE* 형이다.

stdout        표준출력        화면으로 연결됨
stdin          표준입력        키보드로 연결됨
stderr         표준에러        화면으로 연결됨


따라서, fprintf( stdout ,  ... , ... ) 으로 하면 화면에 찍는다.  이것은 printf() 와 같다.


printf()
printf( ... )   =  fprintf( stdout , ... )


마찬가지로, fscanf( stdin , ... , ... ) 으로 하면 키보드로 부터 입력을 받는다. 이것은 scanf() 와 같다.

scanf()
scanf( ...  )   =  fscanf( stdin , ...  )


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

파일을 입출력 받을 장소가 문자열일때는 sprintf() 와 sscanf() 쓸 수 있다.

sprintf()

프로토타입은 다음과 같다.
int sprintf( char * , const char * , ... ) ;

첫번째 인자가 가리키는 문자열에 쓴다.



scanf()

프로토타입은 다음과 같다.
int sscanf( const char* , const char * , ... );


첫번째 인자가 가리키는 문자열로 부터 입력을 받는다.


파일포인터 ( FILE형 포인터가 아니라, 파일 내부위치를 가리키는 포인터 )

int fseek ( FILE* , long , int )

첫번째인자 : 대상파일
두번째인자 : 오프셋 ( 이동시킬 거리 byte 단위, long 형 )
세번째인자 : 모드 ( 0 = 시작점 , 1 = 현재점 , 2 = 끝점 )

다음과 같이 디파인되어있다.
SEEK_SET 0
SEEK_CUR 1
SEEK_END 2

예.
fseek( fp , 100L , 0 );       //   fp 가 가리키는 파일의 시작점에서, 100 바이트 다음으로 이동시킴
fseek( fp, -100L , 2 );      //   fp 가 가리키는 파일의 끝점에서, 100 바이트 이전쪽으로 이동시킴





long ftell ( FILE* );       //  해당 FILE형 포인터가 가리키는 파일의 파일내부포인터의 위치를 반환한다.
                                      시작점으로 부터 몇 바이트 위치에 있는지 long 형으로 반환함.

void rewind(FILE*);     //  파일 내부포인터를 시작점으로 옮김
.



fopen() 이나 fclose() 로 파일을 열고 닫을때, 꼭 등장하는 녀석이 바로 FILE 형 포인터 일 것이다. 대문자로 쓴것에서도 냄새가 물씬 풍기듯이 keyword 가 아니고, stdio.h 에 정의되어있는 구조체이다. 구조체는 데이터형을 묶어놓은 패키지로 대상의 속성에 맞게 세팅해서 사용하는 것이라고 할 수 있겠다. 구조체는 나중에 살펴보도록 하고...

FILE 은 stdio.h 에 정의되어 있는데, 기본적으로 machine dependent 이다.

struct _iobuf {
              char *_ptr ;
              int    _cnt ;
              char *_base;
              int    _flag;
              int    _file;
              int    _charbuf;
              int    _bufsiz;
              char *_tmpfname;
};

typedef struct _iobuf FILE;

위의 상황에서는 FILE은 typedef 로 이름만 다시 붙인것이므로, FILE 대신 struct _iobuf  을 써도 똑같지만, 기본적으로 machine dependent 하고, typedef 가 machine dependency 를 다소 해결하는데 도움을 주므로, 그냥 FILE 을 쓰는것이 좋다.

fopen() 함수는 stdio.h 에 정의되어 있으며, 프로토타입은 다음과 같다.

FILE*  fopen( const char * , const char * );

즉, 두개의 문자열 상수를 받고, FILE 형 포인터를 리턴한다.

첫번째 인자는 대상 파일명 이고, 두번째 인자는 open mode 이다.

open mode 는 읽기(r) , 쓰기(w) , 첨부(a) 의 세가지 기본적인 모드가 있다.


기본3가지 모드에 대해서 살펴보면...

"r"  read
 파일이 있으면 열고 없거나 접근할 수 없으면 NULL 포인터를 리턴한다.

"w" write
파일이 있거나 말거나 걍 새로 쓴다. 즉, 있으면 지워버리고 새로 쓴다. 역시 접근할수 없으면 NULL 포인터를 리턴한다.

"a"  append
파일뒤에 덧붙여 쓰는 모드이다. 없으면 새로 쓰고, 있으면 덧붙여 쓴다. 접근할수 없으면 NULL 포인터를 리턴한다.


이진처리 옵션은 b 이다.
"rb"           read binary
"wb"          write binary
"ab"           append binary


또다른 옵션으로 + 이 있다. 이것에 대해서는 생략한다.

파일은 동시에 여러개를 열어놓을수 있다. 가령 어떤 파일을 열어서, 다른파일 하나를 더 연다음 뒤에 덧붙여 쓰거나 할 수 있다. 이때는 두개의 파일을 연것이다.

파일처리가 끝나면 파일을 닫아준다. 물고있는 파일을 놓는것이다. 물론, 대부분의 컴파일러는 프로그램이 종료되면 자동으로 처리중인 파일을 닫아주지만, 그래도 확실하게 닫아주는것이 좋다.

이때 사용되는 함수가 fclose() 이다. 프로토 타입은 다음과 같다.
int  fclose( FILE* );

인자는 닫고자 하는 파일을 가리키는 파일포인터이다.
리턴값은 성공적으로 닫았을경우 0 을 리턴하고, 에러 발생시 EOF 를 반환한다.
참고로 EOF 는 end of file 을 의미하고, int 값으로 -1 이다.


예.
#include<stdio.h>

void main(void)
{
     FILE *fp;
        /*  FILE 대신 struct _iobuf 를 써도 똑같이 돌아간다. */
 
     fp = fopen("test.txt","w");       /*   쓰기모드로 열고, 파일포인터가 test.txt 를 가리키도록...   */

     fprintf(fp,"Hello!");                 /*   test.txt 에 Hello! 를 기록한다. */

     fclose(fp);      /*  fclose() 가 인자로 파일포인터를 갖는데 반해, _fcloseall() 은 인자가없다.*/

     return;
}

컴파일후 실행해보면, test.txt 파일을 생성한다. 그 파일을 열어보면, Hello! 라고 기록이 되어있는 것을 확인할 수 있다.

배열로 메모리를 요청할 경우, 싸이즈를 미리 예측해서 요청을 해야 한다. 그러나 많은 경우, 가변적으로 메모리를 요청해야 하는데, 이와 같이 동적으로 메모리를 요청하는 함수로 malloc() 과 calloc() 이 있다.


malloc()     memory allocation

헤더파일:  stdlib.h
프로토타입: void* malloc( size_t )

파라메터: 요청할 메모리의 바이트 크기   (size_t 는 보통 unsigned int )
리턴값: 메모리 요청 성공시에 그 메모리의 시작주소, 실패시 NULL

char *ptr;
ptr = malloc(6);      // 6바이트를 요청하고, 포인터를 그 시작주소에 포인팅함




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



calloc()     contiguous allocation

헤더파일:  stdlib.h
프로토타입: void* malloc( size_t , size_t )

첫번째 파라메터: 요청할 메모리 단위의 개수
두번째 파라메터: 요청할 메모리 단위의 바이트 크기

즉, 단위크기 곱하기 개수 만큼 요청함

리턴값: 메모리 요청 성공시에 그 메모리의 시작주소, 실패시 NULL

결과적으로, calloc( n , m )  은  malloc ( n * m ) 과 유사하다.
차이점은 calloc 은 요청한 메모리를 모두 0 으로 초기화 시킨다는 것이다.

자주 쓰이는 형태는

ptr = calloc( n , sizeof(double) ) ;

꼴인데, 이때, 기본데이터타입을 typedef 로 쓰면, 나중에 프로그램 수정시 용이하다. 즉,

typedef double entry;
ptr = calloc( n , sizeof(entry));

typedef 에 대해서는 나중에 자세히 설명하겠다.
------------------------------------------------------------------------------------------------------


요청한 메모리를 다시 시스템에 돌려줄때는 free() 를 사용하면 된다.

free()

헤더파일:  stdlib.h
프로토타입: void free( void* )

파라메터: 이전에 할당된 메모리의 시작주소
만약 그 포인터가 NULL 이면, 아무짓도 안한다.

함수가 자기자신을 호출하는 것을 재귀호출이라 한다.

이때 호출중간에 또 자기자신을 호출하면, 이전에 호출했던 함수는 새로자신을 호출한 함수값을 받아야 리턴할수 있으므로 리턴을 미루고, 새호출의 리턴값을 받을때까지 대기한다.

이런젼차로, 새로 자신을 호출한 함수가 리턴값을 주면, 연쇄적으로 바로 이전의 호출이 리턴값을 갖게되어, 호출의 역순으로 리턴을 하게된다. 즉, 호출순으로 스택에 쌓였다가 역으로 꺼내면서 리턴한다.

당연한 얘기지만, 한번의 호출은 연속적인 호출로 이어지고 무한호출이 안되기 위해선, 언젠가는 리턴을 주는 상황이 와야된다.


예제로, 문자열을 하나 받아서 까꾸로 출력하는 함수를 (물론, 그냥 문자열 받아서 포인터로 거꾸로 돌리면서 찍어도 되지만) 재귀적으로 만들어보자.

계획.(재귀적으로...)
문자열에서 문자하나를 받아서 인쇄하는 함수를 만든다. 인쇄직전에 자신을 호출하면 인쇄를 뒤로 미루고 차례대로 뒷글자를 받아서 쌓아둔다. newline \n 을 받으면 재귀적 호출을 중지하도록 한다. 그러면 바로 직전에 미뤄둔 인쇄를 행하고, 연속적으로 거꾸로 인쇄한다.

#include<stdio.h>
#include<conio.h>                        /* for getch() in MS Windows Console. If Not MS platform, remove this and do not use getch() */

void backward(void);

void main(void)
{
      printf("Input a string : ");
      backward();

      putchar('\n');
      getch();

      return ;
}

void backward(void)
{
      char c;
      if((c=getchar()) != '\n' )
            backward();
      putchar(c);
}

backward.exe

영화를 만드는 데에도 천재라는 게 있구나.  이 영화를 만든 넘은 천재임에 틀림이 없다. ★★★★★ 최고의 영화로 손색이 없다.

사용자 삽입 이미지

그것은 마치 사고와도 같이 나는 아무것도 모른채 이 영화를 보게되었다.

그리고 그것이 이 영화를 감상하는 최고의 방법이라고 생각한다.

아니 대부분의 영화에 그것은 최고의 감상법이다.

많은 영화들이, 당신이 그 영화를 처음 접한다고 생각하고 시작할테니까.

실제로 많은 좋은 평을 받는 영화들 중에, 개인적으로 기대를 많이 했던 영화들은, 역시나 개인적으로 그다지 큰 감흥을 주지 못하는 편이다.

반면, 이 영화는 어떠한 사전정보도 없이 심지어 어느 장르인지도 모르고 봤기 때문에 ( 음... 한번더 강조하지만 그것은 역시 최고의 감상법이었다. ) 정말로 가슴이 터져버릴듯한 감동을 느낄수 있었다.


그러면, 진짜로 초반 파티장면이라든가 하는 것을 보면서, 음 누가 떠나고 송별회를 하고 있군... 로맨틱 무비 인가 보군... 하면서 볼 수 있는데, 그러다가 완전 대박 놀라면서 충격적인 감동을 맛 볼 수 있게 된다.


아무튼, 클로버필드는 내 마음속에 영원한 명작으로 남을 것이다.


추신. 혹여 당신이 이 영화를 본적이 없고, 이 영화에 대해 들은바도 없다면, 언젠가는 이 영화를 꼭 보기를 바라며, 어떠한 기대도 하지말고, 어떠한 사전 조사도 하지 말고, 그냥 편안한 마음으로 영화를 감상했으면 좋겠다.

메모리(memory)와 주소(address)
어떤 변수를 선언(declare)하든, 시스템에게 해당 사이즈 만큼의 메모리를 요청할 것이다. 그리고 큰 문제가 없는한 메모리를 할당(allocate)받게 될 것이다. int 형으로 요청하면 4바이트를 요청할 것이고, 뭐 데이터형마다 각기 싸이즈를 요구할 것이다. 그리고 각각의 메모리는 주소를 갖는다. 즉, 데이터와 주소는 쌍을 이룬다. 그 주소를 알면 그 저장된 값도 언제든지 가져다가 쓸 수 있는 것이다.

포인터(pointer)
포인터는 주소값을 저장하는 녀석이다.

포인터에 저장된 주소값을 이용하면 원하는 대상이 저장되어있는 메모리를 직접 찾아갈수가 있다.


포인터의 이동단위
C에서는 포인터의 연산이 가능하다. 어떤 포인터가 어떤 주소값을 갖고 있는 상태에서 +1 해서 포인터의 값을 1 증가 시키면 포인터는 다음 주소의 값을 갖게 되는데, 이때 이동단위는 해당 포인터의 데이터형에 의존한다. 가령 char 형은 1바이트씩 이동하고, int 형은 4바이트씩 이동한다.

실제 메모리는 비트들로 되어있으므로, char 형은 8 비트씩 뛰어다니느 것이고, int형은 32비트씩 뛰게 되는 것이다.


주소연산자 (address-of operator) &
어떤 대상에 대해서, 저장된 값이 아닌, 메모리 주소(address)값을 반환한다. address-of 라고 읽기도 한다.


Reference (참조)
Reference 한다는 것은, 기본적으로, 어떠한 대상을 가리키는 것을 의미한다. C 에서는 어떠한 대상을 가리키는 방법으로 주소값을 사용한다. 주소값은 & 를 통해서 얻을수 있고, pointer variable 에 값으로 저장해두었다가 사용할 수도 있다. 참고로, C++ 에서는 주소를 직접 pointer variable 에 저장하지 않고도, 메모리에 저장된 대상을 직접 참조하는 참조변수(reference variable) 를 선언할 수 있지만, C 에는 그런게 없다. 따라서, C 의 context 에서는 reference 한다는 표현은 결국, 주소값 ( &를 통해 구한 값이든, pointer 에 저장된 값이든) 을 통해서 대상을 가리킨다는 말과 같다.


역참조 연산자 (dereference operator) *
어떠한 대상이 다른 대상의 주소를 가지고 있을때, 그 주소를 따라가서, 다른 대상의 값을 취하는 것을 dereference (역참조) 라고 한다.
역참조연산자는 * 를 사용한다.

그러므로, 주소연산과 역참조연산은 역연산 관계이다. 따라서, 이경우, & 와 * 는 약분된다.
예를 들어. 어떤 p 가 포인터라고 하자.  그러면, &*p 는 p 가 된다. 어떤 a 가 일반 변수라고 하자. 그러면 *&a 는 a 가 된다.

오프셋 역참조(dereference w/ offset) []
어떤 주소에서 부터 지정된 스텝을 오프셋시킨 상태에서 역참조하는 연산자이다.

즉,  arr[ i ]  와   * ( arr + i ) 가 같은 뜻이 된다.



포인터 변수 선언
포인터 변수의 선언 구문은 다음과 같다.
type  * variable1 , * variable2 , ... ;


* 연산자는 토큰으로, 독립적으로 쪼개질수 있으므로 붙여쓰던 띄어쓰던 그건 중요하지 않다. 다만 우선순위에서 밀릴때는 다른 연산자들과 마찬가지로, 결합우선순위를 확보하기위해 괄호를 쓰면된다. 참고로, * 연산자의 우선순위는 [] 에도 밀리고, 함수의 () 에도 밀린다.

예. int (*arr)[2];

int (*FnPick(int PickNum))(int, int) { ... }


매개변수로 포인터 변수를 넘길수 있고, 프로토타입의 경우 변수명이 필요없으므로 다음과 같은 형태가 된다.

return_type function_name ( type * , type * , ... ) ;

예. int func( int * , int* );



배열(array)명
배열명은 포인터 상수이다. 즉, char 형 배열을 선언했다면 그 배열명은 char형 포인터가 된다. 즉, 그 배열의 시작 주소를 값으로 갖고 있는 것이다. 보통의 포인터가 포인터변수(pointer variable) 임에 비해 배열명은 상수이다. 변경이 안된다. 즉, 포인터는 배정문을 통해 어디든 원하는 주소를 가리킬 수 있겠지만, 배열명에 배정문을 써서 값을 변경하는 것은 불가한다.

배열문이 포인터상수인것은 어찌보면 당연하다. 배열을 선언과 동시에, 배열명은 배열의 시작주소를 가리키는 일종의 고유명사화된 주소라고 할 수 있다.

배열의 인덱스(index)
배열의 인덱스를 나타내는 연산자 [ ] 는 사실은 오프셋 역참조 연산자이다. 배열명을 기점으로 몇칸이동(데이터형에 의존)한 지점을 역참조 할 것인가 를 나타낸다. 어떤 아파트가 있는데, 그 배열명이 그 아파트의 첫버째 집을 나타낸다면, 인덱스는 몇호에 사는 사람을 나타낸다. 즉, 호수까지 이동을 한 상태에서 역참조를 하는 것이다.

예.

char Apartment[100] = "Hi, everyone. How are you? I'm Fine, Thank you. And you? -_-" ;
 
printf( "%s" , &Apartment[15] ) ;

여기서, &Aprtment[15] 부분을 보자. Apartment 는 배열명이다. 이 아파트의 첫번째 번지수를 나타낸다. 즉, 첫번째 번지에는 H가 살고 있다. 즉, 저 H의 주소가 되는 것이다. 그런데 Apartment[15] 라고 했다. 15번째 다음의 집에는 누가 살고 있나를 말한다. 참고로 [0] 은 H가 되겠다. 0 만큼 다음 집이니까 첫집 그대로이다.

[0] : H
[1] : i
[2] : ,
[3] : 공백
[4] : e
[5] : v
...

[15] : o

즉, o 가 된다. 이때 이것은 주소가 아니라 이미 역참조( 해당주소에 살고있는 사람) 임을 상기하자.
따라서, 앞에 &를 붙여서, &Apartment[15] 라고 하게되면,  o 의 번지수를 가리키게 된다.

참고로. printf() 의 %s 는 char 형 주소를 만나면 반복적으로 주소를 다음으로 넘겨가면서 null 문자를 만날때까지 찍어댄다. 따라서 결과는 다음과 같다.

ow are you? I'm Fine, Thank you. And you? -_-


이것은  printf( "%s" , Apartment + 15 ) ;  와 같다.


포인터 오프셋(offset)

벡터 스타일의 데이터를 쓰고 싶을때, 보통의 습관은 인덱스를 1부터 쓰는것을 선호한다. 그러나 [] 연산자를 인덱스처럼 사용하게 되면, 인덱스가 0 부터 출발하기 때문에 우리의 습관과 일치하지 않는다.

이럴때 포인터를 앞으로 하나 땡겨주면, 뒤로 한칸 옮겼을때 요청한 메모리의 첫 칸을 사용하기 때문에 [1] 이 첫 주소가 된다.


따라서...
입력장치로 부터 양의정수 n을 받아서, 원하는 형태의 데이터 타입으로 n 개의 메모리를 요청하고, 첫번째 인덱스 1부터 n번째 인덱스 까지 사용하는 방법은 다음과 같다.

typedef double entry;

...

int n;
entry *a;

...   // get n somewhere

a = calloc( n , sizeof(entry) );
--a;


이런식으로 하면, a[1] 부터 a[n] 까지 쓸수있다.
대신 이 경우, a[0] 는, 우리가 요청한 곳도 아니고 접근해도 되는곳인지도 알수없으므로 절대로 써서는 안된다.
메모리를 반납할때도 free(a+1); 과 같은 식으로 반납해야 한다.


열거형 ( enum )

The enumeration data type (enum) sets up a different use of an integral data type.
열거형 데이타 타입은 그니까 정수형이다.

열거형 타입의 선언은 유한집합을 명명하고, 그 집합의 원소로서 식별자를 선언한다.
집합명은 tag 라고 하고, 집합은 { } 로 나타내며, 각 원소는 comma(,) 로 구분한다. 각 원소들은 int형 "상수" 이다.

enum tag { element_list } ;

, 로 구분된 원소들은 indentifier 들이며, 값을 assign해서 초기화 할 수 있다.  공용체의 원소들은 같은 값을 갖을 수 있다.

초기화 되지 않은 원소들은, 자신의 바로 앞(왼쪽) 원소의 값보다 1큰 수를 부여받는다. 첫번째 원소를 초기화하지 않으면 0 으로 초기화한다.



예. enum boolean { FALSE , TRUE } ;       // FALSE = 0 , TRUE = 1

     enum day { sun=1, mon, tue=1, wed, thu, fri=9, sat };   // mon=2, wed=2, thu=3, sat=10



enum 은 데이타 타입이 아니라, 열거형임을 나타내는 키워드이고, enum tag 를 묶어서 type 으로 분류한다.



열거형 "변수"의 선언은 다음과 같은 형식이다.

type variables;        // where type:=  enum tag_name

예. enum boolean a, b;     // enum boolean 타입의 변수 a, b 선언



열거형으로 선언된 변수들은 집합의 원소들을 값으로 갖을수 있다.

예. a = FALSE ;


열거형 타입의 선언과 변수선언을 한번에 할 수 있다.

예. enum day { sun=1, mon, tue, wed, thu, fri, sat } a , b ;
     a 와 b 는 enum day 타입의 변수들.


tag 는 생략가능하다(anonymous tag). 대신, tag가 없기 때문에, 처음 변수선언을 제외하고는 enum tag 의 타입으로 새로운 변수를 선언할 수 없다. (단, typedef 를 이용하면 가능하다.)

예. enum { beer, cocktail, soju } a, b ;        //같은 타입의 변수를 더이상 선언할 수 없음.


열거형 변수 선언시, 매번 열거형임을 나타내는 키워드 enum 을 쳐야되므로, typedef 를 이용해서 좀더 간단하게 하는 것이 보통이다.

예. enum alcohol { beer, cocktail, soju };
    typedef enum alcohol alcohol;      //  enum alcohol 을 alcohol 로...
     alcohol a , b ;


열거형의 선언과 typedef를 한번에 쓸수도 있다.

예. typedef enum alcohol { beer, cocktail, soju } alcohol;
     alcohol a , b ;

 

tag 가 없는 경우라도, typedef 를 사용하여 그 자체를 renaming 해버리면, 이후에도 자유롭게 사용할 수 있다.


typedef enum { element_list } identifier;        // 해당 identifier 는 tag 없는 enum { ... } 타입의 새이름이 된다.


구조체 (structure)


배열은 동종의 자료들을 표현하기 위해 파생된 타입이다. 반면, 구조체는 서로다른 타입의 자료들을 표현하는데 사용된다.  구조체를 나타내는 C의 키워드는 struct 이다.

즉, 구조체는 다양한 타입의 데이타형의 묶음이다. ( 각 타입 요소를 member 라고 한다. )

C의 struct 는 C++ 의 출발점이다. 
C의 struct 개념은 이후에 C++에서 Class 개념으로 확장된다.



structure의 선언은 형식상 enumeration data type 과 유사하다.

열거형은...  enum tag { element_list };   와 같은 꼴이었다.

구조체는 ...  struct tag { declaration_statements }; 와 같은 꼴이다.

열거형에서 {} 안에 들어가는 원소들의 구분은 콤마(,) 로 했었지만 ( 집합의 원소들이니까... )
스트럭쳐에서는 {} 안에 들어가는게 선언문들 이므로, 당연히 세미콜론(;) 으로 구분한다. (문장들 이니까...)


예. struct menu { char *item; int price; char rate; double tax, calories; } ;

열거형에서는 {} 안에 들어간것이 단순히 "정수상수"가 될 원소들이었다면, 구조체에서는 그것들이 "변수선언문"으로 대치된것이다.


구조체형 변수의 선언은 열거형과 마찬가지다.

struct tag variables ;

예. struct menu { char *item; int price; char rate; double tax, calories; } ;
    struct menu a, b, c ;


열거형과 마찬가지로, 구조체형 선언시 변수 선언도 동시에 할 수 있다.

예. struct menu { char *item; int price; char rate; double tax, calories; } a, b, c ;


tag 의 생략도 열거형과 마찬가지다.

예. struct { char *item; int price; char rate; double tax, calories; } a, b, c ;


열거형임을 명시해주는것이 귀찮아 typedef 를 쓴것과 같이, 구조체임을 명시하는 것이 귀찮아 typedef 를 많이 쓴다.

예. struct menu { char *item; int price; char rate; double tax, calories; } ;
    typedef struct menu menu;
    menu a, b, c ;


열거형때와 마찬가지로, 구조체형 선언과 typedef를 같이 할 수 있다.

예. typedef struct menu { char *item; int price; char rate; double tax, calories; } menu ;
    menu a, b, c ;



tag 가 없는 경우도 마찬가지다.

예. typedef struct { char *item; int price; char rate; double tax, calories; } menu ;
    menu a, b, c ;



구조체 타입의 배열을 선언할 수도 있고, 배열이 구조체 내부의 멤버가 될 수도 있다. 심지어 구조체의 멤버가 구조체가 될 수도 있다.
구조체 형 변수도 보통의 변수와 마찬가지로 배열을 선언할 수 있다. 즉, 이것은 동종의 구조체형 자료들을 다루기 위함이다.

예. struct menu { char *item; int price; char rate; double tax, calories; } ;
    typedef struct menu menu;
    menu a[10] ;



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

구조체 타입은 사용자가 임의로 조합해서 만든 사용자 정의 타입이라고 할 수 있다.
하지만 그것들도 전히, int 나 double 와 같은 데이터 타입이다. 물론 C에서는 키워드와 함께해야하고 연산등에 있어서 차별을 두지만, C++ 에서는 정말 차별을 두지 않는다. (적어도 C++의 철학은 그렇다.)


따라서, 그 형에 맞는 포인터를 생각할 수 있다.

가령 위의 구조체 menu 형에 대해서 그것의 포인터 형 구조체 menu 포인터 형을 생각할 수 있다.
즉, struct menu 타입의 포인터 타입은 struct menu * 타입이다. 이 포인터는 struct menu 타입 데이타들의 주소를 가리킨다.


가령, 위에서 typedef 로 간단하게 struct를 안써도 되도록 menu 타입을 셋팅했으므로 menu 타입의 포인터 타입은 menu* 타입이다.

예. menu a;
    menu *mpt;
    mpt = &a;

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

열거형에서 집합의 각 원소가 정수상수였던 것과는 달리, 구조체에서는 멤버들이 변수들이므로, 값을 자유롭게 변경하게 된다. 따라서, 멤버에 접근을 할 수 있어야한다.

구조체형의 변수는 멤버라고 하는 변수들을 포함하므로, 위계구조를 갖는 변수형이라고 할 수 있다. 이러한 위계구조는 구조체의 멤버가 또다시 구조체일때 여러단계로 나타난다.

위의 예에서, 구조체 menu 형으로 선언된 변수들 a, b, c 들은 각각 멤버로 item, price, rate, tax, calories 를 갖는다.

구조체 변수명에서 멤버에 접근할때는 . 연산자를 쓴다. C++ 클래스에서 멤버에 접근할때 . 을 쓰는것도 이와 같다.
구조체형 포인터에서 멤버에 접근할때는 포인터를 해당 구조체 변수에 포인팅한후에, ->
를 사용한다.

가령 다음과 같이 선언이 되어있다고 할때...

struct menu { char *item; int price; char rate; double tax, calories; } ;
typedef struct menu menu;
menu a ;
menu *strptr;
strptr = &a;                 //  menu형 포인터 strptr 이 menu형 변수 a의 주소를 가리킴

다음과 같이 멤버접근 연산자 . 을 이용해서 a의 멤버에 접근해서 값을 할당할 수 있다.
a.item = "chiken salad";
a.price = 9800;
a.rate = 'A';
a.tax = a.price * 0.05 ;
a.calories = 480.7 ;


또한, menu형 포인터 strptr 이 menu형 변수 a의 주소를 가리키고 있으므로...
다음과 같이 포인터의 멤버접근연산자  -> 를 이용해서 a의 멤버에 접근해 값을 할당할 수 있다.

연산자 -> 는 * 와 . 의 짬뽕이다.      p -> = ( *p ).


strptr -> item = "chiken salad";  // strptr 이 가리키고있는 구조체 변수에서 멤버 item 으로 접근 후 배정
strptr -> price = 9800;
strptr -> rate = 'A';
strptr -> tax = strptr -> price * 0.05 ;      // ( strptr -> price ) * 0.05
strptr -> calories = 480.7 ;


구조체 포인터 strptr 가 현재 구조체형 변수 a의 주소이므로, 그것을 역참조하면 a 이다. 즉, (*strptr)a

따라서,  (*strptr).rate a.rate 가 된다.  이와 같이 구조체도 포인터와 역참조 연산자 * , [] 따위의 관계를 그대로 사용할 수 있다.

또한, 구조체의 멤버가 구조체인경우에는 멤버접근 연산자  .  이나  -> 를 여러단계로 사용하면 된다.





구조체 변수의 초기화
배열은 동일자료형의 패키지이고, 구조체는 다른 자료형의 패키지라는 점에서 둘은 유사하다.
또한, 초기화 구문도 배열의 그것과 유사하다.

구조체 변수의 초기화에서도 배열과 마찬가지로, 중괄호 {} 와 콤마를 이용해 상수목록을 전달한다.
멤버들의 초기화를 위해 전달되는 상수목록은 멤버의 선언순서와 대응된다.

배열에서 상수목록의 개수가 부족하면 나머지 모든 원소가 0 으로 초기화 되었던것과 마찬가지로,
구조체에서도 상수목록의 개수가 부족하면 나머지에 대해서는 0 으로 초기화시킨다.

예.
#include <stdio.h>
int main(void){
    typedef struct { int no; char *item; int price; char rate; double tax, calories; } menu;
    menu a = { 17, "Salad", 5600 , 'A' , 560.0 , 1769.56 };    // 구조체 변수 a의 초기화

    printf("Menu No.%d\nname: %s\nprice: %d\nConsumer's rate: %c\nTax: %g\nCalories: %g\n",
            a.no, a.item, a.price, a.rate, a.tax, a.calories);

 return 0;
}


만약, 위의 초기화 구문을 다음과 같이, 첫번째 목록만 남기고 나머지를 생략하면....

    menu a = { 17 };    // 첫번재 멤버는 17로, 나머지는 모두 0 으로...


첫번째 멤버를 제외한 모든멤버가 0 으로 초기화된다. 포인터인경우 NULL 포인터로 초기화된다.

따라서, 구조체의 모든 멤버를 0으로 초기화 하는 것은, 위와같은 방식으로 초기화시키되 첫번째 멤버를 0 으로 초기화시키면 된다.

예. menu a = { 0 } ;


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

구조체 변수들 사이의 대입
구조체 형도 한번 선언이 된 후에는 int 나 double 과 마찬가지로 자료형으로서의 기능을 다 하므로, 구조체형 변수들도 마찬가지로 대입문을 사용할 수가 있다.

가령, fopen() 으로 파일을 열어서, FILE* 형 변수에 배정했던것도 마찬가지다. 이때 fopen()의 리턴타입이 구조체 FILE* 형 이므로, FILE* 형 변수에 배정할 수 있었던 것이다.

즉, 같은 타입의 구조체 변수들 사이에는 = 를 이용한 대입문이 가능하다.
이때, 대입의 의미는 대응되는 모든 멤버들에 대한 대입과 같다.

예.
#include <stdio.h>
int main(void){
    typedef struct { int no; char *item; int price; char rate; double tax, calories; } menu;
    menu a = { 17, "Salad", 5600 , 'A' , 560.0 , 1769.56 };

    menu b = a ;        //   구조체 변수 b의 선언과 배정을 동시에


    printf("Menu No.%d\nname: %s\nprice: %d\nConsumer's rate: %c\nTax: %g\nCalories: %g\n",
            b.no, b.item, b.price, b.rate, b.tax, b.calories);

 return 0;
}


구조체 값들 사이의 연산은 C++ 에서 operator 들을 overloading 함으로써 가능하나, C 에서는 그와같은 간단한 방법은 없다.




계층적 구조체

하위 개념의 구조체를 멤버로 갖는 구조체를 구성함으로써 계층적인 데이터 구조를 형상화 시킬 수 있다.

다음의 예제는 구조체를 어떻게 계층적으로 사용할 수 있는지를 보여준다. ( 물론, 겉으로는 아무것도 실행되지 않지만... )


예.
#include<stdio.h>

typedef struct { int month, date, year; } DATE;
typedef struct { char *name; int month, date, year; } SDATE;
typedef struct { DATE birth, marriage, death; SDATE specialEvent; } EVENTS;
typedef struct { char *name; EVENTS event;} PERSON;

int main(void){
     PERSON a;

     a.name = "Greenwood";

     a.event.birth.year = 1903;
     a.event.birth.month = 3;
     a.event.birth.date = 19;

     a.event.marriage.year = 1942;
     a.event.marriage.month = 1;
     a.event.marriage.date = 15;
 
     a.event.specialEvent.name = "Invention of the Perpetual Mobile";
     a.event.specialEvent.year = 1951;
     a.event.specialEvent.month = 3;
     a.event.specialEvent.date = 27;
 
     a.event.death.year = 2009;
     a.event.death.month = 11;
     a.event.death.date = 6;

     return 0;
}



위에서 보듯이, 다른 구조체에서 같은 멤버명을 사용할수 있고, 또한, 구조체의 멤버로 구조체가 들어가면서 멤버구조체의 멤버에 연소적으로 접근함으로써 계층적 구조를 만들 수 있다.


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


공용체 ( union )

공용체는 구조체와 같은 구문형식을 갖지만, 멤버들마다 다른 메모리를 잡는것이 아니라, 멤버중 가장 큰 메모리 크기를 잡은후에, 그것을 공유한다.

즉, 다시말해서, 같은 장소에 저장된 정보를 다른형식으로 읽어들일수 있다는 말이다.

자세한 얘기는 비트단위 연산을 살펴보아야 하므로 여기서는 간단히 개념만 살펴보도록 한다.

C의 공용체 키워드는 union 이고, 형 선언 구문은 아래와 같다. 구조체와 키워드 union 만 다르다.

union tag { declaration_statements };   

이외에, 변수 선언이나, 태그없는 공용체, typedef 문의 활용 등은 모두 구조체나 열거체와 마찬가지 이므로, 생략하고, 바로 예를 살펴보겠다.

union integreal { int integer; double real; };   // union integreal 형의 정의, 메모리 8바이트 할당
union integreal x ;                               // union integreal 형 변수 x 선언



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

공용체의 멤버접근도 역시 구조체와 같은 구문을 같는다.
union integreal x, *p;
x.integer = 10 ;                 
// 공용체  integreal 형 변수 x 의 멤버 integer 에 접근하여 10을 배정함.
p = &x ;                          // 공용체 integreal 형 포인터 p로 x의 주소를 포인팅
p->real = 1.542354 ;         //  포인터로부터 멤버접근후 대입


공용체의 멤버들에 각각 접근하여 배정을 하였을때, 메모리를 공유하므로 이후의 선언에 의해 앞의 선언이 덮어씌워지며 데이타의 손실이 일어날 수 있다. 그러한 것이 어떻게 진행되는지에 대한 판단과 계산은 프로그래머의 몫이다. 우리는 나중에 비트단위 연산을 통해, 그것을 좀더 자세히 살펴보도록 할 것이다.


예.

#include<stdio.h>

int main(void)
{
     union integreal{ int integer; double real;};
     union integreal x,*p=&x;
 
    x.integer = 10;
    p->real = 1.542354;

    printf("%d\n%lf\n",x.integer,x.real);

    p->real = 1.542354;
    x.integer = 10;

    printf("\n%d\n%lf\n\n",x.integer,x.real);

 return 0;
}


실행결과는 다음과 같다.

1666034994
1.542354

10
1.542354


공용체를 이용하면, 같은 데이터를 다른 싸이즈 단위로 접근하는 것이 가능하므로, 가령, 바이트단위나 비트단위로 선택적으로 접근하는 것 등을 가능케 한다.


형 재정의 ( typedef )

어떠한 데이터형으로 int를 가정해서 프로그래밍을 했다고 할때, 그 컨셉과 관련해 수없이 많은 int 타입의 변수를 도입하게 된다. 그런데 프로그래밍이 한참 진행되던 도중, int 가 아니라 long 으로 수정해고자 할 때, 일일이 찾아다니며 바꿔야하는 번거로움이 생긴다.

사실 위와같이 int 와 long 의 선택은 단지 컴퓨터적인 개념에 의해서 나눠졌을뿐, 컨셉적으로는 integer 로 동일하다. 이는 프로그램을 구상하는 관점에서도 int 를 쓸지 long 을 쓸지는 본질적인 문제가 아니다.

typedef 를 이용하면, 좀 더 컨셉적인 부분에 집중할 수 있다. typedef 는 #define 과 마찬가지로, 이식성과 수정효율성 역시 높여준다.


구문은 다음과 같다.

typedef type identifier;


#define 과는 달리, 문장이다. ( 따라서 ; 를 찍어준다.)
#define 과는 순서가 반대이다. typedef 는 일종의 data type renaming 으로 볼 수 있다.


#define 는   define A as B

typedef 는 rename A as B


따라서, 코드내에서는 #define 의 경우, B를 새로 정의한 A를 사용하고, typedef 는 A 를 새로 이름붙인 B를 사용한다.

type 은 단순히 int 뿐만 아니라 이미 어떠한 정의된 타입이 될 수 있으며, 한정자로 쓰이는 각종 키워드나 tag 도 포함하고, 하부구조를 갖는경우 그것도 허용한다.


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

예.
typedef int element;
...

element x,y;              // 변수 x, y 를 int 형으로 선언
...


나중에 typedef int element;  에서 int 만 double 로 바꾸면, 자동으로 x, y의 선언은 double형이 된다.


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

포인터형의 재정의

앞의 포인터 내용에서, 어떤 type의 포인터형을 type* 형으로 부른다고 했었다.  이것을 이용하면, 포인터 형도 typedef 를 사용해서 * 없이 선언할 수 있다.

예.
typedef int* intPtr ;         // int 형 포인터 형의 재정의
intPtr p, q ;                   // int형 포인터변수 p , q 의 선언

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

마찬가지로, 어떠한 한정자나 tag를 매번 붙이는 것이 귀찮을 경우에도 typedef 는 유용하다.


Declaration(선언)과 Definition(정의)
Declaration(선언) 과 Definition(정의) 를 구분하는 경우, memory 를 실제로 요청해서 할당받으면 definition 이고, 단지 대상의 type을 알리는 문장이면 declaration 이다. 참고로, 함수의 경우, 원형(prototype)의 선언 없이 정의하면, 정의와 동시에 선언된다.


Data Types (자료형)
메모리에 저장하고 꺼내는 데이터의 크기, 용도, 사용법 등의 정보를 뭉뚱구려서 data의 type이라고 한다.
primitive types (int, char, float, double, void) 도 있고, 그러한 타입들을 재료로 구성되는 타입도 있다.


Types in C
C 의 선언 형식은, 이름이 나타내는 대상에 대해, 어떠한 행위를 했을때, 결과적으로 어떤 primitive 값을 얻게 되는가 형식으로 되어있다.
(NOTE: C++ 의 경우는, type distinction 만 가능하면 여기저기 끼워넣은 내용이 많기 때문에, 이처럼 심플하고 일관되게 설명되지 않는다. 반면에, C 는 매우 심플하고 일관되게 설명이 가능하다.)

선언 대상은, 선언에 등장하는 첫번째 이름이고,
선언 type 은, 대상의 선언에 사용된 이름들, 값, 함수정의 block 등을 제외한 것이다.
(참고로, 보통 함수의 경우, 이런걸 signature 라고 하고, 보통 다른 언어에서는 overload 시 올바른 함수를 고르거나 할 때 사용된다.)

int a;

이름 a 는, 그대로, int 값을 준다.
type 은 int <-- 이름 (identifier) a 를 빼고 남은 부분.


int *a;

이름 a 는, 전위 * 에 의해, int 값을 준다.
type 은 int *                                                                            <-- 마찬가지로 이름 (identifier) a 를 빼고 남은 부분.


int a[7];

이름 a 는, 후위 [ ] 에 의해, int 값을 준다.
type 은 int [ ]


int *a[7];

이름 a 는, 후위 [ ] 후, 전위 * 에 의해, int 값을 준다. (주의: 연산자 우선순위)
type 은 int * [ ]


int (*a)[7];

이름 a 는, 전위 * 후, 후위 [ ] 에 의해, int 값을 준다. (NOTE: 괄호를 통한 연산자 우선순위 변경)
type 은 int (*)[ ]


int a( int b, int c);

이름 a 는 후위 ( int ) 에 의해, int 값을 준다.         --> 즉, arg : int 한개 pass, 호출 결과 int
type 은 int (int)


int a( int b, int c);

이름 a 는 후위 ( int, int ) 에 의해, int 값을 준다. --> 즉, arg : int 2개 pass, 호출 결과 int
type 은 int (int, int)


int a( int *b, int c[ ] );

이름 a 는 후위 (int *, int [ ]) 에 의해, int 값을 준다. --> 즉, arg : int* 와 int [ ] pass, 호출 결과 int
type 은 int( int*, int[ ] )


int *a( int b);

이름 a 는 후위 (int) 후, 전위 * 에 의해, int 값을 준다. (NOTE: 연산자 우선순위)
type 은 int *( int )

int (*a)( int b);

이름 a 는 전위 * 후, 후위 (int) 에 의해, int 값을 준다. (NOTE: 괄호를 통한 연산자 우선순위 변경)
type 은 int (*)(int)



콤마를 통한 일괄선언
여러개를 동시에 선언할 수 있는데, 그냥 여러줄에 쓸 것을 한줄에 쓴 것에 불과하고, 그 방식은 다음의 예와 같다.

다음의 선언은

int *a( int b), c, d[7], (*e)[3];


다음과 같다

int *a( int b);
int c;
int d[7];
int (*e)[3];




참고: Old Form of Function Definition before ANSI C
아래 그림에서, 
int F1(x,y) int x; int y; { ... } 는 int F1(int x, int y) { ... } 의 old form 으로, Backward Compatibility 를 위해, ANSI C 에서 사용가능하지만, 쓰지 않도록 한다.


라인단위 처리에 있어 어떠한 경우 해당 라인에서 복잡한 구조를 갖는 처리를 해야할 수 있다. 이러한 요청으로 많은 프로그래밍 언어들은 라인단위를 블락단위로 확장시키는 방식을 가능케한다. C 에서는 중괄호 { } 를 사용한다.

블락은 해당라인에 포함된 내부구조로 인식될 수 있으며, 보통 중첩(nest) 을 허용하고, 내부스코프를 갖으며, 해당라인에 귀속된다.

가령, 아래와 같은 상황에서 라인 8,9,10 은 라인 8 에 귀속되는 하위 혹은 내부구조라고 볼 수 있다.

5    a line

6    a line

7    a line

8    {    a subline

9        a subline

10    a subline }

11    a line

12    a line


블락은 기본적으로 자체 스코프를 갖는다. 즉, 중괄호 내의 영역은 일차적으로 같은 스코프 영역이다. 중첩된 경우 하위의 블락을 벗어나는 스코프는 우선적으로 바로 자신의 상위에 있는 블락을 바라보며 (scoping), 이는 계층에 대해 연쇄적으로 작용한다.

{

상위블락영역

{

하위블락영역

}

상위블락영역

}


[C언어] #001 토큰 (tokens)

COM2008. 1. 11. 23:49 |

프로그래머가 코드를 짜면, 컴파일러는 일차적으로 해석을 해야한다. 그러나 컴퓨터는 계산기일 뿐이므로 "이해"라는 것을 하는것은 불가능하다.

물론, AI 에 대한 논란의 여지는 있다. 그리고 그것은 결국 인간의 인텔리젼스는 무엇인가로 귀착된다. 단지 뇌의 신경물질 전달의 결과로 볼것이냐 혹은 더 상위의 개념 ( 가령 영혼이라든가... ) 을 전제하느냐의 문제이다. 전자라면, AI는 매우 가능한 것이며, 이미 진행되고 있는 그것이다. 반면에 후자라면, AI는 이해라기 보다는 이해를 하는 것처럼 보이게 만들기위한 인간의 노력일 뿐이다.

궁극에가서는 같은 것을 놓고, 혹자는 AI라고 칭하고, 혹자는 조금더 진보된 계산기일 뿐이라고 칭할 것이다. 그러면 AI 라고 칭하는 자들은 , 인간이 그 이상도 그 이하도 아니므로 당신말도 맞다 라고 할 것이다.

여튼, 컴파일러는 소스코드를 모종의 기본단위로 쪼개고, 오브젝트 코드로 바꾸는데, 그러한 쪼갬의 단위를 token 이라고 한다.


ANSI C 에서는 6가지 종류가 있고, 다음과 같다.

keyword
identifier
constant
string
operator
punctuation


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

키워드는 C언어에서 미리 예약된 토큰이고, ANSI C 에서는 32 개가 있고, 다음과 같다.

auto , break , case , char , const , continue , default , do , double , else , enum , extern , float , for , goto , if , int , long , register , return , short , signed , sizeof , static , struct , switch , typedef , union , unsigned , void , volatile , while

여기에 컴파일러마다 추가적으로 지정할 수 있다. 그러나 주의할 것은 키워드 외에도 실제적으로 사용할 수 없는 단어들은 많다는 거다. 특히, 자주쓰는 라이브러리에 정의된 상수나 함수들은 피해야 한다.


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

identifier 는 어떤 대상을 identify 해주는 녀석이다. 가령, 함수명이라든가, 변수명 등을 예로 들 수 있다.  우리말로는 식별자 라고 한다.

예를들면...
...
int a , b , sum ;
callback();
...


에서, a 도 identifier 이고, b 도 , sum 도 , callback 도 identifier 이다. 반면, int 는 keyword 이다.
() 는 operator 이고, ; 는 punctuation 이다.

identifier 에 사용가능한 문자는 문자, 숫자, 그리고 underscore  ( _ ) 이다.


identifier 의 구문규칙을 살펴보면 다음과 같다.

identifier  ::=  { letter | underscore }1 { letter | underscore | digit }0+
underscore  ::=  _

즉, 숫자로 시작하는 것은 허용하지 않는다.

underscore 로 시작하는것도 구문상 문제가 되지는 않으나, 시스템에서 정의된 많은 식별자들이 _ 로 시작하도록 셋팅되어있기때문에, 시스템 프로그래머가 아닌경우, _ 로 시작하는것은 권장하지 않는다.

stdio.h 안에 _iobuf 등을 예로 들수 있다.



Backus-Naur Form (BNF) 은 고급언어를 기술하는 표준형식이다. (나도 잘 모르므로.. ) 간단히 언급하면...

::= 는 definition 정도의 의미가 되겠다.

|  는  OR 정도의 의미가 되겠다.


예를 들면...

digit  ::=   0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9

와 같이 digit 를 정의할 수 있다.


{ } 를 이용하여 대상에 대해 subscript 로 몇 몇 부가설명을 덧붙일 수 있다.

{ }1      :    하나만 선택
{ }0+     :    0 번 이상 선택 또는 반복.
{ }1+     :    1번 이상 선택 또는 반복.

{ }opt    :    옵션 항목


예.


alphanumeric_string ::= { letter_or_digit }0+
letter_or_digit ::= letter |
digit
letter
::= lowercase_letter |
uppercase_letter
lowercase_letter
::= a | b | c | ... | y | z
uppercase_letter ::= A | B | C | ... | Y | Z
digit ::= 0 | 1 | 2 | ... | 9


위와같이 alphanumeric_string 을 정의하면, 숫자와 문자로 이루어진 문자열을 정의를 할 수 있다.
이 정의는 아무것도 없는 즉, 널문자열도 포함한다. 왜냐면 { }0+ 이기 때문이다.
참고로, 정의하고자 하는 것은 이탤릭체로 쓰고, 더이상 정의가 불가능한 최하위개념은 그냥체로 쓴다.


Visual Studio 2008 의 MSDN 을 쓸때, 검색어가 파랗게 반전되어 있는게 짜증날땐, 옵션에서 해제하면 된다.

사용자 삽입 이미지




이건 에뮬레이터만 있는거임.
롬 파일들은 따로 올림.

사용자 삽입 이미지

사용자 삽입 이미지

사용자 삽입 이미지


오픈 프로젝트나 뉴 워크스페이스 만들때도 같은 오류가 나는데, 서비스팩 6 을 설치해주면 해결되는 경우가 있고(나는 그렇게 해결이 되었다.) 
서비스팩 6 : 아래 분할압축파일을 모두 받아서 한 폴더에 넣고 Vs6sp6.alz 압축풀면 됨. 그안에 setupsp6.exe 를 실행하면 서비스팩6이 설치됨.

Vs6sp6.alz

Vs6sp6.a00

Vs6sp6.a01

Vs6sp6.a02

Vs6sp6.a03

Vs6sp6.a04

Vs6sp6.a05






만약, 그래도 안되면 이럴때 다음 파일(출처: 어디서 퍼왔던건지는 기억이 안남, 검색해보면 나옴)을 C:\Program Files\Microsoft Visual Studio\COMMON\MSDev98\Bin 에 덮어씌우면 일단 해결된다. 대신 군데군데서 약간의 문자깨짐이 보이지만 별 문제는 안됨.

devshl.dll




사용자 삽입 이미지

스페인 내란에 관한 영화인데, 영화 내내 다리하나 부술려고 용을 썼던거 같은데, 뭐 볼만하다. 영화에서 종은 맨 마지막에 한 번 등장한다. 댕~

암튼, 스케일만 따져도 바람과 함게 사라지다 나 닥터 지바고에 비할 영화는 아니다.

역시 1943년으로는 저장이 안된다. 흠..1970년 부터 되네..
사용자 삽입 이미지

몇 번을 봐도 결코 질릴 수 없는 영화.  영화음악도 끝장임. 그리고 남북전쟁하면 떠오르는 음악은 역시 딕시랜드.

빠라 밤 밤 바라바라 빰 빰 빠~밤 빰 빰 빠~라 빠~라 빠라라라라~ 빠라밤~ 빠라밤~ 빠바밤~

음. 1939 년 으로는 저장이 안된다. ㅡㅡ;

 
바흐, 이탈리안 콘체르토. BWV 971. F장조. 3악장. 프레스토.
이번 대회는 레베루가 -_- 높군요... ( 과제곡인 모차르트 K310 대신 바흐를 ... )

전악장 듣기 (굴드)






모짜르트 피아노 소나타 8번 K.310. A단조. 3악장. 프레스토.

 슈헤이 : 뭐하는 거지?
             왜 멈춘거야, 한군데도 틀리지 않았는데...

카이 : 같이가자! 숲에.

아줌마 : 뭐야 이건 모차르트가 아니지 않아 !?

이거 녹음을 아쉬케나쥐가 했다던데...

전악장 듣기 (브렌델)
수학자들 모으기 위해 출제된 문제가 숫자 대여섯개 주고 패턴알아내는거다. ( 수학자적으로 생각하자면 답은 무한개이고, 어떠한 것도 답이 될수 있음을 보일수 있으며, 따라서, 결과적으로 숫자 댓개가 어떤패턴인지는 관심이 없는 문제가 되겠다.)

더 어이없는건 답이 알파벳순서가 어쩌고 한다. 즉, 수학문제가 아니라, 일종의 넌센스 퀴즈다. 근데 이걸 알아내고 줘낸 좋아하는 녀석이, 며칠뒤에 골드바흐 추론의 증명을 잘표하시겠단다. 이때, 녀석이 가짜 수학자라는 것을 눈치챘어야 했다. 실제로 녀석은 나중에 사실은 뻥이었다고 고백한다.


사용자 삽입 이미지

골드바흐 추론을 증명했다는 넘도 섞여있는데, 저런 문제나 풀고 있다. 내는 놈이나 푸는 놈이나...
( 저 모레시계문제는 다이하드에서 나오는 물통 두개로 다른용량 얻어내는 문제와 같다. )


그리고, 문제의 답을 PDA에 입력하는데, 숫자로 딱 떨어지는 답도 아니고, 과정을 적어도 몇문장 이상으로 설명해야 하는 문제들이 있었다.

그것이 정답인지 분간해 내는 프로그램은 인공지능이냐? 이미 인간의 언어를 이해하는듯.

아무리 영화고, 수학에 관심없는 사람들한테까지 흥미를 이끌어내야된다고 해도, 이건 해도 너무한거다.
그리고 추리스릴러를 표방했으면, 어느정도 딱딱 맞아떨어지는 탄탄한 플롯은 가장 중요한 요소가 아닌가. 대중성을 위해 그것을 포기한거라면 애초에 장르를 코미디나 패러디 정도가 더 어울렸다.

한마디로 이 영화는, 온갖 허섭쓰렉이의 집합체로, 영화동아리 습작 급의 영화이다. 이런게 개봉작이라면 개슈레기라고 생각한다.
사용자 삽입 이미지

마냥 유쾌하고 코믹한 영화. 아무생각없이 막 재밌음.

특이사항 : 밥 샙이 나옴 ㅋㅋㅋ
Aram Khachaturian - Masquerade Suite

1. 왈츠 ★★
2. 녹턴
3. 마주르카 ★★★
4. 로망스
5. 갤럽


사용자 삽입 이미지




어렸을때 이거 100원 넣고 끝판깨면 주위에 애들이 구경했었다. 그뒤로 몇번 그와같은 일이 반복되자 내가 이거 하려고 하면 아저씨가 나와서 100원 주면서 가라고 했다.


첨부파일은 mame (=emulator) rom 파일이다. 아무버전의 mame 에뮬레이터를 구한후에, roms 폴더에 넣으면 됨.


아래는 본인의 플레이.

사용자 삽입 이미지

이거시 본인의 초절정 원코인 플레이!

사용자 삽입 이미지

최종스코어는 85만 7400 . 너무 빨리 깼는지 스코어가 별로 안나왔다.


피아니스트의 전설 이후, 또하나의 역작. 가슴에 남는 작품이니까 흉작 -_-

음악이나 듣자. 한동안 이곡들을 들으며 걸을 생각하니 행복하다.
- 사운드트랙 전곡 듣기 -
01. Opening
02. 脚踏車
03. 早操
04. 淡水海邊
05. 斗琴
06. 湘倫小雨四手聯彈
07. Ride With Me
08. 父與子
09. 情人的眼淚
10. First Kiss
11. 女孩別爲我哭泣
12. 晴天娃娃
13. 阿郎與阿寶
14. 與父共舞
15. 路小雨
16. The Swan
17. Flash Back
18. Secret (慢板)
19. angel
20. 小雨寫立可白 I
21. 小雨寫立可白 II
22. Secret (加長快板)
23. 琴房
24. Ending
25. 不能說的秘密


특히, .. secret...


이건 주걸륜이 과거로 갈려고 빨리치는 거....



-_- 쭝국어 몰른다해~  ( 위에것이 샤오위 버전이고, 아랫것이 주걸륜 버전인것 같다만... )
느리게 치면 20년 뒤로가고, 빠르게 치면 20년 전으로 간닷 !

암튼, 이건 악보...


못치니까 직접 들을 수 있다면 참 좋겠다.



피아노 배틀.
흑건을 백건으로 바꾸고, 왈츠 7번


그리고, 이건 주걸륜하고 샤오위의 연탄곡

Korngold - Concerto for Violin & Orchestra in D, op. 35


1악장. Moderato mobile
2악장. Romanze- Andante
3악장. Finale- Allegro assai vivace

- 바이올린 : 야사 하이패츠
사용자 삽입 이미지

다소 충격적인 영감을 줬던 영화.


" 지금 이 순간, 내 삶의 한 순간. 이 순간 순간이 누군가가 쓰고 있는 소설의 한 순간이라면?"

이거 생각하기에 따라서는 굉장히 엽기적이고 흥분되는 내용이다.




가령, 지금 내가 컴퓨터 자판을 두드리고 있는데, 사실은 이게 내 자유의지가 아니라, 어느 누군가가 세상 어느곳에선가 쓰고 있는 소설의 일부인 것이다.

작가는 다음과 같이 쓸 수 도 있다.

" 그는 흥분에 감싸인채, 어느 이상한 영화에 대한 평을 쓰느라 자판을 두들기고 있다. "



이러한 상상이 모든 사람에게 적용된다고 상상을 확장하면, 실로 엄청나게 복잡하고 거대한 새로운 시스템의 세상을 느낄 수 있게 된다.

그것은 메이트릭스와 유사한 또하나의 신세계가 될 것이다.