.-= 재미로 보는 C/C++언어 =-.
                                          시립인천대학교
                                          전자계산학과
                                          971125 OPG
                                          조경민
   ===========================================================

      안녕하세요 인천대학교 전산학과 97학번 조경민이라고 합니다.^^
     이 강좌(?)아니 이야기는 전문적인 C/C++에 가깝지 않다는것을
     미리 얘기 드리고 싶습니다. 제가 알고있는 작은 이야기를 재미(?)
     있게 쓴글이라고 보는게 낫겠군요 ^^; 물론 그렇다고 아주 틀린
     내용을 말하는 것이 아니므로... 나름대로 효율성이 있다는 것에
     대해서 얘기하고 싶습니다.
      물론 오타및 많은 틀린 부분이 있을거라고 믿습니다. 많은 조언
     부탁드립니다. 아참 이야기는 C/C++을 조금씩은 알고 계신것으로
     알고 쓰는 겁니다. (저도 조금밖에 모르니 광범위하게 쓰지 못한
     것이니 양해 바랍니다. ) 재미있게 읽으시면 감사하겠습니다.

     *) 재미있는 토막 얘기 꺼리로 진행하며 괄호 안에 해당하는 언어를
        너었습니다.

  ----------------------------------------------------------
   1. If 문의 조건 (C/C++)                         '98/03/14
   2. 웃기는 & Passing-by-reference (C++)          '98/03/14
   3. 뭐가 에러지!!!  (C++)                        '98/03/15
   4. const는 상수가 아니다 (C)                    '98/03/15
   5. const int *a 와 int * const a  ???? (C/C++)  '98/03/16
   6. void main vs. int main (C)                   '98/03/16
   7. Pointer 이야기         (C/C++)               '98/03/17
   8. 매개변수 생략????      (C)                   '98/03/17
  ----------------------------------------------------------


   1. If 문의 조건 (C/C++)   '98/03/14
   '`'`'`'`'`'`'`'`'`'`'`'`'`'`'`''`'`'`'`'`'`'`'`'`'`'

     if문은 아시다시피 조건이 참(1 또는 TRUE)이면 다음을 실행하고
     거짓(0 또는 FALSE)이면 else 다음 문을 실행합니다.  당연하지요...
     하지만 이렇게만 알고 있는것이 키워드 if 라면 다음은 아리송해집니다.
     (x는 int형 변수이고, ptr은 포인터형 변수, ch는 char형의 변수입니다.)

      if(x)
      if(!ptr)
      if(ch)

     위의 두 문장은 아래와 같은 표현입니다.

      if(x!=0)
      if(ptr==NULL)
      if(ch!='\0')

     아하 하는 분이 계시겠군요.
     아시는 분은 다 아시겠지만 키워드 if는 다음과 같이 정의되어있습니다.
     다음은 Turbo C++ 3.1의 도움말을 그대로 옮긴것입니다.

         if ( <expression> ) <statement1>
         OR
         if ( <expression> )<statement1>
         else <statement2>

         The value of <expression> is evaluated and
         if it is nonzero, then <statement1> is
         executed. In the second case, <statement2>
         is executed if the expression is 0.

     즉, <expressin>이 0이 아니라면 <statement1>을 실행하고
     <expressin>이 0이라면 <statement2>을 실행하라는 말입니다.
     따라서 처음의 짧은 방식의 코드를 쓸수 있는 것이었습니다.
     그리고 또한 이런 zero조건 검사 회피는 좋은 코드 습관이 될수 있습니다.
     이유는 간단하지요. 이미 if의 조건에 의해서 0인지 아닌지를 검사하기
     때문입니다. 그래서 다음과 같은 것은 쓸데없이 두번이나 0이 아닌지를
     검사하는 것입니다.

        if(x!=0)

     이는 다음과 같은 효율적인 방법입니다.

        if(x)

     참고로 위의 NULL과 '\0'은 각각 널포인터와 아스키 0번 문자에 해당하는데
     모두 컴파일시 정수 constant 0으로 대체가 됩니다. 하지만 의미상 정수상수
     0이 아니므로 혼동하면 에러가 발생하므로 알맞게 쓰는것도 좋은 습관입니다.

  2. 웃기는 & Passing-by-reference (C++) '98/03/14
  '`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'
    여기 또 재미있는 게 하나 있습니다. 그것은 참조자 &입니다.
    물론 C의 주소반환연산자는 아닙니다. C++로 넘어오면서 생긴 것인데요
    다음 재미있게 생긴 코드가 있습니다.

        int x,*p;
        int &y=x;
        p = &y;

     참조자( reference declaretor )는 위 코드에서 단한번 쓰여 졌습니다.
    바로 두번째 줄에서 말입니다. 물론 아래있는 &는 흔히 알고 있는 주소반환
    연산자 입니다.
     결론부터 말씀드리면, 참조자&를 통해 만들어진 변수 y는 변수 x를 참조하
    므로 y는 곧 x가 되는 것입니다. 따라서 세번째 줄 &y에 의해 불려진 주소는
    곧 x의 주소(&x)와 같은 값을 갖게 되는 것이지요.
     그런데 참조자 &는 불행하게도(?) 변수 정의할때와 lvalue로만 쓰일수 있습
    니다. 따라서 변수 선언시 &가 붙었으면 이건 참조자구나.. 하고 쉽게 알수
    있습니다.
     그러나 의문이 생깁니다. 왜. C++에서는 할일없이 이런 웃기는 것을 만들었
    을까. 그것은 함수 호출후, 매개변수로 쓰거나 변수 선언시 스택을 전혀 쓰지
    않는다는 점에 있습니다.메모리 낭비가 없겠지요? 그게 뭐야. 하는 분들울 위해.
    아참 아래 코드는 C++양식입니다. C에서는 안돼겠지요? :)

        #include <stdio.h>

        struct int_vac{
                int data[100];
        };

        // Call by Value 방식의 매개변수
        void call_by_value( int_vac d)
        {
            int index;

            for(index=0;index < 100;index++)
            {
                d.data[index] = 0;
            }
        }

        // Call by Reference 방식의 매개변수
        void call_by_reference( int_vac* d )
        {
            int index;

            for(index=0;index < 100;index++)
            {
                d->data[index] = 0;
            }

        }

        // Passing by Reference 방식의 매개변수
        void passing_by_reference( int_vac& d )
        {
            int index;

            for(index=0;index < 100;index++)
            {
                d.data[index] = 0;
            }
        }

        int main()
        {
            int_vac a;

            a.data[1] = 2;
            call_by_value(a);
            printf("%d\n",a.data[1]);

            a.data[1] = 2;
            call_by_reference(&a);
            printf("%d\n",a.data[1]);

            a.data[1] = 2;
            passing_by_reference(a);
            printf("%d\n",a.data[1]);

            return 1;
        }


     보시면 아시겠지만 위의 세가지 함수 모두 같은 일을 합니다.
    int_vac라는 data배열을 갖은 스트럭처를 받아 함수로 넘겨 0으로 초기화
    하는 것입니다. 출력물은 아래와 같습니다.

        2
        0
        0

     처음과 두번째함수는 보아왔기 때문에 별 문제 없습니다.

     Call-by-Value : 중요한것을 매개변수안의 값(Value)만으로 보고 이를
                     같은 크기의 양만큼의 스택메모리상에 복사 적재(팝)시키고
                     함수안에서는 이 스택메모리상의 복사된 객체를 이용한다.
                     함수 종료시 스택메모리에서 푸쉬를 하여 클리어한다.
                     따라서 매개변수로 온 자체는 어떤 변화도 없다

     Call-by-Reference : 매개변수를 수정혹은 참조를 위한 실제 객체의 주소
                     포인터를 얻어와서 이를 스택메모리상에 포인터변수로서
                     넘어온 객체의 주소를 적재시키고 함수안에서는 스택상의
                     주소를 이용해서 데이타 접근을 한다.
                     함수 종료시 스택메모리상의 포인터변수를 푸쉬클리어한다.
                     따라서 매개변수로 온 포인터 변수를 통해 실제 객체에 변화
                     가 있을수 있다.

     Passing-by-Reference : 매개변수를 참조자로 썼기때문에 매개변수 실제 객체
                     는 곧 매개변수와 같다. 따라서 스택메모리상에 아무것도 복
                     사,팝 하지 않으므로 메모리 낭비가 없으며 효율적이다.
                     함수안에서 매개변수 이용은 곧 실제 객체 이용이다.
                     함수 종료시 스택메모리상에 올리지 않았기에 매개변수에 의
                     한 푸쉬가 없다. 이또한 매개변수가 실객체와 같으므로 실객
                     체의 변화가 있을수 있다.

    매개변수에서 참 재미있는 일이 벌어 진것이군요 :)
    그렇타면 이 요물은 리턴할때도 재미있는 일을 만들수 있겠지요???
    다음 또 재미있는,그러나 쓸모없는, 코드좀 볼까요?

        int x;

        int & x_value()
        {
            return x;
        }

        int main()
        {
            x = 4;

            x_value() = 2;
        }

     쿠쿠... 웃기는 코드이네요 :) x_value함수는 리턴을 참조자를 썼기때문에
     자체가 lvalue가 되는 것입니다. 즉, x_value()는 x와 같겠군요
      더 재미있는 코드요???

        int x,y;

        int & _value(int & d)
        {
            return d;
        }

        int main()
        {
            x = 4;

            _value(y) = _value(x);
        }

      _value()함수는 참조자 매개변수를 이용했으므로 함수안의 d는 실매개변수
     자체가 되는데 이것은 또 리턴할때도 참조자이니 다시 lvalue가 되는군요
     참으로 이토록 쓸모없는 코드가 세상에 또 있을까요?
      그렇게 보면 매개변수사용으로 볼때 참조자는 딱 좋은데.... 리턴에 쓰기에
     는 정말 안 좋아 보이네요? 쓸데 없고요.... 그렇지만 다음 경우 참조자는
     그 어떤 경우보다 효율적이며 강력함을 자랑합니다. 또한 이를 위해서 참조자
     가 생겼다고 보아도 과연은 아닙니다.
      다음은 클래스의 연산자 재정의 부분입니다.

     #include <stdio.h>

     class CPoint {
     public:
        int x,y;

        CPoint(int a,int b) {x=a;y=b;}
        ~CPoint()()

        // Operator
        CPoint operator+(CPoint p1)
        {
            x += p1.x;
            y += p1.y;

            return CPoint(x,y);
        }
     };

     int main()
     {
        CPoint a(1,5),b(2,3);

        a = a + b;

        printf("%d %d",a.x,a.y);

        return 1;
     }

     결과

        3 8

     차근 차근 볼까요? 아... operaor+ 는 연산자 재정의( operand overload)
    입니다. C에서 부터 있어왔던 얘기지요.. :)
    연산자 + 재정의를 통해서 CPoint형간의 +연산이 가능해 지겠군요.
    값을 적절히 받아와서 이를 다시 생성자를 이용해서 건내줍니다.
     그러나 매개변수인 피연산 자료 p1을 위해 스택메모리를 사용했으며 리턴시
     CPoint 생성자를 이용해서 꽤 안 좋아 보이는 코드입니다.

     #include <stdio.h>

     class CPoint {
     public:
        int x,y;

        CPoint(int a,int b) {x=a;y=b;}
        ~CPoint()()

        // Operator
        CPoint & operator+(CPoint &p1)
        {
            x += p1.x;
            y += p1.y;

            return *this;
        }

     };

     int main()
     {
        CPoint a(1,5),b(2,3);

        a = a + b;

        printf("%d %d",a.x,a.y);

        return 1;
     }

     결과

        3 8

      참조자 &를 이용해서 매개변수(피연산자)를 얻어 왔으므로 스택을 쓰지 않
     습니다. 또한 계산한후 바로 자기 자신을 리턴하므로 효율적입니다.
     (this는 클래스 맴버함수에서 자기 자신을 가리키는 포인터입니다.)
      그러나 주의해야 할것은 함수시 매개변수불러오는 방식을 참조자 &와 포인터
     를 너무 혼용하여 쓰면 헷갈리는 수가 많습니다. 하나로 통일해서 쓰는것이
     좋을듯합니다.
      다음을 보면요

      #include <iostream.h>

      struct int_vac{
                int data[100];
      };

      void print(int_vac& i)
      {
        cout << i.data<< endl;
      }

      void print2(int_vac* i)
      {
        cout << i->data<< endl;
      }

      int main()
      {
        int_vac s;
         :
        s.data에다가 값을 넣습니다.
         :
        print(s);      // 참조자 매개변수에 맞게 넣습니다.
        print(&s);     // 포인터 주소를 매개변수로 넣습니다.
        return 1;
      }

      이렇게 어떤함수는 포인터를 어떤함수를 참조자로 쓰면 꽤나 헷갈리게
     되지요. 아참. 참조자를 통해서 매개변수를 넘기게 되면 값이 변동될수
     있는 일이 발생하잖아요. 그럴샔는 매개변수 넘겨올때 const를 붙이면
     이런 사태를 막을수 있지요.

      void print(const int_vac& i)
      {
        //  i.data 의 내용을 변동할려고 하면 에러가 납니다.
        cout << i.data<< endl;
      }


    3. 뭐가 에러지!!!  (C++)     '98/3/15
    '`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'

      int & add( int a, int b )
      {
           int result;
           result = a + b;
           return result;
      }

       뭐가 에러인지 단숨에 보인다면 요기 글은 그냥 넘어가세용^^;
      이런건 가끔씩 범하게 되는 오류인데요 참조자가 좋다니깐 맹목적으로
      쓰다가 이런 에러두 생깁니다. -_-;
       결론부터 말씀드리면, 위의 함수는 건내줄수 없는 변수를 건내려고
      했습니다! 즉 지역변수인 result는 함수 블럭이 끝나면 곧바로 지워지는데
      이를 리턴할라구 했느니깐요. 에러가 납니다.
       그럼 리턴 변수를 포인터로 하면요?

      int * add( int a, int b )
      {
           int result;
           result = a + b;
           return &result;
      }

       실행에는 문제가 없자먼 건내줘서는 않될 지역변수의 주소를 건내려했으므
      로 예상치 못한 이상한 값이 리턴 욉니다.
       이런 실수는 하지 맙시다


    4. const는 상수가 아니다 (C)  '98/3/15
    '`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'

      이 이야기는 C에서의 이야기입니다. 그럼.
      다음 이야기는 어디까지나 책을 읽고 느낀 나의 생각이므로 다른 사람과
      입장이 틀릴수 있습니다.
      C/C++에서는 const 라는 키워드를 이용해서 상수를 정의 했습니다.

        const my_age = 21;

     (const 다음 자료형을 안쓰면 int형으로 디폴트 처리후 컴파일이 됩니다. )
      이것은 너무나 자명하게도 상수 a는 5이며 더이상 바뀔수 없는 읽기
     전용의 자료를 의미합니다. 그렇다면 위의 의미를 새겨보면....

        내 나이는 영원히 21살이다.

      후.. 영원한 젊음이군요. 대단한 const 키워드입니다.
     그런데 과연 그럴까요?
      사실, const는 불완전한 상수 표현에 속합니다. 이는 무슨 말이냐고요?
     우습게도 상수로 믿었던 const형 상수(?) a는 값이 변할수 있다는 얘기
     입니다. 간단하게 다음을 봅시다.

       *(int *)&my_age = 81;

      이렇게 써버린다면. 내 나이는 금세 81... 쭈그렁 할아버지가 됩니다.
     슬프죠? 웃기다고요? 아닙니다. 이건 슬픈 현실입니다. 실제로 C 컴파일
     하면 my_age는 81이라는 값을 갖게 됩니다. 웃기죠?

      결국 하고 싶은 말이 뭐냐구요? const말고 #define 이라는 다른 상수 표현
     매크로가 있습니다. 이것을 쓰는것이 낫다는 것입니다. 이것을 씀으로써
     컴파일동안 상수로 취급했던 것이 변하지 않는다는 것을 완벽히 보장받을수
     있으며 상수 폴딩(Constant Folding)이라는 컴파일시간의 효율도 얻을수 있죠

      *상수 폴딩 : 대부분의 컴파일러는 컴파일시간을 줄이기 위해 상수폴딩이라는
                   것을 쓰는데 만일 const로 MAX를 30으로 선언했다면

                    MAX + 1

                   이 문장은 30 + 1로 치환되어서 다시 계산을 하게 되지만
                   #define 을 이용해 MAX를 30으로 선언했다면 위의 문장은 곧바로
                   31로 치환되어(상수 폴딩) 컴파일시간을 단축할수 있다.

      상수 폴딩이라는 것도 참 재미있는 거 같지요? 그럼 이를 이용한 활용예를
      봅시다.

       3 * a * 5

      이것을 상수폴딩이라는 관점에서 다시 본다면 이렇게 쓰면 효율적입니다.

       a * 3 * 5

      컴파일시 3 * 5 는 15로 곧바로 치환이 되어 처리가 되지요 그런데 대부분의
      컴파일러는 상수폴딩을 컴파일러가 알아서 해주게 됩니다. 물론 ANSI C에서는
      프로그래머 모르게 임의로 위치를 변경하는것은 위험할수 있다고 보아서
      컴파일러가 임의로 위치를 바꾸는것을 제한했지만, 대부분의 컴파일러는 효율
      을 위해서 배째라 식으로 임의의 상수 폴딩을 한다고 그럽니다. -_-;


    5. const int *a 와 int * const a  ???? (C/C++)  '98/3/16
    '`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'

        const를 이용한 두줄의 선언이 있습니다.

         const int *p;
         int * const p2;

        둘이 비슷해 보이지요? 그러나 하는 일은 정 반대랍니다.
       헷갈리죠... 저두 많이 헷갈립니다. :)
        그냥 먼저 정하고 보지요? 약속 처럼.

        const int *p  - int * 즉 정수 포인터로 선언하고 p는 포인터
                        const *p즉 내용물을 고정(const)합니다.
                        대신 주소를 변동할수 있습니다.

                          p = &val;  (o)     *p = 12;  (x)

        int * const p2 - int * 즉 정수 포인터로 선언하고 p2는 포인터
                        const p2즉 주소를 고정(const)합니다.
                        대신 내용물을 변동할수 있습니다.

                          p = &val;  (x)     *p = 12;  (o)

        이렇게 보면 조금이나마 편하지 않을까 합니다. :) 아닌가?
       그리 많이 쓰지는 않지만 함수에 매개변수로 넘어온 매개변수들의
       내용이나 주소를 고정하고 싶을때 const를 씁니다.

        아참. 이왕 이것을 알았으니 const의 값을 바꿀수 있는 다른 방법
       다음을 보세요

        const int my_age = 12;

        int * const p2 = &my_age;

        *p2 = 81;

        마지막으로 다음 처럼 써있으면 무얼 말할까요?

        const int* const p;

        의심할여지 없이 주소와 값을 고정하는 것이겠네요 ^_^


     6. void main vs. int main            (C) '98/3/16
     '`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`

        void main()
        {
            printf("Hello world!");
        }

         정겹기 그지없는 Hello world! 출력 프로그램입니다. 아마도
        처음 씨를 배울때 한번씩은 해보셨을것으로 봅니다. 저도 역시
        이 프로그램을 실행하고선 C를 다 배운것 처럼 즐거워 했던 적이
        있습니다. :)

         그런데 조금은 잘못(?)된 곳이 있다고 합니다. 바로 main의
        리턴 선언 부분인데요. void 맞지 않나?? 하시는 분들이 계실것
        같은 분을 위해서 말씀드리는 것입니다.
         컴파일러는 원래 main를 아래 두가지 형태만 취한다고 합니다.

           int main()

           int main(int argc, char* argv[])

          뭐 Unix용 C에서는 파라미터가 하나 더 붙어서
          int main(int argc, char* argv[], char** environ)
         인가라고 하지만..(세번째꺼 글짜 틀릴지두 모르겠네요. Unix
         한번두 안 사용해 봤습니다. 어쨋든 OS를 알아오는 매개변수라는군요)
          어쨋든 그것은 지금 얘기하는것과는 관계가 없으므로 생략하고요
         위의 두가지 형태를 지원한다고 합니다.
          아참 이얘기를 할라면 꼭 알고 넘어가야 하는 것이 있습니다.
         스타트업 코드(Start Up)라는 것이지요. 프로그래머가 만든 프로그램
         을 실행하기 전에 main으로 넘어온 argument 들(argc,argv) 처리와
         여러 프로그램 실행을 위한 잡다한 일을 하는것을 말하는데요
          스타트 업 코드는 저도 그리 잘 아는 부분이 아니기 때문에 인용을
         조금 할까 합니다. 제가 전에 스타트업 코드가 뭔지 몰라 고민할때
         질문에 아주 잘 가르처주신 분의 말씀입니다.

         "   스타트업 코드란 어떤 컴파일러가 메인 루틴을 시작하기
           이전에 여러가지로 준비해주는 부분을 가리킵니다.
           말그대로 start-up 코드죠.  초기화코드라고보시면 됩니다.
           대부분의 컴파일러마다 자체의 스타트업 코드를 이용하고
           있고, 어셈블리 프로그래머들도 나름의 자신의 스타트업
           코드를 이용하는 경우가 많습니다.

            그러나 C 컴파일러를 제외하곤 모든 컴파일러들이 스타트업
           코드를 따로 소스로 제공하는 경우가 없습니다.  이건 역시
           C만의 유연성의 일부라고 볼수있겠죠.  쓸데없이 길고 느린
           스타트업 코드를 자신의 구미에 맞게, 또 자신이 만들고 있는
           프로그램의 성격에 맞게 고칠수 있는 기회를 제공하니까요..

            디버거등을 이용해서 보시면 C의 main 부분으로 가기전에 많은
           루틴을 거치는 것을 보실수 있습니다.  이 루틴들은 필요한 인터
           럽트들의 후킹, 셋팅,argc, argv를 전달하기 위한 준비, 환경변수
           셋팅, 스택 준비등의 여러가지 역할을 합니다.  이렇게 본체가 되
           는 루틴을 시작하기 전에, 프로그램이 잘 실행될수 있도록 앞에서
           미리 준비해주는 루틴들을 스타트업 코드라고 합니다.  스타트업
           코드를 끝마치면 C에선 main루틴으로 점프하죠. 윈도우용 프로그램
           에선 WinMain을 이용하면 됩니다.                              "


     7. Pointer 이야기   (C/C++)      '98/03/17
     '`'`'`'`'`'`'`'`'`'`'`'``'`'`'`'`'`'`'`'`'`'`'`'`'`'`''`'

       포인터는 많이들 보셨을겁니다. 죄송하지만 그냥 읽을라면 읽으시고요
      그렇게 색다르고 재미있는 특이한 내용이 아닙니다. 더이상 특이한 것을
      알지 못해서. 씁니다.. 죄송합니다. -_-

       포인터는 아시다시피 객체의 주소를 너을수 있는 즉. 주소를 변수로갖을
      수 있는 거지요.


     8. 매개변수 생략 ???? (C/C++)        '98/03/17
     '`'`'`''`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`'`
       C/C++에서는 함수의 인자 즉 매개변수를 생략할수 있습니다.

        #include <iostream.h>    

        void ma(int c,int a=0)
        {
         cout << c << a << endl;
        }

        int main()
        {
            ma(2);     // 두번째 인자인 a는 매개변수 생략되어
                       // default로 0이됩니다.
            ma(5,2);
        }

        매개변수를 다 생략 할수도 있겠죠?
    
        void ma(int c=0,int a=0)

        이렇게 하면

        ma(); 라고만해도 c,a는 디폴트로 0이 됩니다.

        그러나 매개변수 생략은 오른편에만 쭉있어야지 건너 띈것은 안됩니다.

        void ma(int a,int b=0,int c)

        이런 형식은 오류가 납니다.
        
          

'KB > C/C++' 카테고리의 다른 글

콤마에 대해서  (0) 2004.03.19
예전 콜링 컨벤션  (0) 2004.03.19
생성자 계승  (0) 2004.03.19
반올림하기  (0) 2004.03.19
2차원 배열을 억세스하는 포인터 여러 이용 사례  (0) 2004.03.19

+ Recent posts