기계어

realprogrammers.jpg

좋은 뇌만 있다면 키보드가 매우 간단해도 된다....(물론 시간도 엄청 걸린다)
그래도 백스페이스는 넣어주지 그걸 '01'로 하겠지 저걸로 핑퐁은 할 수 있을까

1 개요

Machine Code. 프로그래밍 언어의 하나. 언어라고 하기엔 거시기하다.

인간에게 난해한 프로그래밍 언어지만, 이건 컴퓨터만을 위해 사용되는 언어다.

컴퓨터, 정확히는 CPU에게 던져주면 바로 해독할 수 있는 유일한 언어다. 쉽게 말해 기계의 언어. 비트단위로 표기하기 때문에 01로만 표현된다. 그 때문에 간단한 연산문도 작성하면 옆으로 길게 늘어져 개떡같은 가독성을 자랑한다. 좀 더 보기 쉽게 16진수로 변환하여 보여주기도 하는데 압도적인 길이가 줄어서 마음에 안정감만 줄 뿐, 거지같은 가독성은 마찬가지이다. 어떤 기계어의 경우 2진수에서 16진수로 가도 오히려 가독성이 떨어지기도 한다. 물론 ASCII나 유니코드로 변환하면 이 거지같은 가독성은 극단을 달린다(…). 그리고 궁극적으로 코드를 던져줘도 "그래서 하고 싶은 말이 뭐냐?"란 반응이 나올 정도로 해독하기가 난해하다. 하지만 고대(그러니까, 컴퓨터 시대 초창기)에는 이걸 컴퓨터처럼 해독하는 작자들도 많이 있었다고 한다. 대표적으로 빌 게이츠라거나, 안철수. 물론 이 분야의 원조 굇수인 존 폰 노이만기계어가 아닌 어셈블리어포트란 같은 다른 언어를 쓰는 것조차 못마땅해 했다. 기계는 기계와 말이 통하는가보다

엄밀히 말하면 기계어는 특정한 언어가 아니다. 단지 CPU 또는 MPU 제조사에서 CPU를 만들어 낼 때 해당 CPU에서 사용하는 명령어 집합을 공개하는데 이것을 기계어 라고 부를 뿐이다. 때문에 CPU가 변경되면 기계어가 달라지는 것이다. 같은 회사의 CPU라도 버전 별로 다른 명령을 포함할 수 있으며 다른 회사라도 같은 명령어집합을 공유할 수도 있다.(기계어 수준의 호환을 말한다.)

가독성은 0에 수렴하고 배우기도 힘들고 생산성도 심하게 낮다. 또 CPU 종류, CPU의 아키텍처에 따라서 같은 동작을 수행하는 코드라고 할지라도 완전히 다른 0과 1의 나열이 될 수 있기 때문에 이식성도 거지같다.[1] 간단히 CPU 바꾸면 기존에 작성한 기계어 코드가 안 돌아갈 수도 있다. 아주 기본적인 연산자들은 서로 호환이 되는 편이라서 운이 좋으면 돌아갈 수도 있으나 어지간한 수준의 프로그램들의 경우 호환될 확률이 한없이 0에 수렴한다. 이 때문에 CPU를 제작하는 업체에서는 새로운 CPU 아키텍처를 공개하고나면 기계어 코드를 함께 뿌린다. 일반인이 알아들을 수 있게 비유하자면, NDS 게임을 에뮬레이터 없이 PC에서 돌릴 수 없는 것과 같은 이치다.

프로그램 작동을 키보드 치는 것에 비유하자면, 기계어는 그걸 키보드의 특정 부위를 누르시오로 표현하는 것이라고 볼 수 있다.
예를 들어 "나무위키"라는 문장을 치기 위해 ㄴ을 눌러야 한다면, ㄴ을 입력하라는 말을 "키보드 중앙에서 좌로 10cm 밑으로 1cm을 누르시오"같이 표현하는 것이다. 상식적으로 그딴 식으로 얘기하는데 누가 그걸 "아, ㄴ을 누르는 거구나..."라고 생각하겠는가? 게다가 키보드라는 게 크기도 제각각 모양도 제각각이므로 키보드를 바꿨을 때도 ㄴ이 그 자리에 있다는 보장이 전혀 없으며, 일일이 "키보드 중앙에서 좌로 10cm 밑으로 1cm"이라고 설명하기도 힘들다. 따라서 가독성도 거지같고 호환성도 낮으며 생산성도 거지같은 특성을 지닌다. 하지만 결국 ㄴ을 입력하려면 (최소한 그 키보드에서는) "키보드 중앙에서 좌로 10cm 밑으로 1cm인 지점을 누르는" 행위가 반드시 필요하다. 따라서 기계어는 프로그램 작동의 가장 근본적인 부분인 것이다.[2] 그러나 "'ㄴ'을 누르시오"보다 "좌로 10, 밑으로 1cm 지점을 누르시오"가 훨씬 편리한 경우가 있는데 바로 당신이 로봇(이 비유에서는 CPU)일 경우이다. 로봇에게는 키보드의 'ㄴ'을 인식하는 기능을 탑재하는 것보다 이런 식으로 누름 동작을 실행할 물리적 위치를 알려주는 것이 훨씬 간단하다. 비유를 벗겨내고 말하자면 CPU 입장에서는 C언어 등의 (상대적으로) 고급 언어를 통해 코드를 입력받는 것보다 어셈블리어나 이진수 코드(어셈블리어는 일대일대응을 이루는 이진수 코드가 있다)를 그냥 입력받는 것이 더 간단하다는 거다.[3]

유니코드아스키 코드같은 코드와는 또 다르다. 이런 코드는 '이 수는 이 문자에 대응한다'하는 약속을 정해놓은 것이다. 컴퓨터는 수(數)만을 다룬다는 사실을 기억하자.

사실 컴퓨터 아키텍처를 연구하는 사람이 아니라면 직접 접해볼 기회는 거의 없다. 다만 매트릭스에서처럼 영상에서 뭔가 디지털스러움(?)을 어필하고 싶을 때 배경에 촤르륵 하며 흘려보내는데 쓰이긴 한다.

이 문제를 커버하기 위해서 등장한 것이 어셈블리어. 어셈블리어의 경우에는 모든 기계어 코드와 1:1로 대응되는 형태로 되어있다. 문제는 어셈블리어도 상당히 난해하고 생산성이 떨어지다보니 점점 사람이 보고 이해하기 쉽고, 생산성이 높은 프로그래밍 언어들이 개발되기 시작하였다. 그러다보니 등장한 것이 객체지향 언어.

2 배울 수 있는 방법

컴퓨터공학과에서 컴퓨터 구조론 및 어셈블리어를 배우면 된다.
또는 당신이 직접 CPU를 설계하면 배울 수 있다.

전문적으로 기계어를 가르치는 학교나 학원은 존재하지 않는다. 시대가 어느땐데 정 배우고 싶다면 해당 CPU제조사의 홈페이지의 매뉴얼에 아주 자세하게 서술되어있으므로 그걸 보고 공부하도록 하자.물론 영어다 CPU가 제공하는 인스트럭션(기계어 단위)중 자주 쓰는 인스트럭션 몇십개 정도를 외운다면 아주 불가능한 것은 아니다. 물론 어셈블러를 쓰는 것이 훨씬 빠르고 건강에 이롭다.

대학교에서 컴퓨터 전공이라면 컴퓨터 아키텍처에 관해 배울 때 맛만 보는 수준으로 배울 수 있다. 그리고 학교에 시스템 프로그래밍과 관련된 과목이 있으면 상당히 친숙해지게 된다. 시장 영향력이 강한 x86이나 ARM을 주로 다루는 편이다.

물론 16진수로 된 인스트럭션들을 생으로 보진 않고 번역된 어셈블리어를 통하여 공부하게 된다. 어셈블리어와 기계어는 1:1로 대응관계가 있기 때문에 어셈블리어를 공부하는 것만으로도 기계어를 알 수 있고 무엇보다 훠~~~얼씬 보기 편하기 때문에 일반적인 경우 기계어를 생으로 쓸 일은 없다고 해도 과언이 아니다. 물론 전혀 없는 건 절대 아니다. 자신의 코드를 스스로 바꿔서 전염하는 다형성 바이러스를 분석하거나 제작(!)할 때 순수 기계어 포맷에 대한 지식이 필요하긴 하다. 더 접하기 쉬운 예를 들자면 에뮬레이터나 버추얼라이저를 구현하기 위해서도 필요하다. 각각의 아키텍처에 맞는 기계어 코드를 경우의 수에 따라 번역해줘야 하므로[4] 기계어로 모든 걸 코딩할 일은 없다해도 어쨌든 싱싱한 생 기계어를 쓰긴 써야 한다. 이런 것들을 하다보면 C언어가 세상에 존재하는 것은 신의 축복이었단 사실을 깨닫게 된다. 물론 이 생각은 컴파일러를 배울 때 깨진다(…).

여담으로, 정말로 기계어를 배워보고 싶다면 마인크래프트의 레드스톤 논리회로를 사용해 직접 CPU를 설계하면 된다. 8비트나 16비트 CPU는 양덕들이 맵 파일을 올려놓은 게 있으니 다운받아 돌려볼 수도 있다. 비트 수가 늘어날수록 한 번에 세팅해야 할 레버의 숫자가 8개, 16개, 32개로 늘어나니까 웬만하면 4비트나 8비트 CPU 맵파일을 받자. 간단한 덧셈 동작을 하려고 해도 입력해야 할 인스트럭션 수가 십여 개는 되므로 8비트 CPU조차 100여회의 레버 조작이 필요하다. 그리고 난이도를 반드시 평화로움으로 하자. 크리퍼가 ALU근처에서 터지거나 엔더맨이 회로를 집어가기도 한다(!) ... 디버깅은 곡괭이로 한다.

3 기계어의 간단한(?) 예

x = 10+2
y = x+4

이 표현을 MIPS라는 아키텍처의 기계어로 옮기면 다음과 같다.

001001 11101 11101 1111111111111000
001000 00001 00000 0000000000001010
001000 00001 00001 0000000000000010
101011 11101 00001 0000000000000000
001000 00010 00001 0000000000000100
101011 11101 00010 0000000000000100
001001 11101 11101 0000000000001000

토나온다.
가독성을 높이기 위해서 보통은 4자리씩 끊어서 16진수로 표현한다. 그래서 그렇게 써보자면 다음과 같다.

27BDFFF8
2020000A
20210002
AFA10000
20410004
AFA20004
27BD0008

보기는 좀 편하지만 그래봤자 여전히 뭔 소린지 알 수 없다.[5] 이걸 빠르게 읽어나갈수 있다면 당신은 머리안에 CPU가 장착된 자렘인일지도 모른다...

다음은 리눅스 환경, x86 아키텍쳐를 이용한 hello world 코드.출처(breadbox의 코드)

7F 45 4C 46 01 01 01 00 00 00 00 00 00 00 00 00 02 00 03 00 01 00 00 00 35 40 B3 04 2C 00 00 00 00 00 00 00 00 00 00 00 34 00 20 00 01 00 00 00 00 00 00 00 00 40 B3 04 B2 0C EB 1C 62 00 00 00 62 00 00 00 05 00 00 00 00 10 00 00 48 65 6C 6C 6F 20 77 6F 72 6C 64 0A B9 4C 40 B3 04 93 CD 80 EB FB

이게 무슨 소리야!

4 생산성

4.1 어셈블리 vs 기계어

실상은 어셈블리어나 기계어나 속도는 똑같다[6]

부연설명을 하자면 프로그래밍 언어를 CPU에서 읽고 해독하려면 기계어로 번역해야만 하는데, 어셈블리어는 위에 언급했듯 명령어가 기계어와 1:1 대응된다.

즉, 성능을 위해 기계어로 된 프로그램을 최적화하는 거나 어셈블리어로 된 프로그램을 최적화하는 거나 결국 똑같다는 얘기. 때문에 최적화를 위해 어셈블리어로 코딩하는 일은 간혹 있지만 최적화를 위해 기계어를 쓰는 사람은 이 세상에 존재하지 않는다.뭐 에드삭(최초로 폰 노이만 구조를 사용한 컴퓨터)수준 컴퓨터라면 몰라도 그리고 프로그래머들은 알겠지만 성급한 최적화 시도는 만악의 근원이다. 진짜로.

4.2 생산성 vs 최적화

현대의 고급언어와 기계어를 쉽게 비유하자면, 현대의 고급 프로그래밍 언어는 포크레인 같은 것이며 기계어는 완벽한 티스푼 같은 수준이다. 포크레인은 작업속도(생산성)는 매우 빠르지만 세세한 작업(최적화)은 하기 힘들고, 티스푼은 작업속도는 매우라는 표현도 부족할 정도로 느리지만 세세한 작업을 할 수 있다.
고급언어가 기계어에 비해서 느린 편이긴 하지만, 요즘은 하드웨어 성능이 좋아 단점을 커버하는게 충분히 되기 때문에, 고급언어를 쓰는게 당연하다.

일단 무엇보다도 그냥 딱 보면 알겠지만 타이핑해야 하는 문자의 개수가 어셈블리어나 고급언어 쪽이 훨씬 적다. 거기에 타이핑만 많으면 다행이고, 코딩 시 항상 나오는 불가항력을 고려하면... 안습. 한 마디로 CPU를 직접 제어해야 할 필요성 없이 복잡한 프로그램을 기계어로 짜는 것은 그냥 '잉여짓'이다.

심지어 C를 이용해서 프로그램을 짜도 처리속도차이는 기계어와 몇 배 정도밖에 안 난다. [7] NASA 미션 같이 극단적으로 낮은 성능을 커버하기 위해 어셈블리로 쥐어짜야하는 경우를 제외한다면, 같은 시간복잡도라면 컴퓨터 과학에서 몇 배 차이는 크지 않다. 즉 0.003초든 0.006초든 충분히 빠르다.

오히려 0.006초를 0.003초로 만들기 위한 대가가 3주 걸려 만들 것을 3달 걸려 만드는 것이라면 결코 이득이라고 할 수 없다. 그리고 초짜면 컴파일한 결과가 더 빠를 수도 있다. 오히려 3주 걸려 만들 것을 3일만에 만들기 위해 C언어보다 몇십배 느린 고생산성 언어를 쓰는 판국.

7,80년대에는 아직 어셈블리나 기계어를 직접 건드리기도 했는데 이 당시는 개인용 PC의 CPU가 느려터졌기 때문에 최적화를 할 필요가 있었던 것이다. 당시 DOS에 포함되어 있던 debug.exe를 실행하면 기계어로 직접 프로그래밍할 수 있었다. 그리고 최근에는 컴파일러 기술도 발전했기 때문에 잘 모르면서 어설프게 손으로 최적화 하는 것보다 컴파일러가 만들어준 코드를 믿는 쪽이 더 낫다.

5 여담

훨씬 복잡한 예제들을 보고 싶다면 계산기나 메모장의 실행파일을 텍스트 에디터로 열어보자. 메모장으로 열면 외계어 폭풍에 절로 몸사리 칠 것이다(……). 심지어는 C 처음 배울 때 만드는 HelloWorld 예제조차 열어보면 수십수백줄은 사뿐히 넘어주신다.[8]

폰 노이만은 제자들이 컴파일러와 고급언어를 만들라치면 '이렇게 은혜로운 기계를 가지고 연구는 안 하고 잔머리를 굴린다'고 노발대발 했다고 한다. 본인은 0과 1로만 프로그래밍해서 프로그램을 잘 돌렸다고 한다. 왜 이걸 못하지?

6 픽션

헛소리 시리즈쿠나기사 토모가 프로그래밍할 때 쓰는 언어가 이 기계어라는 설정이다. 쿠나기사 토모의 PC는 메인보드부터 자작이고 OS도 자작[9]이라, 기계어로 코딩하는 것도 딱히 이상하지는 않다. 쿠나기사가 사용하는 CPU가 현재 가장 많이 사용되는 x86이나 x86-64와 구조가 같을 거라는 보장 자체가 없기 때문이다. 아키텍처가 다른 CPU + 자작 OS 환경에서 본인이 어셈블리나 고급언어를 만들어 두지 않았다면 닥치고 기계어로 코딩하는 수밖에 없을 수도 있다. 대신 이렇게 만든 시스템 자체의 보안성은 궁극 그 이상이 된다. 근데 작중에서는 가상머신 쓴다...?

뭐, 사실 CPU를 자작한다는 말은 등장하지 않았기 때문에 반박하려면 얼마든지 반박할 수 있긴 하다.[10] 그런데 이걸 두고 현실성이 없는 설정이니 뭐니 하면서 비판하는 것은 다소 억지스러운데, 애초에 메인 보드를 자작하는 19세 여자아이라는 설정 자체가 현실성이 있는 설정이 아니라는 점을 상기하자. 애초에 헛소리 시리즈는 고증이 필요한 현실성에 크게 의미를 두는 작품도 아니다. 소녀를 암살단으로 키우는 고등학교 따위가 나오는 작품인데 뭐.

Warhammer 40,000의 설정에서 아뎁투스 메카니쿠스테크프리스트들은 Lingua-technis라는 이진 코드로 된 언어를 사용한다고 한다. 쉽게 말하자면 4만년대판 기계어로, 자연어에 존재하는 오류 없이 복잡한 기술 자료를 파일을 보내는 것 처럼 아주 간편하게 통신할 수 있다고 한다. 언어 자체에 ECC[11]가 포함되어있다. 인간 컴퓨터 오오 기본적으로 링구아-테크니스를 듣고 말할 수 있게 하는 사이버네틱 이식이 필요한데다가, 외부인이 해독하려 해도 기계교 안에서만 내려져오는 전승을 통해 (2차대전 당시 나바호족 언어 암호마냥) 추가로 암호화가 걸려있어서 아직 문법조차 밖으로 알려지지 않았다고 한다.
  1. 당연히 operator도 다르고, operand가 2개인 아키텍쳐도 있고 3개인 아키텍쳐도 있으며, 어디서부터 어디까지가 operator고 operand인지도 다르고, 애초에 아키텍처가 16비트인지 32비트인지 64비트인지에 따라서 명령어의 비트 길이부터(!) 다르다. 어셈블리어 코드 형태로 보면 뭐 ADD SUB같은 기본적 명령어는 거의 똑같은 형태니 비슷하다고도 생각할 수 있겠지만 비트스트림으로 보면 이건 뭐...
  2. 여기서 더 나아가 'ㄴ을 누르시오' 수준으로 추상화시킨 게 어셈블리라고 볼 수 있다. 이를 더 사람이 알기 쉽게 '나무위키 라고 입력하시오' 수준으로 나아간게 흔히 말하는 고급 프로그래밍 언어이며, 타자 숙련도에 의한 손의 빠르기, 타법의 효율성 등은 컴파일러의 성능이라고 볼 수 있겠다. 때문에 일반 분야에서의 프로그래밍은 추상화 개념이 발생하는 방향으로 발전하게 된다.
  3. 당연한 사실인데, CPU 입장에서 써먹을 수 있는 것은 기계어이고, 높은 레벨의 언어 및 API에 가까울수록 다양하고 복잡한 로직을 가지므로 이를 기계어로 해석하는 과정에서 비효율적 요소가 발생하는 것은 어쩔 수가 없다. 그나마 컴파일러의 성능이 높아지면서 심각한 차이를 보이지 않는 것 뿐이다.
  4. 당연하게도 에뮬레이터나 버추얼라이저로 수행해야 하는 파일들은 친절하게 어셈블리어 따위로 구성돼 있지 않다.
  5. 예로 든 MIPS 아키텍쳐의 경우는 각각의 2진수 필드들이 깔끔하게 4비트나 8비트로 떨어지지도 않기 때문에(MIPS의 R-type 명령어(가산 계열 명령어)들은 op필드 6비트, rs/rt/rd 필드 5비트, shamt필드 5비트, funct필드 6비트로 구성되어 있다.) 오히려 16진수로 표현하면 뭔 소리인지 더욱 알기 힘들다.
  6. Reversing: Secrets of Reverse Engineering 中.
  7. 이것도 이 분야의 전문가일 경우에만. 자신이 컴파일러보다 똑똑할 자신이 없다면 기계어 쪽이 오히려 더 느리다. 현대 CPU의 처리 속도를 극대화 하기 위해서는 파이프라이닝 같은 CPU의 최적화 테크닉이 원활하게 돌아갈 수 있도록 기계어 코드 수준에서 최적화가 되어있어야 한다. 옛날이라면 모를까 2010년대 기준으로 이것을 모두 이해하고 실제 코딩까지 할 수 있는 개발자는 드물다.
  8. 하지만 엄밀히 말하면 실행파일(윈도에선 .exe 파일)은 기계어만으로 되어 있는 게 아니라, PE 이미지라고 해서 운영체제가 램에 올릴 때 참고하는 정보들도 함께 들어있는 파일이다. 이 외에도 여러 이미지 형태가 있다.
  9. 작중에서는 일단 가상머신을 함께 사용하는 것으로 보인다.
  10. 가령 자작 OS와 메인보드라고 해봤자 x86 구조의 CPU를 사용하고 적당히 리눅스 계열 자작OS를 사용하는 것뿐일수도 있고 그렇게 보면 고급언어를 얼마든지 사용할 수 있다. 그러나 자세한 설정이 나오지 않은 한 이렇게 생각하는 것 자체가 AD HOC이다. 작가가 기계어 쓴다는데 뭐 어쩌라고(...).
  11. Error Correction Code 혹은 Error Correcting Code 데이터속 오류를 검출하는 코드 혹은 그것을 정정까지 할수있는 코드를 말한다. 또한 그 기능이 있는 하드웨어를 의미하기도 한다.