본문 바로가기

C / C++ / Win32 / MFC

왜 C/C++인가?

본 포스트는 2006/8/12 작성되었습니다.

C/C++를 사용해 온지 15년째 접어 들고 있습니다.

그 옛날 Turbo C를 시작하여 UNIX  C, Visual C++에 이르기까지 그 동안 C/C++를 사용해 오면서 C/C++를 사용하면 사용할 수록 정말이지 이만큼 개발자로 하여금 자유로움과 유연성을 제공하는 언어가 또 있을까 하는 생각을 자꾸 하게 됩니다. 안정성과 속도가 중요시 되는 네트워크 서버 시스템에서 부터 화려함과 생동감을 강조해야 하는 멀티미디어와 복잡한 여러가지의 알고리즘들을 연관성 있게 구현하고 다양한 인터페이스를 제공해야 하는 Core 엔진 개발에 이르기까지 주어진 프로젝트에서 강조되는 부분을 개발자 마음대로 쉽게 적용할 수 있기 때문입니다. 물론 제가 C/C++ 골수분자이기 때문에 다른 언어에 대해 깊은 지식이 없는 상황에서 이런 말을 한다는 것 자체가 편협된 발언일 수 있음을 부정하지 않겠습니다. 그러나 제가 이 글을 통해 말하고자 하는 것은 C/C++의 유연성을 말하고 싶은 것이지, C/C++만이 최고의 언어이며, 다른 언어들은 C/C++에 비해 열등하다는 내용은 결코 아닙니다.

 

C/C++가 개발자에게 폭 넓은 유연성을 제공하는 이유는

첫째, 시스템에 대해 native 하기 때문에 빠른 속도로 시스템의 깊숙한 부분까지 제어가 가능하다는 점과,

둘째, C++에서 지원되는 객체자향 개발을 이용하여 수 많은 기능들이 복잡하게 얽혀야 하는 대규모 프로젝트에도 그 고유한 프로세스 속도를 별로 잃지 않으면서(C에 비해 속도차이가 전혀 없는 것은 아닙니다.) 체계적으로 개발과 업그레이드가 가능하다는 점,

그리고 마지막 세째로 C/C++에서는 함수 단위로 구성된 절차 중심의 개발과 객체지향 개발 중 어느 한쪽만을 고집하지 않고  모두 다 받아 들인다는 것입니다.

 

저는 이처럼 C/C++가 가지는 포용성에서 오는 유연성과 그 유연성으로 C/C++로 프로젝트 개발이 얼마나 즐거운 작업인지 이야기하고 싶습니다.

 

 

 

C/C++에서는 goto를 절대로 사용해서는 안된다?

 

저는 C언어를 공부하기 전에 BASIC과 FORTRAN, COBOL 등을 공부한 경험이 있습니다.

지금 생각해보면 그 언어들을 공부하면서 if... else... 문과 for... next와 같은 반복문, 그리고 goto 문 등으로만 프로그래밍을 했던 것 같습니다. 코드가 조금만 길어져도 goto를 쫓아가다 보면 정말로 눈이 돌아가고 정신이 없어 집니다.

그러다 C언어를 공부하면서 goto 문은 가급적 사용해서는 안되는 이유를 발견하고는 무릎을 탁 친적이 있었습니다. 어떤 책에서는 goto를 절대로! 절대로! 사용하지 말라고도 나와 있었습니다. 그래서 한 동안 저는 그 지시에 충실히 따랐습니다.

그러나 어느 순간 goto를 절대로 사용하지 않는 것이 얼마나 어리석은 코딩인지 깨달았습니다.

다음의 코드를 예로 들어 보겠습니다. COM을 이용해보신 개발자라면 아래의 코드에 익숙할 것입니다. 아래의 세 개 함수는 모두 동일한 기능입니다. 다만 foo1()과 foo2()는 goto를 절대로! 사용하지 않은 케이스이며 foo3()함수는 가급적! 함수 내부에서만 goto를 사용한 경우입니다.

여러분들은 어느 코드가 마음에 듭니까? 저는 개인적으로 foo3() 이 마음에 듭니다.

MSDN에서 COM을 사용하는 샘플코드에서도 foo3()과 같은 코드를 많이 찾아 볼 수 있을 것입니다.

 BOOL ClassObj::foo1()
{
   ClassObj1                   *c1 = NULL;
   ClassObj2                   *c2 = NULL;
   ClassObj3                   *c3 = NULL;

   c1 = this->GetObject();
   if(!c1)
      return FALSE;

   c2 = c1->GetObject();
   if(!c2)
   {
      c1->Release();
      return FALSE;
   }

   c3 = c2->GetObject();
   if(!c3)
   {
      c2->Release();
      c1->Release();
      return FALSE;
   }

   //
   // Processing...
   //

   if(c3)
      c3->Release();
   if(c2)
      c2->Release();
   if(c1)
      c1->Release();

   return TRUE;

}

BOOL ClassObj::foo2()
{
   ClassObj1                   *c1 = NULL;
   ClassObj2                   *c2 = NULL;
   ClassObj3                   *c3 = NULL;
   BOOL                        retval = FALSE;

   c1 = this->GetObject();
   if(c1)
   {
      c2 = c1->GetObject();
      if(c2)
      {
         c3 = c2->GetObject();
         if(c3)
         {
            //
            // Processing...
            //

            retval = TRUE;
         }
      }
   }

   if(c3)
      c3->Release();
   if(c2)
      c2->Release();
   if(c1)
      c1->Release();

   return retval;
}

 

BOOL ClassObj::foo3()
{

   ClassObj1                   *c1 = NULL;
   ClassObj2                   *c2 = NULL;
   ClassObj3                   *c3 = NULL;
   BOOL                        retval = FALSE;

   c1 = this->GetObject();
   if(!c1)
      goto Fail;

   c2 = c1->GetObject();
   if(!c2)
      goto Fail;

   c3 = c2->GetObject();
   if(!c3)
      goto Fail;

   //
   // Processing...
   //

   retval = TRUE;

Fail:
   if(c3)
      c3->Release();
   if(c2)
      c2->Release();
   if(c1)
      c1->Release();

   return retval;
}


 


C/C++에서는 goto를 절대로 ! 사용해서는 안된다? 컴파일러에서 goto를 에러로 내뱉지 않는 이상 저는 goto를 하나의 함수내부에서 한 번씩만 제한적으로 사용할 생각입니다.

 

 

 

C++는 완전한 객체지향 언어가 아니다.

 

개발 분야에서의 저명한 인사들은 C++가 완전한 객체지향 언어가 아니라고 합니다. C++보다는 자바가, 자바보다는 스몰토크가 완벽한 수준의 객체지향 언어라고 이구동성으로 말합니다. 네 저도 동감합니다. 그러나 C++가 완전한 객체지향 언어가 아니면 어떻습니까? 우리의 목적은 고객이 요구하는 기능을 정확히 파악하고 정리하여 프로젝트를 성공적으로 이끄는 것이지 객체지향 자체가 목적은 아니지 않습니까? 그렇다고 제가 객체지향의 장점과 중요성을 간과하는 것은 절대 아닙니다. (객체지향의 장점에 대해서는 더 이상 언급하지 않겠습니다.)

완전한 OOP(Object Oriented Program : 객체지향 프로그램)가 되려면 OOD(Object Oriented Design : 객체지향 설계)가 선행되어야 합니다. OOD를 위해서는 또 OOA(Object Oriented Analisys : 객체지향 분석)가 선행되어야 합니다. 이 단계만 충분히 지켜진다면 C++가 완전한 OOP가 지원되지 않는다 하더라도 우리가 객체지향 개발을 하는 목적에 충분히 부합되리라 생각합니다. 즉, 객체지향의 완성도에 미치는 영향은 언어가 아니라 얼마나 OOA와 OOD에 충실했느냐에 달려 있다고 생각합니다. (그러나 저 역시 OOA가 되지 않아 OOD가 되지 않고,OOD가 되지 않아 완전한 OOP가 되지 않습니다. C++로 클래스를 만든다고 해서 MFC를 상속받아 사용한다고 해서 OOP라고 말하고 싶진 않습니다.)

만일 C/C++가 C++로 재탄생되기 위해, 즉, 저명 인사들의 지적을 받아들여 main() 함수는 물론이고, 클래스 내에 선언되지 않은 모든 정적함수와 정적변수를 사용하지 못하도록 하고 OOP를 위하여, 추상화와 캡슐화를 위하여 지금의 C++ 클래스 구조에 난도질을 한다면 어떻겠습니까? 물론 이와 같은 일은 절대로 일어나지 않을 것입니다. 그리고 수 많은 개발자들을 위한 수 많은 SDK와 API를 사용하거나 자신이 직접 SDK나 API를 만들 때, C++가 아닌 C를 지원하는 유연성을 생각하면 이런 일은 절대 발생하면 안되겠습니다. 컴파일러는 결코 객체지향을 있는 그대로 컴파일하지 않습니다. 컴파일러는 각 클래스와 맴버함수와 변수들을 어셈블리 형태의 기계어로 다시 재구성합니다. 즉, 객체지향은 우리 개발자를 위해서 틀, 혹은 제약을 제공하는 것입니다. 제약이 있다는 것은 오류를 범하는 실수를 최소화하고 틀은 업그레이드를 용이하게 합니다. 대신 개발자의 자유를 빼앗아 간다고 저는 생각합니다. 저는 이 자유를 간혹 C에서 보상받기도 합니다. 그래서 C/C++이 유연하다는 것입니다. 제약과 자유를 동시에 주니까요.

 

 

 

C/C++를 공부하고자 하는 사람들에게...

 

적지 않은 사람들이 C를 공부하지 말고 C++를 바로 공부하기를 권유합니다.

그러나 저는 C를 먼저 반드시 공부해야 한다고 생각합니다.

그 이유는 C++ 객체지향을 익혀야 하는 부담감으로 포인터를 등한시 하는 경향이 있기 때문입니다. 저는 옛날에 C를 공부할 때 기본 문법책 외에 포인터 부분만 다루는 서적을 따로 사서 공부했습니다. C의 90% 이상의 영역이 포인터이며, C/C++가 유연성을 제공하는 방법으로 90% 이상이 포인터를 이용합니다. 즉, C를 공부하되 기본 문법 외에 포인터를 중점으로 공부하고, 그 다음에 C++와 함께 객체지향을 공부하는 것이 효과적인 순서라고 생각합니다. (그리고 OOA와 OOD를 익힌다면 환상적이겠죠.) 제가 말하는 여기서의 포인터란 단순히 메모리 변수의 주소를 기억하는 그런 포인터가 아니라 메모리 맵에 관한 모든 부분을 말하는 것입니다. 적어도 아래의 말도 안되는 코드에서 어디가 어떻게, 왜 잘못되었는지 설명할 수 있어야 합니다.

왜 잘못되었는지를 설명하는 것이 가장 중요합니다.

 

int* foo()
{
   int n = 10;
   int *p = &n;
   return p;
}

void main()
{
   int *p = foo();
   *p = 20;
}


 

C/C++는 개발하는 과정에서 저에게 유연성과 자유로움을 제공합니다.

그래서 저는 C/C++로 작업하는 것이 정말 즐겁습니다.

제가 말하고 싶은 것은 위 두 문장입니다, ^^;