C프로그래머가 알아야 할 것들 - 04 프로그램 언어

C프로그래머가 알아야 할 것들 - 04 프로그램 언어

왜 문법을 배워야 하는가?

한국어를 할 줄 모르는 독일인과, 독일어를 할 줄 모르는 한국인과 대화가 가능할까요? 바디 랭귀지로 하면 되지 않느냐는 분도 계시겠지만 그것도 어느 정도 한계가 있기에, 제대로 된 의사소통은 불가능할겁니다.

컴퓨터는 0과 1 (2진수)밖에 인식하지 못한다고 배웠습니다. 컴퓨터에게 명령을 내릴 때,

00001110 01010101

이런 식으로 모든 명령을 내려야 한다면 프로그램을 만드는 데에 드는 시간이 막대할 것입니다.

그래서, 기계어에서는 16진수 (2진수를 4개씩 묶어서) 표현하고 있습니다.

00001110 01010101 -> 0x0E55

2진수일 때보다 조금 나아졌지만, 과연 이걸로 프로그램을 만들 수 있을까요? 0x0E55가 무슨 뜻인지 알 수 있는 사람이 몇이나 될까요??

프로그램을 작성하는 사람이 이해하기 쉽고, 컴퓨터가 이해할 수 있는 언어가 필요했습니다.

그래서 나온 것이 어셈블리어였습니다. 어셈블리어로의 5와 6의 덧셈은 아래와 같습니다.

mov ax,5
add ax, 6

계산 결과는 ax레지스터에 담겨 있게 됩니다.

이진수나 16진수로 표현된 기계어보다는 어셈블리어가 이해하기 쉬웠지만, 규모가 큰 프로그램에는 적합하지 못했습니다.

게다가 어셈블리 언어는 기계어와 1:1대응이다 보니, 하드웨어에 종속적인 언어였습니다.

이 같은 문제들을 해결하기 위해, 프로그래머가 사용하기도 편하고 하드웨어에 상관없이 구동 될 수 있으며, 기능도(비교적) 우수한 구조 형 언어 (파스칼, 코볼, 포트란, C언어)등이 나오게 되었습니다.

프로그램 언어를 통해 컴퓨터에게 일을 시키기 위해선 어떻게 해야 할까요? “야 지금부터 시간 재라.” 혹은 “야 지금부터 음악 재생 좀 해봐.”

이렇게 컴퓨터에게 우리가 사용하는 말로 말하면 알까요? 컴퓨터가 알 수 있는 말로 해야 한다고 했는데 기계어로 말하는 건 사실상 불가능에 가까우니 프로그래밍 언어의 힘을 빌려야 합니다.

그 프로그래밍 언어에 해당하는 문법을 분석해주는 컴파일러도 만능이 아니다 보니, 자신이 알 수 있는 내용으로 말해주길 바라죠. 컴파일러가 이해할 수 있는 내용에 대한 규칙이 바로 문법입니다.

사용하는 프로그래밍 언어에 맞는 문법을 지켜야만 컴퓨터에게 원하는 일을 시킬 수 있기 때문에 문법에 대해 알아야 하는 건 당연하겠죠?

내가 이걸 배워서 과연 실전에 사용할 수 있을까?

많은 분들이 하시는 고민. 바로 그것이 과연 지금 배운 문법들로 프로그램을 만들 수 있겠는가 하는 것입니다. 결론부터 말하자면 “그렇다”는 것입니다.

왜 난 문법은 이해했는데 원하는 프로그램을 만들 수 없을까 하는 생각을 하시는 분들이 많은데, 그 이유는 프로그래밍에 대한 막연함, 프로그램을 구성하고 있는 기본 법칙이나 내부 원리에 대한 이해가 부족한 것 등의 이유가 있습니다.

프로그래밍에 대한 막연함이란, 입문서 혹은 문법 서에 나온 예제 정도만 작성해보았지, 실제 사용할 만한 프로그램 개발 경험이 전무하기 때문에 가지는 부담감이라 할 수 있을 겁니다.

추상화를 통해 이런 부분까지 알지 않아도 가능한 시대가 왔다는 사람들도 있지만, 여전히 이 것에 대한 이해는 중요합니다. 우리는 API를 사용함으로써 키보드 장치에 대한 직접적인 제어를 하지 않아도, 키보드 입력에 관한 정보를 얻을 수 있고, 어떤 식으로 화면이 점을 찍는지 내부원리를 알지 못해도, API에서 지원해주는 점 찍기 함수를 통해서 점을 찍을 수 있습니다. 이 것은 매우 유용하고 대다수의 프로그래머들이 이런 기능을 사용하고 있습니다. 그렇지만, 점을 어떤 식으로 화면상에 표시하는지에 대한 원리, 키보드 입력이 어떻게 하여 프로그램으로 메시지로 전달되는지에 대한 이해가 되어있는 사람과, 그렇지 않은 사람과의 실력차이는 분명합니다.

근본 원리를 알고 있는 사람은 좀 더 멀리 내다볼 수 있고, 신 기술에 대한 적응도 빠릅니다. 음악파일의 근본 원리를 아는 사람은, 새로운 포맷을 만들어 낼 수도 있고, 새로운 알고리즘을 만들어 낼 수 있고, 자신이 작성하는 프로그램을 구성할 때 사용할 효율적인 사용법을 알아 낼 수도 있지만, 원리는 모르고 라이브러리나, 컴포넌트 등에서 제공하는 함수의 사용법만으로 프로그램을 개발하는 사람은 그 함수가 지원하는 기능으로 프로그램을 구성하는 그 이상은 구현 불가능합니다.

프로그래밍을 잘하기 위해, 좋은 프로그램을 만들기 위해선, 프로그램 언어 이외에도 배워야 할 것들이 많고, 그것들을 놓치면 안 되겠죠?

최적화

프로그램을 만들 때에, 여러분이 생각하시는 최적화는 이 중에서 무엇인가요?

1. 유지보수가 쉬운 읽기 쉬운 코드
2. 빠른 속도
3. 메모리 사용의 효율
4. 유저 편의
5. 시스템 리소스 사용의 효율
6. 시간 투자 효율

최적화의 기준이란 프로그램에 목적에 따라 가장 중요하게 여겨질 것이 결정되는 것이지, 늘 우선되어야만 기준은 없습니다.

다만, 자신이 작성할 프로그램이 가지고 있는 특징을 잘 파악하여, 어떤 부분을 최적화 할지 정하는 것이 좋습니다.

예를 들어, 임베디드 프로그램에서의 최고의 가치는 주로, 빠른 속도와 메모리 사이의 효율이 중요합니다. 일반적인 응용 프로그램에서라면, 유지보수가 쉬운 코드와, 유저 편의 사이에서의 밸런스를 맞추는 것이 좋죠. 게임 클라이언트 프로그램이라면, 빠른 속도와 유저 편의를, 서버 프로그램이라면 메모리, 속도, 안정성이 중요하죠.

시간도 중요한 의미를 가질 때가 많습니다. 지금 당장 해야 할 일이 산더미인데, 발생 빈도가 매우 낮고 치명적이지 않은 버그를 잡기 위해 한 달을 투자 할 순 없습니다.

버그 없는 프로그램은 존재하지 않습니다. 완벽에 가까운 프로그램은 있어도, 완벽한 프로그램은 없습니다.

0.1%의 완성도를 향상 시키기 위해서, 10%의 완성도를 높일 수 있는 시간을 소요한다는 것은 비효율적이죠.

좋은 프로그램을 만들려는 노력은 프로그래머로서 당연한 과제이기에, 완성도를 높이려는 노력은 당연한 것이지만, 당장 눈앞에 놓인 문제 해결만을 생각하기 보다, 어떤 부분에 노력을 기울이는 것이 더 좋은 결과를 낳을지 고민해보는 것이 좋을 것입니다.

언어의 선택

지금 현재 가장 많이 사용되고 있는 언어로는 C (C/C++ 두 가지를 함께 지칭. C와 C++은 다른 언어이고, 다른 점이 굉장히 많지만, 여기서는 같은 의미로 사용하겠습니다)와 자바를 꼽을 수 있습니다. 여전히 델파이나, 비주얼 베이직도 많이 쓰이며, 웹 언어인 PHP, ASP, 스크립트 언어인 파이썬, 루아, 루비 등 다양한 언어가 있습니다.

모든 언어들은 각기 장, 단점이 있습니다. C언어는 메모리를 자유 자재로 다룰 수 있는 대신, 그 만큼의 위험 부담을 가지고 있고, 코딩의 자유로움을 가진 대신, 잘못된 코드가 작성 됐을 경우 코드 분석의 어려움 (유지 보수의 어려움)의 위험성을 갖고 있습니다. 자바는 JVM을 통해서 멀티 플랫폼 프로그램을 구현했지만, JVM이 지원하지 않는 Low Level 접근은 불가능 하고, C++에서 코드 분석에 어려움이나 오해의 소지가 있는 문법은 없앤 대신, 그 문법들이 가지고 있던 장점들도 함께 사라졌죠. 다른 언어들도 각기 장단점을 갖고 있습니다.

자신이 능숙한 언어로 프로그램을 작성할 때 얻을 수 있는 이점은, 새로운 언어를 습득하는 데에 걸리는 시간 단축, 익숙한 언어 이기에 새로운 언어를 사용할 때 실수가 발생할 여지가 줄어드는 장점 등이 있습니다.

하지만, 최적화를 위해서 어느 한가지 면만이 우선시 될 수는 없죠. 처리 속도가 우선시 되는 프로그램에서 비주얼 베이직을 사용하거나, 웹 언어로 화면 전환이 잦은 채팅 프로그램을 작성 하거나, 스크립트 언어로도 작성할 수 있는 문자열 해석기를 어셈블리어로 만들거나 하는 것들은 비효율적인 일입니다.

언어는 구현을 위한 도구이지, 언어 자체는 프로그래밍에서 중요한 가치가 아닙니다. 한가지 언어만 잘해서는 여러 가지 상황에 유연하게 대처하는 것이 불가능하기에, 특정 언어를 능숙하게 다루는 것은 매우 긍정적인 일이지만, 특정 언어에만 집착해서 만드는 프로그램의 완성도를 떨어뜨리는 일은 없어야겠습니다.

C프로그래머가 알아야 할 것들 - 03 운영 체제와 컴퓨터 원리

C프로그래머가 알아야 할 것들 - 03 운영 체제와 컴퓨터 원리

운영체제란?

초기에 컴퓨터는 컴퓨터를 키자마자 프로그램이 담겨 있는 디스크를 삽입해야만 했습니다. 그리고 특별한 경우를 제외하고는 다른 프로그램 사용시에는 재 부팅 시켜야만 했습니다. 이 방법은 매우 불편했습니다. (비디오 게임기들은 이 방식을 채용하고 있는 경우가 많습니다)

그래서 유닉스, MS-DOS등의 운영체제가 나오게 됐습니다. (참고로 MDIR은 운영체제가 아닙니다. 인터페이스를 제공해주는 프로그램이죠) 각 운영체제하에 프로그램을 구동시킨 후, 프로그램 종료 시에는 그 운영체제로 돌아오게끔 하는 방식을 취한 것이죠.

이전에는 그래픽 카드나 프린터, 사운드 카드마다 출력을 지원해주는 방식이 달랐습니다. 점 하나 찍거나 소리를 내는 방법이 하드웨어에 따라 달랐죠.

그래서 각 하드웨어 장치(지금은 그래픽 카드와 사운드 카드를 의미합니다)를 컨트롤 하기 위한 작업들은 프로그램 개발 업체마다 따로 이루어져야 했고, 그렇기에 발매된 지 얼마 되지 않은 하드웨어나, 대중적이지 않은 하드웨어의 경우는 지원되지 않는 경우가 대부분

상황이 이렇다 보니 프로그래머들은 프로그래머 나름대로 다수의 하드웨어 장치를 지원하려다 보니 힘들었고, 사용자들은 사용자 나름대로 내 하드웨어가 내가 사려는 소프트웨어와 호환되는지를 따져봐야 하는 불편한 상황이었죠.

물론 DOS시절에도 VESA (Video Electronics Standard Association: 비디오 가전 표준 협회)등에서 그래픽 카드의 표준화를 시키고 표준에 맞는 그래픽 카드는 모두 지원되도록 노력을 기울였지만 만족스러운 결과를 얻어내진 못했습니다.

윈도우 이런 문제에 대한 대한을 가지고 있었습니다.

MS-DOS와 비교되는 윈도우의 장점으로 GUI (Graphic User Interface)를 꼽지만, 플러그 앤 플레이나, API (Application Programming Interface)도 빠지면 안될 정도로 중요한 요소입니다.

플러그 인 플레이는 자동 하드웨어 장치 인식 기능으로, MS-DOS의 단점을 보완해주기에 충분했습니다. 각종 장치에 대한 추상화를 이뤄내서, 사용자가 어떤 장치를 사용하던 간에, 운영체제가 그 장치를 지원하기만 한다면, 프로그래머는 그 장치를 이용할 수 있어졌습니다.

API는 프로그램 개발용 함수 모음으로, 점 찍기, 타이머, 텍스트 출력, 마우스 입력, 키보드 입력 등등 프로그램 개발에 필요한 기본적인 기능을 지원해줍니다. 각 프로그램마다 자체적으로 지원하기 위해 시간투자를 해왔던 작업들을 운영체제에서 추상화를 통한 지원이 이뤄짐에 따라, 프로그램 개발이 한결 편해진 것입니다.

이벤트

MS 윈도우(이하 윈도우)에서 이벤트란 윈도우에서 발생하는 정보들을 말합니다. 즉, 마우스 이동, 마우스왼쪽 버튼 클릭, 마우스오른쪽 버튼 클릭, 키보드 누름, 키보드 뗌, 문자 키 누름, 프로그램 시작, 프로그램 종료 등 다양한 상황마다 이벤트가 발생하는데, 그렇게 발생되는 이벤트를 메시지로 프로그램에 보내줍니다.

윈도우에서 응용 프로그램에 전달 해 주는 메시지 중 원하는 메시지를 이용하여 처리해주는 것이 윈도우 프로그래밍에서의 이벤트 프로그래밍이라고 합니다.

윈도우가 하드웨어의 접근을 직접 관리하기 때문에, 윈도우용 프로그램을 개발하는, 프로그래머들은 하드웨어 제어에 대해 그다지 신경 쓰지 않아도 되는 것이죠.

프로세스와 쓰레드

프로세스는 프로그램의 실행 단위를 의미 합니다. 다른 말로는 작업이라는 의미로 태스크라 부르기도 하죠.

멀티 태스크나 멀티 프로세스란, 다중 프로그램 구동이라고 생각하시면 됩니다.

멀티 태스크를 통해 우리는 동시에 프로그램이 실행되고 있다고 생각하시는 분도 많을 겁니다. 그러나 실상은 눈 깜짝 할 사이에 여러 개의 프로그램이 번갈아 가면서 실행되고 있는 것인데, 그것이 매우 빠른 속도로 이루어지기에, 우리는 동시에 작동하는 것으로 느끼는 것이죠.

쓰레드는 프로세스 내부의 실행 단위를 말합니다. 프로세스 내에서 쓰레드가 여러 개 존재하여 처리되는 것을 멀티 쓰레드라 하죠.

예를 들면 메신저라는 한 프로그램 내에서 음악 재생하면서 채팅(메시지 입력)을 할 수 있는 것은, 음악 재생과 채팅기능이 쓰레드 단위로 구동되기 때문입니다.

멀티 쓰레드도 멀티 태스크와 마찬가지로 한 프로세스의 시간을 쪼개 씀으로 인해, 동시에 여러 가지 작업이 이루어 지는 것처럼 보여지는 것입니다.

컴퓨터는 계산기다

컴퓨터는 계산기라고 한다면, 아니? 계산기에서 동영상도 볼 수 있고, 게임도 할 수 있고, 그림도 볼 수 있고, 음악도 나온다는 게 말이 되냐고 하시는 분도 있으시겠지만 사실입니다. 컴퓨터라는 이름 자체가 Compute (계산하다)에서 파생된 것도 우연은 아니겠죠?

초기 컴퓨터(최초의 컴퓨터는 애니악으로 알려져 있는데, 최초의 컴퓨터는 앨런 튜닝이 2차 세계 대전에서 독일군 암호 해독을 위해 만들어진 콜로서스입니다)는 계산을 하기 위해 만들어졌습니다. 대형 고속 계산기쯤이었다고 생각해도 되겠죠? 그 당시의 컴퓨터는 연산속도도 느리고, 연산을 위한 저장 장소가 작았기 때문에 간단한 처리밖에 못했습니다.

시간이 흐르면서 컴퓨터는 발전을 거듭했습니다. CPU의 연산 속도도 이전과는 비교도 안될 정도로 빨라졌고, 메모리 용량이 증가했으며, 보조 기억 장치의 용량도 증가했습니다.

소프트웨어도 하드웨어 장치를 활용할 수 있도록 발전해왔습니다.

하드웨어는 사용자로부터 입력을 받아 그 것을 비트 정보로 변환하고 프로그램(혹은 운영체제)에 전달합니다.

프로그램은 어떤 소리를 출력해야 하는지, 어떤 이미지를 보여주어야 하는지가 결정해서 다시 출력 장치로 전송합니다.

출력 장치인 모니터나 스피커는 전달 받은 디지털 데이터를 분석해서 그에 맞는 출력을 해주면서 컴퓨터를 통한 하드웨어 연동이 이루어지고 있습니다.

이런 과정이 다 계산으로 이루어 질 수 있는 것은, 챕터2에서 설명한 비트의 법칙 덕분입니다. 컴퓨터에서는 소리도, 영상도 모두 디지털 데이터인 비트 값으로 저장하고 있습니다. 비트 값은 수치화된 값입니다. 저장된 값에 맞는 출력을 장치에 요청함으로, 소리가 재생되고, 영상도 출력되는 것입니다.

2D게임이 3D게임보다 빠르다?

우리가 흔히 하는 착각은 2D게임이 3D게임보다 빠르다. 혹은 2D게임은 저 사양이다라는 생각입니다.

컴퓨터에서 이뤄지는 모든 것들은 계산에 의한 것입니다.

그렇기 때문에, 2D게임이던, 3D게임이던 간에 게임의 속도는 얼마만큼 많은 계산을 필요로 하는지에 달려있는 것이지, 같은 (혹은 비슷한) 기능을 가진 게임이라면 2D와 3D의 기본적인 연산 속도의 차이(3D는 일반적으로 다각형으로 이루어져 있기에 기본적으로 이루어져야 할 연산이 많고, 실수 연산이 많이 필요하기 때문에 2D보다는 확실히 연산할 것이 많긴 합니다)가 있지만, 게임의 규모가 커지다 보면 오히려 2D게임이 느려지는 경우가 발생하기도 합니다.

할 수 있는 액션이 별로 없는 경우에는, 2D쪽이 월등이 빠르겠지만, 많은 수의 액션, 많은 수의 프레임 갱신, 부드러운 화면 처리, 시각적 효과 등이 필요할 경우에는 3D보다 많은 연산을 해야 하는 경우도 많습니다. 특히나 화면 확대를 해야 할 경우, 3D는 카메라를 당기기만 하면 되지만, 2D는 현재 픽셀 값을 기반으로 확대 했을 때 영상 비를 유지 시키면서 보간 및 확대 연산을 해야 합니다. 2D일 때의 계산 량이 더 많을 가능성이 높습니다.

심지어는 화면 전환마저 거의 없는 게임인 Football Manage시리즈의 경우 웬만한 3D게임보다도 속도가 느린데, 이 것은 이 게임이 처리해야 될 데이터가 많기 때문입니다. 모든 경기 결과는 랜덤이 아닌, FM시리즈의 규칙(데이터에 기반하되, 그 데이터가 전부가 아닌)에 따른 결과가 나와야 하기 때문에, 모든 경기 결과를 시뮬레이션을 통해 얻어내야 되는데, 그 계산해야 될 데이터가, 웬만한 3D게임보다 많기 때문에 느린 것입니다.

3D게임이 느렸던 것에는 실수 연산도 한몫 했는데요, 부동 소수점 실수의 연산은 정수처럼 간단하지가 않아서 실수 연산을 많이 필요로 하는 3D게임이 느렸었죠. 요새는 3D게임을 위해 실수 연산 속도를 끌어 올린 그래픽 카드들로 인해 이런 문제는 많이 해결됐죠.

어때요? 컴퓨터의 속도에 대한 감이 오시나요?

C프로그래머가 알아야 할 것들 - 02 비트의 법칙

C프로그래머가 알아야 할 것들 - 02 비트의 법칙

비트가 뭐지?

비트란 이진수(Binary Digit )의 약자로써 컴퓨터에서 제어 가능한 데이터의 최소단위입니다. 하지만, 컴퓨터에서 입 출력할 때 사용하는 최소 단위는 바이트죠. 둘 다 최소단위라는 건 알겠는데 정확한 차이가 뭐냐고요?

비트란 저번 강좌에서 배웠던 2진수 10 (10진수 2)을 2비트(2진수 2자리 수이기에)로 표현 가능하고 제어 가능하단 의미고, 바이트는 비트 8개가 모여서 구성된 것이 1바이트로, 파일이나 데이터 형의 최소단위로 쓰입니다.

프로그래밍과 비트는 무슨 상관일까?

비트가 2진수를 의미한다는 건 알겠는데 도대체 프로그래밍에서 비트가 무슨 의미가 있는 것인지 궁금해하실 분들이 계실 거라고 생각합니다.

하드웨어의 발전에 따라 조금 더 빠른 프로그램 보다는 쉬운 사용법과, 코드 재사용 또는 유지 보수가 쉬운 프로그램을 만드는 것이 더 중요해졌고, 그로 인해 C++을 대신할 차세대 언어라 불리는 자바, C#이 등장했습니다. 하드웨어가 발전함에 따라 그 하드웨어의 기능을 활용하기 위한 기술이 적용 되다 보니 여전히 빠른 프로그램을 작성할 필요성은 존재합니다.

속도 면에서 타의 추종을 불허한다는 C/C++에서도, 속도 최적화에 크게 민감하지 않은 자바에서도, 코드 수행의 근본적인 속도 문제는 벗어 날 수 없습니다.

어떤 언어를 사용하건 간에, 결국엔 기계어로 번역되어 수행되기 때문에 가능하다면 빠르고 효율적인 프로그램을 작성하도록 하는 것이 좋습니다.

대부분의 알고리즘은 N (임의의 수)에 비례해서 수행속도가 증가하는데, N이 증가할 때마다 제곱으로 수행 시간이 증가해 100년 이상 걸려야만 수행이 완료되는 경우도 있기에 여전히 프로그램의 수행 시간은 중요합니다.

최적화 된 프로그램이란 메모리와 속도 모두 만족시키는 프로그램을 말하는데, 그것을 만족하기 위해선 비트 단위 연산 또는 처리가 필요 할 때가 많기 때문에, 비트 연산에 대해서도 알아 두는 것이 좋다고 볼 수 있는 것이죠.

데이터 형

데이터 형 C언어에서는 다양한 데이터 형을 제공합니다. 여기서는 주로 사용되는 몇 개의 데이터 형만 가지고, 비트와 관련해 알아보도록 하죠.

문자 형으로 알려진 char (캐릭터 형)는 1바이트로 이루어져있습니다. 1바이트=8비트이므로, 0000 0000 이렇게 8자리 2진수만큼 사용할 수 있는데, 2의 7승은 256 (첫 자리가 2의 0승이므로, 8비트의 경우 2의 7승만큼 사용 가능합니다) 이므로, 부호가 없는 경우는 0~255, 부호가 있는 경우는 -128~127까지 사용 가능하게 되는 겁니다. 부호가 있는 경우는 최상위 비트를 부호 비트로 사용하게 되므로 사용 가능한 수의 범위가 반으로 줄 게 됩니다.

2바이트 데이터 형인 short int 도, 2의 15승인 65536만큼의 수를 사용할 수 있는데, 부호 없는 경우 0~65535, 부호가 있는 경우 -32768~32767까지의 수를 사용할 수 있습니다.

데이터 형 크기(바이트) 부호 표현 범위
int 4 있음 -2147483648~2147483647
short int 2 있음 -32768~32767
long int 4 있음 -2147483648~2147483647
unsigned int 4 없음 0~4294967295
unsigned short int 2 없음 0~65535
signed char 1 있음 -128~127
unsigned char 1 없음 0~255

위 표에서 알 수 있는 것은, C언어 계열에서는 별도 표기를 하지 않는 이상 signed(부호 있는)로 인식한다는 점 입니다.

실수 형은 단순히 2진수로 데이터를 담고 있지 않습니다. 다음과 같은 형식으로 실수를 표현하고 있습니다.

부호 지수부 가수부

부호는 말 그대로 +- 부호를 의미하고, 가수부는 값을 의미합니다. 지수부는 10의 거듭승을 의미합니다.

좀 더 정확히 말하자면, 지수부는 그 자체가 지수부의 값이 아니라 이 수치에서 bias 값을 빼 주어야 그 값이 나옵니다. 일반적으로 float에서 bias의 값은 127 입니다. 가수부는 가장 좌측의 값이 20를, 그 다음이 2-1, 그 다음은 2-2… 입니다.

부호를 s, 지수부를 e, bias를 b, 가수부를 m 이라고 한다면 그 값은

(-1)s * 1.m * 2e - b

로 표현하면 됩니다.

6 을 부동 소수점 실수로 표현해봅시다.

6 = 1.5 * 2^2 = 0100 0000 1100 0000 = 40 C0

부동 소수점 실수를 우리가 일반적으로 사용하는 인텔 프로세서에서 float형으로 표현할 경우, 순서가 조금 다릅니다.

6.0f = 0000 0000 0000 0000 1100 0000 0100 0000 = 00 00 C0 40

이를 리틀 엔디안 표기법이라고 하구요, 이는 6번째 주제로 다루고 있으니 거기서 더 자세히 얘기해보도록 하고 넘어가죠.

데이터형 크기(바이트) 표현 범위
float 4 3.410-38~3.41038
double 8 1.710-308~1.710308
long double 10~16 1.210-4932~3.4104932

실수 형의 경우 값의 표현 범위가 넓은 대신 정밀도가 떨어지고 오차가 존재합니다. 그렇지만 고정 소수점 소수에 비해 표현 범위가 넓은 것이 장점이 되어 현재 사용되고 있습니다. 어떤가요? 도움이 조금 되시나요?

이렇듯 컴퓨터에서 사용되는 데이터들은 모두 비트 기반으로 이루어져 있습니다.

바이트로 구성된 파일

비트 8개가 모여서 만든 바이트가 모여서 만들어진 것이 파일입니다. 우리가 흔히 사용하는 이미지 파일들도 알고 보면 색상 정보를 담고 있는 바이트의 집합입니다. 여기서 조금 부연 설명을 하자면, BMP(비트맵)파일의 경우는 헤더나 컬러 테이블 정보도 담고 있긴 하지만, 일반적으로는 RGB색상 값만 가지고 있다고 보시면 됩니다. RGB 색상 값은 Red 1바이트, Green 1바이트, Blue 1바이트씩 저장됩니다.

색상 값을 가지고 있다가 프로그램에서 BMP파일을 읽어 들였을 때, 색상 정보를 읽어서 RGB색상 값을 조합 한 후 점을 찍어서 (BitBlt함수로도 찍을 수 있는데 라고 생각 하실 분도 있으시겠지만, BitBlt함수도 결국 내부적으론 점을 찍어서 표현해줍니다) 그 색상을 모니터에 표현하도록 명령을 내려주기 때문에, 우리 눈에는 컬러 이미지를 볼 수 있는 겁니다.

저장되어 있는 RGB색상 값을 아무런 압축도 하지 않고 모두 가지고 있는 경우가 위에서 설명한 BMP파일 포맷이고, JPG의 경우는 고도의 압축 기법(압축률도 지정 가능 합니다)을 통해서 용량을 줄였지만 결과적으로는 색상 값을 가지고 있다 신장(압축해제)후 화면에 뿌려주는 원리는 비슷합니다.

벡터 그래픽 파일의 경우에는 점, 선, 곡선 등의 정보를 저장하고 있는 포맷이지요. 그래서 화면에 축소나 확대에서도 같은 이미지를 볼 수 있는 것 이지요.

동영상 파일도 많은 양의 그림 정보를 담고 있다가, 1초에 몇 번 이상(1초에 몇 번 화면이 갱신되는지를 프레임이라고 하는데, 초당 24내지 30프레임은 되어야 깜빡임이 사람 눈에 보이지 않는다고 합니다. 일반적으로 모니터의 경우는 60번 이상 갱신되고 있죠)갱신되는지에 따라 그것을 재생시켜줍니다.

음악파일도 마찬가지로, 소리 정보를 디지털 값(수치 값)으로 가지고 있다가, 그 정보를 재생시간에 맞춰 재생하는 방식을 취하고 있죠.

Star Craft의 Replay 파일의 경우에도 비슷하게 첫 위치 값을 저장한 후에 거기서 변화한 값과 내린 명령,시간 값들을 저장했다가, Replay 메뉴에서 재생 시 그 정보를 바탕으로 게임을 재생시키는 방식을 취합니다. 그래서 Replay파일의 용량이 그다지 크지 않은 것입니다.

게임 세이브 파일의 경우에도, 그 캐릭터의 레벨, 무기 일람, 체력, 공격력, 방어력 등의 Parameter와, 그 캐릭터의 현재 위치, 플레이 시간 등의 정보를 저장하고 있습니다. 물론 Edit가 힘들 게 하기 위해 단순한 파일 구조(순차적)으로 구성하지 않는 경우가 대부분이긴 하지만요.

핵심은 모든 데이터는 바이트 단위로 저장된다는 것. 그 바이트를 구성하고 있는 것은 비트라는 것입니다.

비트 연산자

자 비트에 대해 배웠으니 비트 연산을 한번 해봐야겠죠? 기본적으로 C언어에서의 대입은, 데이터 형 단위로 제어가 되죠. 하지만, 비트 연산이란, 비트 단위로 데이터를 제어하는 것을 말합니다.

비트연산에 사용되는 비트연산자란 논리 곱 (&) , 논리 합 ( ), 논리 부정 (~), 베타적 논리합(^)이 있습니다. 부울 대수를 배우셨던 분들은 익숙하실 겁니다.

논리곱(AND : &) 둘 다 1일 때만 참(TRUE)이 됩니다.

입력 값 1 입력 값 2 결과
0 0 0
0 1 0
1 0 0
1 1 1
논리합(OR : ) 둘 중에 하나라도 1이면 참(TRUE)이 됩니다.
입력 값 1 입력 값 2 결과
0 0 0
0 1 1
1 0 1
1 1 1

베타적 논리합 (XOR : ^) 두수의 값이 달라야만 참(TRUE)이 됩니다.

입력 값 1 입력 값 2 결과
0 0 0
0 1 1
1 0 1
1 1 0

논리 부정 (NOT : ~) 0은 1로, 1은 0으로 만들어줍니다.

입력 값 결과
1 0
0 1

비트의 위치를 이동 시키는 시프트 연산자 (SHIFT) 라는 것도 있습니다.

좌측 시프트 연산자 («)는 비트를 왼쪽으로 옮겨주고, 빈자리엔 0을 넣어줍니다.

0011 0010 << 2

0011 0010 (10진수 50)에 «연산자로 비트를 왼쪽으로 두 번 옮겨보겠습니다.

1100 1000

왼쪽으로 2번 이동했더니 수가 10진수 200이 되었습니다. 시프트 하기 전 값에 4배 (50 x 4 = 200)가 되었죠? 비트를 왼쪽으로 한번 옮길 때마다 수가 2배가 된다는 것을 아실 수 있을 겁니다.

우측 시프트 연산자 (»)는 비트를 오른쪽으로 옮겨줍니다. 오른쪽 시프트의 경우는 좌측 시프트와는 다르게 항상 0 이 들어 가는 것이 아니라. (시프트 하는 데이터 타입이 무엇인가에 따라, 혹은 컴파일러에 따라 다릅니다) 일반적으로, unsinged 타입일 경우 -> 0 이 들어가고 (논리적 시프트), singed 타입일 경우 첫 비트가 1 이면 1을 0이면 0을 채우게 됩니다. (산술적 시프트)

0011 0011 >> 2

0011 0010 (10진수 50)에 >> 연산자로 비트를 오른쪽으로 두번 옮겨보겠습니다.

0000 1100

우측으로 2번 이동했더니 수가 0000 1100 (10진수 12)가 되었습니다. 4분의 1(12.5여야 하지만, 소수점 이하는 버립니다) 이 되었죠?

생각보다 간단한 압축

압축방법에는 Run Length Code, Huffman Code 등 다양한 방법이 있지만, 여기선 시프트 연산자와 비트 연산자의 조합으로 간단한 압축을 구현해보겠습니다.

아까 데이터를 압축하려면 현재 데이터에서 변화한 정보를 담는 것이 좋다고 했었죠? 그 원리를 이용하는 것입니다.

주로 다른 파일보다는 이미지, 음악 파일, 동영상 파일 등에 사용되는 데이터 압축의 원리는 이 바이트를 기본으로 한 데이터들이 비트로 이루어져있다는 것이 핵심입니다.

런 렝스 코드 (Run Length Code)는 특정 값이 몇번 반복되는지를 저장하는 방식입니다.

AAAABCCDDDEEEFFFFFFF

라는 데이터가 있다면,

A4B1C2D3E3F7

로 원래 데이터와 반복 횟수를 표기하는 방법입니다.

문자열 반복이 잦다면 압축률이 높겠지만, 문자열의 반복 빈도가 낮다면 런 렝스 코드를 적용했을 때 용량이 더 커지기도 합니다.

ABCDEFG

라는 문자열이 있을 때, 이 문자열을 런 렝스 코드를 사용하면,

A1B1C1D1E1F1G1

이렇게 반복이 없을 땐 오히려 용량이 두 배가 되기 때문이죠.

허프만 코드(Huffman Code)는 자주 사용되는 문자를 짧은 코드로, 덜 사용되는 문자를 긴 코드로 변환해서 압축하는 방법의 압축 법입니다.

AAAABCCDDDEEEFFFFFFF

위 데이터가 있을 때, 원래 대로 라면 21바이트가 필요하겠죠?

허프만 코드를 이용해서 표현해보겠습니다.

AAAA B CC DDD EEE FFFFFFF

반복 횟수가 적은 순에 따라 비교해가며 값을 대입해주면, 아래와 같은 결과를 얻을 수 있습니다. (같은 값 처리나, 트리 만들어 나가는 과정은 세부 알고리즘에 따라 다릅니다. 현재는 같은 값일 경우 왼쪽에 위치한 값을 더 작다고 판정하였습니다.)

데이터 변환된 값 변환된 비트 크기 반복횟수 소요 비트
F 0 1 6 6
A 10 2 4 8
E 110 3 3 9
D 1110 4 3 12
C 11110 5 2 10
B 11111 5 1 5

위 표와 같은 결과를 얻게 됩니다. 다 더하면 6 + 8 + 9 + 12 + 10 + 5 = 50바이트. 21바이트 였던 168 비트보다 무려 118 비트나 적게 소요되었죠?

이렇듯, 컴퓨터의 모든 데이터는 비트 기반 위에 존재 하기에, 비트의 존재를 꼭 기억해두세요.

C프로그래머가 알아야 할 것들 - 01 진법

C프로그래머가 알아야 할 것들 - 01 진법

왜 진법에 대하여 배워야 하는가?

진법이란, 수를 세는 방법을 의미합니다. 수를 셀 때, 0부터 몇까지 세는지를 의미한다고 생각하시면 됩니다.

우리가 수를 셀 때, 0~9까지 세지요? 이 것을 10진법이라고 부릅니다. 시간에서 분, 초 등을 셀 때 쓰이는 0~59까지 세는 방법을 60진법이라고 합니다. 이와 달리 컴퓨터는 전류가 흐를 때(1)와, 아닐 때(0) 밖에 구분하지 못하기 때문에, 0과 1로써 수를 세는데 이를 2진법이라고 부릅니다.

게임과 메신저, 음악 재생과 같은 것들이 0과 1만으로 된다는 게 말이나 되냐며 못 믿으실 분들도 계실 텐데요, 이 것은 챕터2 비트의 법칙에서 자세히 설명하도록 하겠습니다.

2진법

2진법(Binary)는 0과 1로써 수를 세는 방법입니다. 자리수가 2가 되었을 때 자리 올림을 하여 10으로 표시하는 수 체계죠.

2진수는 표현할 때,

0011(2)

위와 같이 표현 하곤 합니다. 괄호와 그 안에 숫자는 반드시 써야 하는 건 아니고, 혼돈의 여지가 있을 때만 사용 하곤 합니다.

2진수 10진수
0000 0
0001 1
0010 2
0011 3
0100 4
0101 5
0110 6
0111 7
1000 8
1001 9
1010 10

위의 표를 보시면, 대강 어떤지 감이 오실 겁니다. 10진수가 11일 때 2진수로는 몇이 될까요? 네 그렇죠~ 1011이 되는 겁니다. 그런데.. 2진수가 너무 읽기 힘들죠?

8 4 2 1
1 0 0 1 //자릿수가 1일 때마다, 머리 위에 위치한 수만큼 더해줍니다

8에 위치한 2진수가 1이니 8, 4와 2자리에 위치한 수는 0이니 그냥 내버려두고, 1의 자리에 위치한 수가 1이니 1. 그래서 나온 수인 8과 1을 더하면 9가 되는 거죠. 방금 전 방식보단 읽기 쉬워졌죠?

마찬가지로 이진수가 커질 때에도

128 64 32 16   8 4 2 1
0  0  0  0    0 0 0 0

이런 식으로, 수가 하나 증가할 때마다 수가 배로 증가하게 됩니다. 이렇게 하니 2진수 읽는 법은 감이 오죠?

그런데.. 음수를 표현할 땐 어떻게 해야 할까요? -0011

이런 식으로 하면 될 거라고 생각 하신 분 들이 많으실 것 같습니다.

아쉽게도, 컴퓨터 에서 수를 다룰 때는 -부호를 사용할 수 없습니다.

그래서 최상위 비트(MSB: 가장 앞에 있는 비트를 의미)를 부호 비트로 사용하게 되는 것이죠. 비트란 다음 강좌에서 배우게 될 Binary Digit 약자입니다.

최상위 비트가 0이면 양수, 1이면 음수로 표현 하기로 했습니다. 0001은 1, 1001은 -1 이렇게 말이죠.

그런데 이것은 문제가 있습니다. 2진수의 계산이 힘들어진 것이죠.

1001 + 0001 = 1010
(-1)   (1)   (0 이어야 함)

2진수의 덧셈에서 자리 올림수가 발생하면 일반 덧셈에서 자리 올림 수와 마찬가지로 처리해주면 됩니다.

그건 그렇고, 뭔가 이상하죠? 1010이라니…최상위 비트가 1이니 음수인 거 같고, 뒤가 010이니 -2인 것 같은데… 어? 0이 아니네요?

그래서 나온 것이 1의 보수입니다.

1의 보수란, 음수 표현 시에 0을 1로, 1을 영으로 모든 수를 반전 시키는 것이죠.

1의 보수로 표현하자면, 0001은 1, 1110은 -1이 되는 거죠.

자.. 아까 계산이 잘못되었던 -1에서 1을 더해볼까요?

1110 + 0001 = 1111
(-1)   (1)    (0이어야 함)

어? 여전히 뭔가 이상하죠?? 최상위 비트가 1이니 음수인 거 같고.. 111이라면 -7? 왜 또 0이 아니지? 또 문제가 있었습니다. 그래서 2의 보수란 것이 나오게 된 것입니다.

2의 보수란 0을 1로, 1을 0으로 모두 바꿔준 후에 1을 더하는 방법입니다.

0001은 1, 1111은 -1이 되는 것이죠.

그럼 다시.. 계속 잘못 되었던 계산인 -7에서 7을 더해봅시다.

1111 + 0001 = 10000
(-1)   (1)   (0이어야 함)

음.. 이번에도 최상위 비트가 1이군요. 그럼 음순가? 그런데 뒤가 0000이네요? 하지만 이 계산은 맞습니다. 맨 앞에 초과된 1은 무시하기 때문입니다. 그래서 0000 즉 0이 되는 것이죠.

왜 2의 보수가 사용되는지 아셨겠죠?

참고로 1의 보수의 또 다른 문제점은 0이 두 개이기 때문입니다. 1111 1111과, 0000 0000 각각 음수 0 양수 0을 의미합니다.

이에 비해 2의 보수는 0000 0000은 당연히 0이고, 0의 1의 보수인 1111 1111에서 1을 더하면 1 0000 0000이 되고, 이때 초과된 1은 무시하게 되므로 0은 양수 0 하나만 남게 되는 것이죠. 이로써 아까 -1에서 1을 더하는 계산에서의 초과된 1을 무시하는 이유도 함께 이해가 되시죠?

8진법

8진법(Octal)이란 0~7로 수를 세고, 자릿수가 7이 넘어 8이 되었을 때는 자리 올림 하여서, 10으로 표현하는 수 체계입니다.

8진수는 표기할 때,

12(8)

이런 식으로 작은 괄호로 8을 붙여서 표현하곤 하지만, 2진수와 마찬가지로 반드시 표기해줄 필요는 없습니다.

C언어 등의 고급 언어에서 사용시 일반적으로 숫자0을 붙여서 사용하죠.

012

아래 표를 보시면, 2진수, 8진수, 10진수의 관계를 아실 수 있을 겁니다.

2진수 8진수 10진수
0000 0 0
0001 1 1
0010 2 2
0011 3 3
0100 4 4
0101 5 5
0110 6 6
0111 7 7
1000 10 8
1001 11 9
1010 12 10

8진수는 그 자체적인 의미도 중요하지만, 2진수에서의 변환이 매우 편리하다는 장점이 있습니다. 바로 수를 세 개 단위로 묶어서 표현하면 되는 것이죠.

4 2 1
1 1 1 //7입니다

8진수로 바꿀 때는, 4+2+1=7이 되는 거죠.

이런 식으로, 두 자리 수 이상일 때도 마찬가지로,

4 2 1    4 2 1
1 1 0    0 1 1
4+2=6    2+1=3

8진수로 63이 되는 겁니다.

16진법

16진법(Hexadecimal)이란, 수를 0부터 15로 세다가, 16이 되어 자리 올림 시 10으로 표현하는 방법을 말합니다. 그런데…뭔가 조금 이상하죠? 0~15로 수를 센다면, 0~9는 괜찮겠지만, 10부터 15는 자리 올림 된 10~15와 같게 되니까요. 그래서 16진수에서는 0~9는 10진수와 같이 표기하고, 10~15는 A~F로 표현하게 됩니다.

16진수도 8진수 2진수와 마찬가지로,

AF (16)

위와 같이 표기할 수 있습니다.

C언어 등 고급언어에서 16진수 표현할 때 숫자 앞에 0x (숫자 0과 알파벳 x)를 붙여서 사용합니다.

0xAF

아래 표를 보시면 진법에 따른 수 표현 방식을 이해 하실 수 있을 겁니다.

2진수 8진수 10진수 16진수
0000 0 0 0
0001 1 1 1
0010 2 2 2
0011 3 3 3
0100 4 4 4
0101 5 5 5
0110 6 6 6
0111 7 7 7
1000 10 8 8
1001 11 9 9
1010 12 10 A
1000 13 11 B
1001 14 12 C
1010 15 13 D
1010 16 14 E
1010 17 15 F
1010 20 16 10

16진수도 8진수처럼 2진수에서의 변환이 쉽다고 했었죠?

16진수도 2진수를 8진수로 변환하는 것과 비슷합니다. 2진수 4개씩 묶어서 16진수로 변환하면 되는 것이죠.

8 4 2 1
1 1 0 0 //12입니다

8+4=12인데, 12는 알파벳 C로 표현하기로 했으니,

8+4=C로 표현할 수 있는 것이죠.

8 4 2 1    8 4 2 1
1 0 1 0    0 0 1 1
8+2=A      2+1=3

A3으로 4자리씩 묶어서 변환함으로써 2진수를 좀 더 읽기 쉽게 하는 것이죠. 기계어에서 표현하는 수 체계도 16진법이므로 알아두면 여러모로 도움이 되겠죠?

(서평) 레이몬드 첸의 윈도우 개발 282 스토리 - 윈도우의 현재를 말해주는 비하인드 스토리

윈도우 개발 스토리? 저는 매우 궁금했습니다. 맥이나 리눅스에 비해 윈도우가 압도적인 점유율을 갖고 있지만 그만한 인정은 못받고 있지만, 저는 윈도우가 매우 뛰어나고 M$라 불릴만큼 악덕 기업은 아니라고 생각하기 때문입니다. 

그들은 비주얼 스튜디오 만으로도 존경 받을만하죠. (MS만세~)

레이몬드 첸씨의 이름을 달고 나온 책인 만큼 그가 누구인지 매우 궁금했는데, 윈도우 개발에 수석 프로그래머 이셨더군요. 부럽습니다!

이 책은 주로 윈도우를 개발하며 겪은 에피소드와 왜 윈도우가 이렇게 돌아가는 지에 대한 항변(?)을 하는 식으로 이루어져 있습니다.

윈도우는 대부분 사용자 편의에 큰 가치를 두고 있고, 그 대표적인 방침중 하나가 하위 호환이라 할 수 있습니다.

하위 호환을 위해 유지해야 했던 문제 되는 함수들, 잘못된 사용법으로 인해 윈도우가 망가졌던 일, 그런 사용에도 망가지지 않기 위해 했던 예외처리 등 윈도우의 파란만장한 주옥같은 일화들이 많이 있었습니다.

그 중에서도 특히 심시티를 하위 호환하기 위해서 집어넣었던 예외 처리 코드가 정말 인상적이었는데요, 심시티가 그만큼 큰 파급효과를 가졌었다는 점에서 부럽기도 했습니다. (MS가 그 게임 하나만을 위한 코드를 작성해주다니… 쳇… 멋진데?)

WIN16 호환을 위한 수많은 역사와 얽혀있는 코드들에 대해서 설명해주는데, 다른 내용도 매우 많지만 hPrevInstance에 대해 이보다 자세하게 설명해준 책은 아마 없었던 것 같네요. (이 부분은 Windows API 정복을 비롯한 윈도우 프로그래밍 서적들에어느정도 설명이 나오기도 했지만 깊이가 다르다! 깊이가!)

설명 위주의 책이긴 하지만 조각 코드도 꽤 되고, 궁금했던 얘기들로 꼭 집어 설명해주는 것은 수석 프로그래머 였던 그가 아니면 불가능 했을 거란 생각이 드네요.

MS가 윈도우에서 가져간 정책의 밑바탕에는 사용자가 중심이었습니다. 어찌보면 당연한 얘기지만 내가 얼마나 그렇게 해왔나라는 반성이 들어 부끄럽기도 했습니다.

개발을 하다보면 기획자나 그래픽 디자이너가 힘든 방법이지만 내가 편하거나, 코드 상으로 간결한 해결책을 가져다 주는 방법을 선택하고 싶은 욕심이 생길때가 있고, 실제 그런 선택을 할 때가 있습니다.

어찌보면 프로그래머로써 우선시 여기는 유지보수 쉬운 코드, 퍼포먼스 좋은 코드 같은 가치 보다,  중요한 가치가 사용자나 팀웍이 될 수도 있다는 생각이 들기도 했습니다.

물론 이 책을 다 읽고 난 지금은 전보다 사용자나 팀을 위한 노력을 조금 더 기울이게 됐지만, 여전히 저에게 우선 되는 가치는 유지보수 쉬운 코드나 퍼포먼스 좋은 코드지만 말이죠. (….. -_-;; 제가 고집이 좀 쎕니다…쿨럭!)

이 책은 제가 전공서를 읽을때 조차 중요시 여기는 가치인 재미를 주는 책이었습니다. 마냥 윈도우 매뉴얼을 읽고 싶으신 분들은 MSDN을 찾아보세요. 파라미터 하나 하나 자세히 알려줄테니까요.

하지만 조금이라도 윈도우 프로그래밍 하실 여지가 있다면, 아니 윈도우를 조금 더 잘 사용하시려는 일반 사용자라고 하실지라도 윈도우의 기본 철학, 윈도우를 만들었던 과정들에 대해 알고 싶다면 이 책을 반드시 읽어보시기 바랍니다.