C로 다시보는 알고리즘 01 – 알고리즘이란

어떤 문제를 해결하기 위한 논리나 절차를 알고리즘이라고 한다. 하나의 문제를 해결하기 위한 알고리즘의 형식은 다양하지만, 일반적으로는 사람이 생각하는 알고리즘을 그대로 적용하는 데에는 한계가 있다.

예를 들어서 최대 공약수를 구하는 방법에 대해서 생각하게 되면, 두 수의 약수를 찾는 것은 사람의 경험적 직감에 의존하게 된다. 경험에 따라 문제를 해결하기 위해 접근하는 숫자에 대해서 도중식이 다를 수 있다. 이를 컴퓨터에서 실행한다면 논리가 엄청나게 복잡해지게 된다. 그래서 이를 기계석인 방법(=정형화된 수식화)을 통해서 해결하는데 유클리드 호제법을 이용한다.

그리고 하나의 문제를 해결하기 위해서는 여러 방법이 존재할 수 있다. 이 중에서 가장 적합한 방법의 알고리즘을 찾는 것 또한 중요하다. 그러한 것을 찾기 위해서는 여러 요건들이 존재한다.

  1. 신뢰성이 높을 것
    정밀도가 높고 올바른 결과를 얻을 수 있어야 함.
  2. 처리 효율이 높을 것
    계산 횟수가 적고 처리 속도가 빨라야 한다. 알고리즘의 효율성은 O 표기법(Big-O Notation) 단위를 사용하여 처리하는데, 이에 대해서는 뒤에서 추가로 설명한다.
  3. 일반적일 것
    특정 상황에서만 사용되는 알고리즘이 아닌, 다양한 상황에서 통용될 수 있어야 한다.
  4. 확장성이 있을 것
    변경 사항을 간단하게 수정할 수 있어야 한다.
  5. 알기 쉬울 것
    누가 봐도 알기 쉬워야 한다. 이해하기 힘든 알고리즘은 프로그램의 유지, 보수에 엄청난 걸림돌이 된다.
  6. 이식성이 높을 것
    유용한 프로그램은 다른 곳에서도 사용할 가능성이 높다. 따라서 프로그램의 이식성을 높일 수 있도록 해야 한다. 그 프로그램에서 이용된 알고리즘 또한 마찬가지이다.

이 알고리즘이 학문적일 경우에는 사실 1,2번에만 신경을 써도 된다. 그러나 실제로 사용할 경우에는 어느 정도 나머지 것들에 대해서도 고려를 해야 한다.

또한 컴퓨터에서는 다양한 형테의 데이터를 다루게 되는데, 데이터를 처리하는 데에는 특정한 데이터 구조(자료 구조)를 사용하느냐에 따라서도 문제 해결에 필요한 알고리즘이 생기게 된다. 이 이야기는 “Algorithms + Data Structure = Program” (Prentice-Hall, 1976)이라는 책에서도 언급되었던 내용이다. 데이터 구조와 알고리즘은 밀접한 관련이 있으므로 좋은 데이터 구조를 선택하는 것은 좋은 프로그램을 만드는 기초로 이어지게 된다. (그래서 자료구조도 중요하다)

그래서 이 블로그에서는 알고리즘에 대한 이야기를 주로 작성하지만 일반적인 자료구조에 대해서도 다루게 될 것이다.

C로 다시보는 알고리즘 00 – 시작

알고리즘과 디자인 패턴 중에서 여러모로 고민을 좀 많이 했다… 기존에 못썼던 C 내용을 더 쓰긴 할텐데 그거 말고도 내가 뭘 좀 더 해볼까 하는 것을…. 개발 기술이야 뭐 하나하나 느긋하게 올려서 쓰면 된다만…. 활용에 대한 것들을 적다보면 역시 도달하는 과정 중 하나는 알고리즘과 디자인 패턴이기 때문이다.

이 둘에 대해서 고민하는 이유… 내 블로그는 그냥 내가 쓰고싶은대로 글을 만들어 쓰고 있지만 그냥 여러모로 ㅂ고 들어오는 사람들이 맣ㄴ을 꺼라 생각해서 정리해본다.

알고리즘과 디자인 패턴은 컴퓨터공학에서 주로 2~3학년 과목으로 있는데, 이 둘은 주로 개발에 대한 이론적인 것을 다루는 여러모로 골아픈 과목 취급당할 수 있다. 그러나 이 둘을 잘 하는 것은 중요하다. 쉽사리 잘 할 수 있는 과목들은 아니지만 말이다…ㅇㅅㅇ;;;

특히 알고리즘은 프로그래밍 할 때의 문제 해결을 위한 생각의 기초 체력을 올려주는 것에 비유할 수 있다. 반면 디자인 패턴은 문제 해결을 할 수 있는 여러 가지 툴을 써보면서 이 툴을 어떻게 응용해야 할 지를 고민하게 해준다. 둘 다 사고력 업을 위해서 공부하는 것이지만 이 사고의 방향성이 다르게 된다.

이 중에서도 우선적으로 선택한 것이 바로 알고리즘이다. 디자인 패턴은 범위도 너무 넓지만 이렇다할 정답 또한 있는 것이 아니기 때문에…. 그래서 옛날에 공부했던 내용들을 다시 꺼내보면서 하나하나 진행해 보려고 이 챕터를 시작하였다.

참고로 내가 공부할 때에 봤던 자료들을 다시 정립하면서 코드를 짜는 거라 걍 내가 쉽게(?) 할 수 있는 C를 기반으로 작업한다.

….쉽잖아요. C언어….

엔티티 프레임워크 – 03 설치

이전에는 막 여러 방법들이 나와있긴 한데….

이젠 그냥 NuGet으로 설치하면 끝이다. 이쪽이 프레임워크의 버전 관리 및 종속성 설치도 같이 할 수 있으면서 동시에 솔루션의 버전 관리 등에서도 쉽게 쓰이니 이쪽으로 가자.

Install-Package EntityFramework

라고 입력하거나 GUI 인터페이스에서 찾아서 설치하면 된다.

엔티티 프레임워크 – 02 LINQ랑은 뭔 차이?

둘 다 마이크로소프트에서 만든 기술이고, 둘 다 C#, 아니 닷넷 환경에서 잘 돌아간다. 근데 이걸 왜 다 따로 적었냐고 물을 수 있다만….

근데, 결론부터 말하면 LINQ는 플랫폼이고, 엔티티 프레임워크는 그 위에서 동작하는 프레임워크다. 기술 수준이 높다 낮다가 아니라, 편의를 위해 만든 플랫폼과 그걸 이용해서 더 편하게 만들어준 프레임워크인 것이다.

근데 이렇게 말할 수 있어도 그냥 코드만 본 사람들이 이 둘은 그냥 다른 거라고 해서 여러모로 물어봐서 좀 적어봤다. 사실 ORM 같이 이용할 수 있는 엔티티 프레임워크와 LINQ to Entity 이 둘에 대해서 물어보는 사람들이 좀 많아서…

LINQ가 코드로는 사실 금방 배우고 쓸만하지만 데이터베이스 이외에도 xml 등과 같이 형식 있는 파일로도 만들어서 쓸 수 있도록 해주는 녀석도 가지고 있기 땜에 범위가 작은 게 아니다.

엔티티 프레임워크 – 01 시작

C#에서 데이터 처리를 위한 여러 가지 기술들이 있다. 그걸 전체적으로 카테고리로 나눠본다면 다음과 같이 나눌 수 있다.

  • ADO.NET – SQL
  • ADO.NET – DataSet
  • Entity Framework (ORM)
  • LINQ to SQL
  • JSON
  • XML
  • File IO

그리고 저것들 안에서도 여러모로 세부적으로 하나하나 살펴볼 수 있다. 근데 여기서 내가 엔티티 프레임워크(Entity Framework)를 직접 글로 쓰려고 맘먹고 진행하는 데에는 이유가 좀 있다.

엔티티 프레임워크는 사실 ORM(Object-relation mapping)이다. 이 기술을 위해 여러 기술들이 아래에서 움직이고 있다. 하지만 요즘 이런 거 모르겠다 하고는 그냥 지원되는 거니깐 쓰는 친구들이 너무 많다. 특히 요즘 언어 배우는 친구들은 십중팔구 ORM 거의 꼭 쓴다고 본다. ORM을 쓰면 확실하게 편한 것이 바로 객체지향에 맞는 코드를 작성할 수 있다는 것이랑 DBMS에 종속되지 않는 코드를 작성할 수 있다는 것인데, 장점도 있고, 단점도 있다. 장점이야 뭐 여러곳에서 이야기 하겠지만 단점으로는 완벽한 ORM으로만 설계를 하려면 설계를 무지하게 잘 할 수 있어야 한다. 게다가 프로젝트 좀만 더 세세해지고 복잡해지면 이 난이도가 지수 함수를 그리듯 확 올라간다. 근데 이게 좀 제대로 안된 코드를 보고 요즘 빡이 쳐서 내가 다시 공부해서 정리하고 수정중인 플젝이 있다.

요즘 코드 퍼스트로 코드를 배우고 오는 친구들 땜에 기존에 특히 좀 나이 있으셔서 어느 정도 직급을 가진 분들하고 여러모로 안맞는 경우가 있니 어쩌니 하더니 내가 그런 광경이 있었던 곳에 와서 여러모로 중계를 하려다 보니…

사담이 좀 끼었지만 이런 일 땜에 좀 더 공부하고 정리해서 남들한테 쉽게 알려줄 수 있는 목적도 좀 기대하고 쓰게 되었다.

난 글 어렵게 쓰고 설명하는 거 별로 안좋아해서 되는 한 쉽게 쓰고 쉽게 풀어서 설명할 예정이다. 진짜로…

 

C와 객체지향 – 01 – 시작

C언어에 대한 글을 쓰고 있다. 리눅스 환경에서 동작하는 C언어를 쓰면서 C언어의 기본적인 내용을 작성하였고, 리눅스 환경에서 C언어를 개발하는데 이용하는 라이브러리들, 그리고 gcc와 make에 관한 글을 작성하고 있습니다.

그런데 문득 작업 진행이 제대로 이루어지고 있지 않는다는 것을 깨달았다. 이젠 리눅스 환경에서의 사용이 중점이기 때문에 그쪽 자룔 정리해서 올리는 작업이다 보니 언어에 관한 작업이 더뎌지고 해서 그런지 작업 속도가 늘지 않았다. 졸업논문 쓰고 난 후의 피로감도 있고…

그래서 이와는 별개로, C에 대한 내용을 작성하였으니 이젠 C 자체만에 추가적인 내용을 적어보고자 합니다. 그게 바로 고급지게 쓰는 C입니다.

그 중에 첫번째로 바로 C와 객체지향에 대해서 작성해 보려고 합니다.

C는 절차지향적 언어라고 기본적으로 배우죠. 따라서 프로그램의 흐름이 중요하고, 그에 맞도록 함수를 잘 써서 분할하고 하는 작업에 대해서 중요하게 여깁니다. 그러다 보니 아무리 몇만 라인의 코드를 작성하더라도 코드 자체가 상당히 평평한 구조를 가지게 됩니다. 함수 하나만 해도 몇백 라인이 되기 일쑤고, 비슷한 기능 하는 함수들 미친듯이 만들고, 그런 함수들이 뒤에 숫자 2,3,4 이런 식으로 막 붙은 그런 함수들… 양산하기 무지 쉽습니다. (이 글을 읽어야 할 수준이라면 내 이야기 아니라고 생각하는 분 없을겁니다.)

내부 처리 로직 중에서는 공통으로 처리할 수 있는 부분은 공통으로 처리할 수 있지만, 동작 제어 함수들은 좀 사정이 다릅니다. 다양한 인자가 필요한 함수, 사용하는 데이터가 전역 변수로 연결, 독립성 결여된 함수들, 서로 의존관계가 너무 깊어 개별적인 테스트 할 수 없는 함수들….

사실 C에서는 특정 내용을 처리하기 위해 함수라는 것이 존재하고 이를 이용해 프로그램을 구조화할 수 있어야 한다고 가르치는데 왜 이런 현상이 발생할까요?

바로 디자인 패턴의 부재 때문입니다. 소프트웨어 개발에서 말하는 디자인 패턴은, 프로그램 개발에서 자주 나타나는 과제를 해결하기 위한 방법 중 하나로, 과거의 소프트웨어 개발 과정에서 발견된 설계의 노하우를 축적하여 이름을 붙이고, 이를 재사용하기 좋은 형태로 특정 규약을 묶어서 정리한 내용을 말합니다. 문제 해결을 위해 해결 방법을 제시하고, 이를 사용 가능한 구조로 묶고 코드로 만들 수 있는 단계를 알고리즘이라고도 하기 때문에 알고리즘을 가르치는 것인가를 생각하기 쉽지만 디자인 패턴에서는 구조적으로 이미 정의된 문제를 해결하는 방식을 알려줍니다. 이미 정형화된 패턴을 문제에 적용하는 점이 아무것도 없는 상태에서 하나 하나 설계하는 중에 최적화 작업을 하는 알고리즘과는 접근 방법이 조금 다르죠.

그리고 이런 디자인 패턴의 많은 부분이 객체 지향 언어에서 사용하는 것을 전제로 하기 때문에 C랑은 상당히 무관하다는 듯이 배우고, 가르치는 분들 또한 그렇게 가르칩니다. 컴퓨터공학 전공한 분들을 아시겠지만, 객체 지향 프로그래밍 과목을 수강한 후에 디자인 패턴에 대한 강의를 들을 수 있다는 것을 알 수 있습니다.

그러나, C에서도 객체지향을 실현할 수 있습니다. 객체 지향을 잘 활용하면 프로그램의 구조가 기존의 C 프로그램들과는 달리 크게 변하게 됩니다. 또한 각 함수들이 독립적으로 작동하게끔 만들 수 있습니다. 이를 위해서는 C로 객체 지향을 염두해 두고 설계하고 프로그래밍할 수 있어야 합니다. 특히, 현대의 임베디드 시스템들과 C 프로그래밍에서는 이런 사고가 상당히 많이 적용되어 있기 때문에 C를 할 수 있다고 하려면 이런 수준까지 할 수 있어야 합니다.

그래서, C로 객체 지향과 같은 설계와 프로그래밍을 할 수 있는 방법을 포스팅하려 합니다.

p.s. 기존의 리눅스 환경에서의 C 프로그래밍과 별도로 활동합니다.

117 – make 규칙 1: 암시적 규칙

make 파일을 작성할 때는 컴파일에 필요한 각 처리 단계와 의존성을 분명히 지정해주었다. 이와 같이 make가 해야 할 일을 분명히 지정하는 것을 명시적 규칙(explicit rules)이라고 한다. 이와 반대로, make 내에 미리 정의된 규칙을 이용해 make 파일을 단순화시키는 규칙을 암시적 규칙(inference rules)이라고 한다.

이를 보여주기 위해서 전에 작성하던 make 파일을 상당히 줄여서 예시로 보여주겠다.

소스 파일에서 오브젝트 파일로 컴파일하도록 하는 단계를 빼고 그냥 바로 test를 오브젝트로 만들어서 빌드하도록 하였다. 전에 글들의 코드를 보고 비교하면 상당히 단축시킨 것을 볼 수 있다. 이 코드는 실행하면 그대로 컴파일 된다. 그 결과가 아래의 화면이다.

대상이 되는 test를 생성하기 위해 make는 의존하는 파일들을 살필 것이다. 그러나 대상이 의존하는 test1.o test2.o test3.o 이 세 파일이 존재하지 않기 때문에 make는 다시 저 파일들의 대상으로 있는 곳을 찾으려 한다. 이때, 찾지 못할 경우에는 오류를 내고 중단할 것 같지만, 실제로는 암시적 규칙에 따라 오브젝트 파일을 gcc가 생성할 수 있는 것을 알기 때문에, test1.o test2.o test3.o 파일을 생상하기 위해 의존하는 파일을 스스로 찾고 컴파일러 호출까지 알아서 수행한다. 이때, 실행 결과에 찍힌 규칙을 보면 알겠지만 gcc를 호출하는 것이 아니라 cc 컴파일러를 호출하였다.

이 과정을 보고 싶다면 make -p 라고 해서 -p 옵션을 통해서 볼 수 있다. 실행하면 아래와 같이 쭉 나온다. 아래 화면에 스크롤 바를 주목해라. 내용 장난 아니게 많다. 암시적 규칙으로 어떤 것들이 불러지는지 알고 싶다면 계속 스크롤을 내려서 살펴보는 것도 좋을 것이다. 근데 당장은 이해 못한다.

116 – make 매크로 수정

이미 정의된 매크로 부분을 다음과 같이 바꿔봤다.

OB 매크로에 test3를 추가하는 형태로 OBJF를 바꾼 것이다. 그러면 일전에 모든 것을 다 선언했던 매크로와 동일한 효과가 있다.

그렇다면 이제 프로그래밍 좀 해본 분들은 한 번 정도는 생각해 볼 만한 것이 바로 변수에 변수 뒤엎어 쓰듯이 매크로도 그런 식으로 쓸 수 있지 않을까 하는 생각을 할 것이다. 예를 들어 이렇게 말이다.

확대해서 보일 수 있도록 캡쳐를 하였다. 위의 화면과 같이 하면 되겠지? 라는 생각들 할 수도 있다. 결론부터 말하면 안된다. 아래처럼 된다.

매크로가 정의되는 방식에는 두 가지 방식이 존재한다. 재귀 확장형 매크로와 단순 확장형 매크로가 존재하는데, 우리가 지금 이용하는 make의 경우에는 재귀 확장형 매크로이다. 따라서 이미 선언한 OBJF에 대해서 불러오는 부분에서 이용한 $(OBJF) 부분에서 재귀 콜이 불러져서 다시 참조하는 형태, 즉 다른 매크로를 포함하면 같이 확장을 하는 형태로 만들어졌기 때문에 오류를 낸 것이다. 똑똑한 매크로 실행기가 먼저 알아서 멈춰진 것이다.

그럼 이걸 단순 확장으로 만들 수 없는 걸까? 당연히 있다. 단순 확장 매크로는 말 그대로 단순히 확장만 되는 형태로 만들어지고, 매크로의 정의 차이에 따라서 이걸 판단하게 된다. 우선 단순확장에 대해서만 살펴보려고 하는데, 단순 확장의 경우에는 :=를 통해 확장하면 된다. 아래처럼 해주면 된다.

간단하다. 설명만 힘들 뿐이다. (ㅠㅠ) 이를 실행해보면 오류 없이 진행될 수 있는 것을 볼 수 있다.

그렇다면 기존에 있는 내용에 대해서 치환할 수 있는 매크로도 존재한다. 기본식이 다음과 같다.

$(M_NAME:old=new)

old가 기존에 있던 부분이고, new가 치환하려는 부분이다. 이건 예시 코드를 만들어서 보여주겠다. OBJF에 있는 .o 확장자를 .c 확장자로 변경해 보겠다.

위의 화면과 같이 SRCS라고 하는 소스코드 파일들을 매크로 선언으로 하였다. 그 다음에 이걸 확인하기 위해서 명령어를 하나 추가해서 보여주려고 한다. 아마 오류가 날 코드이다.

실제 실행한 화면이다. 예상대로 오류가 났다. (이거에 대해서는 나중에 글을 추가하겠다.) 그러나, 실행하려는 구문에 보면 뒤에 확장자가 .c로 바뀐 것을 볼 수 있다.

115 – make의 내부 매크로

앞에서 살펴본 매크로의 경우에는 사용자가 원하는 대로 맞출 수 있는 매크로이기 때문에 사용자 정의 매크로라고 한다. (이 표현은 프로그래밍 언어 관련된 내용을 많이 보다보면 중복되게 나오는 표현이라 익숙할 것이다.) 그러나, make 파일을 만들 때 이용할 수 있도록 미리 정의된 매크로들이 존재하는데, 이를 내부 매크로라고 한다. 내부 매크로의 종류와 의미는 아래와 같다.

  • $@ |현재 목표 파일의 이름
  • $* | 확장자를 제외한 현재 목표 파일의 이름
  • $< | 현재 필수 조건 파일 중 첫 번째 파일 이름
  • $? | 현재 대상보다 최슨에 변경된 함수 조건 파일 이름
  • $^ | 현재 모든 필수 조건 파일들

이 내부 매크로를 이용하여 앞에서 살펴본 예제를 더 간단하게 만들어보자.

이 표현이 익숙하다면 상관 없겠는데 필자의 경우에는 은근 싫어하는 스타일이라서 사용하진 않는다.

114 – make와 매크로

make 파일을 작성하다 보면 같은 파일 이름을 여러 번 써야 하는 경우가 있다. 이를 매크로를 사용하면 편리하고 명령어를 단축시킬 수 있는다. 매크로는 다은과 같이 정의하면 된다.

M_NAME = value

사용자가 임의로 정해서 쓰는 매크로 이름인 M_NAME은 등호 오른쪽의 값으로 확정되면 다음과 같은 형태로 이용할 수 있다.

$ (M_NAME)

매크로를 이용하면 복잡한 구문을 간단한 단어로 표현할 수 있으므로, 짧은 make 파일을 만들 때보다는 더 복잡한 파일을 작성할 때 유용하다. 그리고 매크로 이름은 대소문자 모두 가능하지만 코딩 규칙 상 대문자만을 사용하는 것을 일반적이라고 한다. 그리고 make 파일의 상단에 미리 정의한 다음에 이용한다.

매크로를 이용하여 이전에 예시로 보여주기 위해 작성하였던 make 파일을 좀 더 단축시켜보았다. 그 결과가 아래의 화면이다.