[[컴퓨터공학|컴퓨터 과학 & 공학
Computer Science & Engineering
]]- [ 펼치기 · 접기 ]
- ||<tablebgcolor=#fff,#1c1d1f><tablecolor=#373a3c,#ddd><colbgcolor=#0066DC><colcolor=white> 기반 학문 || 수학( 해석학 · 이산수학 · 수리논리학 · 선형대수학 · 미적분학 · 미분방정식 · 대수학( 환론 · 범주론) · 정수론) · 이론 컴퓨터 과학 · 암호학 · 전자공학 · 언어학( 형태론 · 통사론 · 의미론 · 화용론 · 음운론) · 인지과학 ||
하드웨어 구성 SoC · CPU · GPU( 그래픽 카드 · GPGPU) · ROM · RAM · SSD · HDD · 참조: 틀:컴퓨터 부품 기술 기계어 · 어셈블리어 · C/ C++ · C# · Java · Python · BIOS · 절차적 프로그래밍 · 객체 지향 프로그래밍 · 해킹 · ROT13 · 일회용 비밀번호 · 사물인터넷 · 와이파이 · GPS · 임베디드 · 인공신경망 · OpenGL · EXIF · 마이크로아키텍처 · ACPI · UEFI · NERF · gRPC · 리버스 엔지니어링 · HCI · UI · UX · 대역폭 · DBMS · NoSQL · 해시( SHA · 브루트 포스 · 레인보우 테이블 · salt · 암호화폐) · RSA 암호화 · 하드웨어 가속 연구
및
기타논리 회로( 보수기 · 가산기 · 논리 연산 · 불 대수 · 플립플롭) · 정보이론 · 임베디드 시스템 · 운영 체제 · 데이터베이스 · 프로그래밍 언어{ 컴파일러( 어셈블러 · JIT) · 인터프리터 · 유형 이론 · 파싱 · 링커 · 난해한 프로그래밍 언어} · 메타데이터 · 기계학습 · 빅데이터 · 폰노이만 구조 · 양자컴퓨터 · 행위자 모델 · 인코딩( 유니코드 · MBCS) · 네트워크 · 컴퓨터 보안 · OCR · 슈퍼컴퓨터 · 튜링 머신 · FPGA · 딥러닝 · 컴퓨터 구조론 · 컴퓨터 비전 · 컴퓨터 그래픽스 · 인공지능 · 시간 복잡도( 최적화) · 소프트웨어 개발 방법론 · 디자인 패턴 · 정보처리이론 · 재귀 이론 · 자연어 처리( 기계 번역 · 음성인식) · 버전 ( 버전 관리 시스템 · Git · GitHub)
1. 개요
機 械 語 / Machine Code / Machine language
기계어(Machine language)로도 알려진 기계 코드(Machine Code)는 컴퓨터( CPU)가 별다른 해석(컴파일) 없이 읽을 수 있는 프로그래밍 언어로, JS가 브라우저를 제어하듯이 기계어는 CPU를 직접 제어한다. 인간의 입장에선 난해해 보일 수 있지만, 이건 컴퓨터만을 위해 사용되는 언어다. 그러니까 CPU에게 입력했을 때 CPU가 바로 해독할 수 있는 유일한 언어다. 쉽게 말해 모든 프로그래밍 언어의 종착지.
기계 코드는 엄밀히 말하면 숫자 언어(numerical language)이며 프로그래머를 위한 CPU에 대한 가장 낮은 수준의 인터페이스다. 어셈블리어는 숫자 기계 코드(numerical machine code)와 인간이 읽을 수 있는 버전 간의 직접 매핑(direct mapping)을 제공하며, 숫자 명령 코드(numerical opcode)와 피연산자는 인간이 읽을 수 있는 문자열로 대체된다.
모든 프로그래밍 언어는 이 기계어를 인간이 이해하고 사용하기 쉽도록 번역하고 축약하고 풀이한 것으로, 어떤 프로그래밍 언어든지 입력된 뒤 최종적으로는 기계어로 번역되어 CPU에 전달되어서 컴퓨터가 인간이 의도한 작업을 수행하게 한다.[1]
비트 단위로 표기하기 때문에 0과 1로만 표현된다. 그 때문에 간단한 연산문도 작성하면 옆으로 길게 늘어져 매우 낮은 가독성을 자랑한다.
2. 컴퓨터 이전~컴퓨터 시대 초창기
컴퓨터라는 물건이 나오기도 전에 고트프리트 폰 라이프니츠가 2진법을 고안해 기계어에 대한 이론적 토대를 만들었다.이후 에니악이라는 최초의 컴퓨터가 만들어졌는데 배전반에서 배선을 조작하는 방식으로 입력하고 전구의 켜짐/꺼짐으로 결과를 확인했다. ENIAC 문서 참고. 그래서 컴퓨터 시대 초창기에는 이걸 컴퓨터처럼 해독하는 능력자들도 많이 있었다.
1980년대까지만 해도 기계어를 직접 입력해서 프로그램을 짜는 것은 이상하지 않았다. 당시에는 잡지에 니모닉과 기계어가 나오는 건 예사고 MS-DOS 팁에 DEBUG 명령어를 써서 16진수를 입력하고 .COM 파일을 직접 작성하는 내용이 올라왔다.
3. 특성
알기 쉽게 0, 1이라고 표현했지만, 엄밀히 말해서 숫자(데이터)는 아니다. 정보는 결국 물리적으로 저장되는데, 이 상태가 '반응 없음·있음', '낮음·높음', '역방향·정방향', 'OFF·ON', '저전압(GND)·고전압(5V)', '음·양'같이 그냥 있고 없고 정도의 '개념'일 뿐이다. 다만, 인간이 '개념'을 받아들이기 위해서는 문자든 음성신호든 개념을 이해시킬 수단이 필요하기 때문에 그냥 직관적으로 0, 1로 표현한 것뿐이다. 참고로 CD 같이 빛을 이용한 저장매체는 빛이 산란되는 부분이 0, 빛이 반사되는 부분이 1이 되고, 마그네틱 테이프 같이 자성을 이용한 경우에는 음극을 0, 양극을 1이라고 취급한다. 일상생활에서 쉽게 확인할 수 있는 예시는 바코드로 여기서는 검은 부분이 0, 흰 부분이 1. 그러니까 2진수 체계가 기계어가 될 수 있어도 2진수에 쓰이는 0, 1이라는 숫자 자체가 기계어가 될 수는 없다는 말.좀 더 보기 쉽게 16진수로 변환하여 보여주기도 하는데 압도적인 길이가 줄어서 마음에 안정감만 줄 뿐, 거지 같은 가독성은 마찬가지이다. 어떤 기계어의 경우 2진수에서 16진수로 가도 오히려 가독성이 떨어지기도 한다. 물론 ASCII나 유니코드로 변환하면 이 거지 같은 가독성은 극단을 달린다. 그리고 궁극적으로 코드를 던져줘도 "그래서 하고 싶은 말이 뭐냐?"란 반응이 나올 정도로 해독하기가 난해하다.
이와 같이 기계어는 초창기 컴퓨터의 명령어 입력 방법 중 하나였다.
엄밀히 말하면 기계어는 특정한 언어가 아니다. 단지 CPU 또는 MPU 제조사에서 CPU를 만들어 낼 때 해당 CPU에서 사용하는 명령어 집합을 공개하는데, 이것을 '기계어'라고 부를 뿐이다. 때문에 CPU가 변경되면 기계어가 달라진다. 이를테면 같은 회사의 CPU라도 버전별로 다른 명령을 포함할 수 있으며[2] 반대로 다른 회사라도 같은 명령어 집합[3]을 공유할 수도 있다.[4] 같은 관점에서, 어떠한 소스 코드를 컴파일하여 얻어지는[5] 목적 코드 역시 기계 코드와 완전 동일하지는 않다. 목적 코드와 기계 코드는 둘 다 2진수로 이루어진 바이너리 포맷이라는 점에서는 동일하지만, 목적 코드가 링커에 의해 처리될 때 함수나 헤더 파일 등의 실제 메모리 주소를 코드에 반영하는 과정에서 일부 주소값이 변경되기 때문이다. 간단히 말해 생긴 건 기계어처럼 똑같이 0101로 생겼지만 실행은 안 된다.
가독성은 0에 수렴하고 배우기도 힘들고 생산성도 심하게 낮다. 또 CPU 종류, CPU의 아키텍처에 따라서 같은 동작을 수행하는 코드라고 할지라도 완전히 다른 0과 1의 나열이 될 수 있기 때문에 이식성도 형편없다.[6] 간단히 CPU 바꾸면 기존에 작성한 기계어 코드가 안 돌아갈 수도 있다. 아주 기본적인 연산자들은 서로 호환이 되는 편이라서 운이 좋으면 돌아갈 수도 있으나 어지간한 수준의 프로그램들의 경우 호환될 확률이 거의 없다. 이 때문에 CPU를 제작하는 업체에서는 새로운 CPU 아키텍처를 공개하고 나면 기계어 코드를 함께 뿌린다. 콘솔이나 아케이드 게임을 에뮬레이터 없이 PC에서 돌릴 수 없는 것과 같은 이치다.
프로그램 작동을 키보드 입력으로 비유하면 사람은 해당 문자를 직접 보고 찾아서 누르는 것이고, 기계어는 해당 문자가 존재하는 장소를 찾아서 입력하는 방식이다.
예를 들어 "A"를 입력한다고 해 보자. 인간이라면 A 글쇠를 찾아서 누르면 된다. 기계어에서는 이것을 "밑에서부터 셋째줄, 왼쪽에서부터 두 번째에 있는 글쇠를 누르시오."라고 표현하는 것이다. 그러나 상식적으로 생각해서, 사람끼리 소통할 때에는 그런 식으로 이야기할 이유가 전혀 없다. 게다가 키보드의 모양이나 키 배열이 다르다면 A 글쇠가 동일한 위치에 있으리라는 보장도 없다. 해독이 필요한 언어이자 호환성이 낮은 언어인 이유가 바로 이것이다.
하지만 A를 입력하는 행동이 "밑에서부터 셋째줄, 왼쪽에서부터 두 번째에 있는 글쇠를 누르는" 행동이라는 점에는 변함이 없다.[7] 즉 기계어는 어떤 프로그램이 작동할 때 가장 근본적인 것을 표현하는 셈이다. 만약 "A 글쇠를 누르십시오" 정도로 표현해준다면 그것이 어셈블리쯤 된다고 할 수 있다. 사람이 알기 쉽게 하려면 단어를 통째로 보여주고 "다음의 단어를 입력하시오"라고 하는 편이 훨씬 빠르기 때문에, 그 정도까지 구현한 것이 고급 프로그래밍 언어이다. 단어보다 정보량이 많은 문장이나 문단을 빠르게 입력할 수 있도록 하는 것은 컴파일러의 성능에 비유할 수 있다.
인간 입장에서, 정보량이 많을수록 의미를 함축적으로 전달하는 것이 프로그래밍 효율이 오르기 때문에, 일반적으로 프로그래밍은 의미의 압축이 많아지고 추상적 개념이 등장하는 쪽으로 발전한다.[8] 그러나 로봇의 경우에는 압축되어 있는 추상적 언어보다 직설적이고 즉각적인 언어가 훨씬 이해하기 쉽다.[9] 그래서 CPU 입장에서는 C언어 같은 고급 프로그래밍 언어를 통해 코드를 입력받는 것보다 어셈블리어나 이진수 코드[10]를 입력받는 쪽이 훨씬 빠르고 간단하다.[11]
유니코드나 아스키 코드 같은 '코드'와는 다르다. 이런 코드는 '이 수는 이 문자에 대응한다'하는 약속을 정해놓은 것이다. 컴퓨터는 수(數)만을 다루기 때문이다.
매트릭스 시리즈에서처럼 영상에서 뭔가 디지털스러움(?)을 어필하고 싶을 때 배경에 촤르륵하며 흘려보내는 데에 쓰이기도 한다.
이 문제를 커버하기 위해서 등장한 것이 어셈블리어. 어셈블리어의 경우에는 모든 기계어 코드와 1:1로 대응되는 형태로 되어있다. 문제는 어셈블리어도 상당히 난해하고 생산성이 떨어지며, CPU 아키텍처가 달라지면 이전에 짠 코드가 호환되지 않거나, 코드의 실행이 가능해도 정상적으로 기능하지 않는 파편화가 발생한다.[12] 그러다보니 점점 사람이 보고 이해하기 쉽고, 생산성이 높은 프로그래밍 언어들과 그러한 언어를 기계어로 번역해주는 컴파일러들이 개발되기 시작하였다. 생산성이 높다는 의미는 추상화 수준이 올라간다는 의미이고 더 쉽게 풀어쓰면 한 줄의 코드가 함축하는 의미가 늘어난다는 의미이다. 현대 프로그래밍 언어는 한 줄의 코드가 수천만 개 이상의 기계어 코드에 대응하기도 한다. 즉 소스 코드의 크기는 10바이트 정도이지만 그것을 컴파일한 실행 바이너리의 크기가 메가바이트 이상의 크기가 된다.
리누스 토르발스가 처음 리눅스 커널을 만들 때 부트 블록에 해당하는 첫 512바이트는 어셈블리어로 코딩했다. 현실에서도 FPGA로 DSP[13]를 제작하는 사람은 그 칩의 프로토타입 초기형까지는 테스트 프로그램의 코딩을 기계어로 한다. 기계어로 테스트해서 버그 없고 안정적이다 판단되면 그때서야 어셈블러와 C언어를 개발한다.[14] DSP의 구조가 특이해서 C언어 사용이 불가능할 경우에도 어지간하면 어셈블러까지는 만든다. 어셈블러는 만들기 쉽고[15] 기계어는 코딩하는 본인이 귀찮기 때문.
4. 배울 수 있는 방법
어셈블리어는 기계어를 알파벳으로 변환한 것이므로 어셈블리어를 배우는 게 기계어를 배우는 것이다. 어셈블리어로 짠 코드를 기계어로 치환하면 그게 바로 기계어로 짠 코드다.[16] 어셈블리어는 컴퓨터공학과에서 컴퓨터 구조론을 연구하거나 시스템 해킹, 임베디드 시스템 등을 다룰 때 배우게 된다. 아주 기초적인 컴파일러조차 돌아갈 수 없는 수준에서의 코딩이나 컴파일 과정에서 발생할 수 있는 오류 혹은 해킹 기법 등을 배울 때 어셈블리어에 대한 지식이 필요하기 때문. 따라서 아래의 내용은 어셈블리어(=기계어)에 대한 내용이다.먼저, 기계어를 배우기만 하는 것과 실제로 쓸 수 있을 정도까지 실력을 키우는 건 전혀 다른 문제다. 기계어가 사람에게 비직관적이라 어렵게 느껴지는 거지, 각각의 기계어가 무엇을 하는지만 배우는 정도라면 오히려 고급 프로그래밍 언어보다도 공부할 양만 따지자면 적다. 애초에 CPU 레벨에서 제공되는 기능이 그렇게 많지가 않다. 하지만 그것들을 조합해 어느정도 복잡한 프로그램을 만들어보려 치면 기계어 특유의 비직관성과 불친절함으로 난이도가 치솟는다. 예를들어 어셈블리어로 작성된 코드를 보면서, 이제 막 기계어를 배우는 사람도 각각의 기계어가 무엇을 하는지 어느정도는 설명할 수 있다. 하지만, 그래서 그 기계어가 프로그램에 있어서 실제 어떤 의미가 있으며 왜 필요한지까지 파악하는 건 전혀 다른 문제가 된다.
기계어와 어셈블리어는 임베디드 코딩[17]을 제외하면 딱히 쓰일 일이 없으므로[18] 어셈블리어를 전문적으로 배우는 학원은 딱히 존재하지 않는다. 어셈블리어에 접근해보려면 학부생 수준의 공부가 필요하다. 대학에서 컴퓨터과학 전공이라면 컴퓨터 아키텍처에 관해 배울 때 맛만 보는 수준으로 배울 수 있다. 이후 학과에 시스템 프로그래밍과 관련된 과목이 있거나 본인이 임베디드 개발 쪽으로 대학원을 진학한다면 상당히 친숙해지게 된다.
어셈블리어와 기계어가 서로 동치관계이긴 하지만, 어셈블리어를 넘어서 순수 기계어 포맷에 대한 지식이 필요한 경우가 전혀 없는 건 아니다. 자신의 코드를 스스로 바꿔서 전염하는 다형성 바이러스를 분석하거나 제작하는 경우라면, 순수 기계어 포맷에 대한 지식이 필요하긴 하다. 조각조각난 기계어 코드를 일일이 어셈블리어로 변환하여 읽는건 귀찮기 때문에 자주 보이는 명령어들은 자연스레 외워지기도 한다. 더 접하기 쉬운 예를 들자면 에뮬레이터나 버추얼라이저를 구현하기 위해서도 필요하다. 각각의 아키텍처에 맞는 기계어 코드를 경우의 수에 따라 번역해줘야 하므로[19] 기계어로 모든 걸 코딩할 일은 없다해도 어쨌든 싱싱한 날것(?)의 기계어를 쓰긴 써야 한다. 이런 것들을 하다보면 C언어, C++ 같은 것이 세상에 존재하는 것은 신의 축복[20]이었단 사실을 깨닫게 된다. 물론 이 생각은 컴파일러를 배울 때 깨진다.
또는 직접 CPU를 설계하면 배울 수 있다. 근래에는 FPGA로 직접 하드웨어를 설계하는 일도 드물지 않게 되었다. FPGA같은 진지한 응용이 아니더라도, 마인크래프트를 하는 게이머들은 레드스톤 논리회로를 사용해 직접 CPU를 설계하기도 한다. 8비트나 16비트 CPU는 양덕후들이 맵 파일을 올려놓은 게 있으니 다운받아 돌려볼 수도 있다. 비트 수가 늘어날수록 한 번에 세팅해야 할 레버의 숫자가 8개, 16개, 32개로 늘어나니까 웬만하면 4비트나 8비트 CPU 맵파일을 받자. 간단한 덧셈 동작을 하려고 해도 입력해야 할 인스트럭션 수가 십여 개는 되므로 8비트 CPU조차 100여회의 레버 조작이 필요하다. 이쪽도 거의 극한까지 가서 현재는 마인크래프트가 돌아가는 수준까지 만들어졌다.[21]
5. 예시
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진수로 표현한다. 그래서 그렇게 써보자면 다음과 같다.
27 BD FF F8
20 20 00 0A
20 21 00 02
AF A1 00 00
20 41 00 04
AF A2 00 04
27 BD 00 08
보기는 좀 편하지만 그래봤자 여전히 무슨 소린지 알 수 없다.[22] 이걸 빠르게 읽어나갈수 있다면 머리 속에 CPU가 장착된 사람일 것이다. 아래는 앞서 본 기계어 코드를 MIPS 어셈블리로 옮긴 것이다. 한 번 비교해 보자.
addiu $sp, $sp, -8 # ; $sp는 스택 포인터를 저장하는 레지스터이다.
addi $1, $0, 10 # $1 = 10 ; $0은 상수 0을 나타내는 레지스터 기호이다.
addi $1, $1, 2 # $1 += 2 ;
sw $1, 0($sp) # x = $1 ; x는 stack의 최상단에 저장된다.
addi $2, $1, 4 # $2 = $1 + 4 ; x의 값이 레지스터 $1에 저장돼 있으므로 그대로 사용한다.
sw $2, 4($sp) # y = $2 ; y는 stack의 최상단에서 x의 크기(4바이트)만큼 아래 저장된다.
addiu $sp, $sp, 8 #
addi $1, $0, 10 # $1 = 10 ; $0은 상수 0을 나타내는 레지스터 기호이다.
addi $1, $1, 2 # $1 += 2 ;
sw $1, 0($sp) # x = $1 ; x는 stack의 최상단에 저장된다.
addi $2, $1, 4 # $2 = $1 + 4 ; x의 값이 레지스터 $1에 저장돼 있으므로 그대로 사용한다.
sw $2, 4($sp) # y = $2 ; y는 stack의 최상단에서 x의 크기(4바이트)만큼 아래 저장된다.
addiu $sp, $sp, 8 #
반면 이것을 현재 가장 객체지향적 언어라는 Python으로 적어보자.
X = 10 + 2
Y = X + 4
print(X, Y)
print 명령어로 출력되는 결과값 : 12, 16
Y = X + 4
print(X, Y)
print 명령어로 출력되는 결과값 : 12, 16
다음은 리눅스 환경, 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
위의 코드 98바이트 중 앞의 53바이트는 ELF 헤더 및 프로그램 헤더로, 실제 x86 코드 부분은 다음과 같다:
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
Python의 Hello, world! 코드
print("Hello World")
6. 생산성
6.1. 어셈블리어 vs 기계어
실상은 어셈블리어나 기계어나 실행 속도는 똑같다.[23] 하지만 코드를 짜는 시간은 어셈블리어가 기계어보다 훨씬 적게 걸린다.부연설명을 하자면 프로그래밍 언어를 CPU에서 읽고 해독하려면 기계어로 번역해야만 하는데, 어셈블리어는 위에 언급했듯 명령어가 기계어와 1:1 대응된다.
즉, 성능을 위해 기계어로 된 프로그램을 최적화하는 거나 어셈블리어로 된 프로그램을 최적화하는 거나 결국 똑같다는 얘기. 때문에 최적화를 위해 어셈블리어로 코딩하는 일은 간혹 있지만 최적화를 위해 기계어를 쓰는 사람은 이 세상에 존재하지 않는다. 존 폰 노이만은 제자들에게 기계어로 코드 짜면 되지 왜 자원을 낭비하면서 어셈블리어 따위를 만드냐고 했지만, 물론 그도 이제 존재하지 않는다. 자동차를 끈과 망치로 튜닝하는 사람이 없는 것과 비슷한 이유이다.
6.2. 생산성 vs 최적화
현대의 고급언어와 기계어를 쉽게 비유하자면, 현대의 고급 프로그래밍 언어는 포크레인 같은 것이며 기계어는 계량스푼 같은 수준이다. 포크레인은 작업속도(생산성)는 매우 빠르지만 세세한 작업(최적화)은 하기 힘들고, 계량스푼은 작업속도는 매우 느리지만 세세한 작업을 할 수 있다.고급언어로 만들어진 프로그램이 기계어로 바로 만들어진 프로그램에 비해서 느린 편이긴 하지만, 그것도 옛날 이야기다. 요즘은 하드웨어 성능이 좋아졌기도 하고, 컴파일러 또한 많은 최적화가 진행되었으므로 일반적인 환경에서는 고급언어를 안 쓸 이유가 별로 없다. 오히려 어중간한 실력으로 하드웨어에 대한 완벽한 이해 없이 기계어나 어셈블리로 코딩하겠다고 덤비면, 컴파일러의 최적화보다 속도가 훨씬 더 느려지기도 한다. 컴파일러는 당신이 생각하는 것 그 이상의 일을 하고 있다는 사실을 항상 명심할 것.
일단 무엇보다도 그냥 딱 보면 알겠지만 타이핑해야 하는 문자의 개수가 어셈블리어나 고급언어 쪽이 훨씬 적다. 거기에 코딩 시 항상 나오는 불가항력인 버그까지 고려하면, 한 마디로 CPU를 직접 제어해야 할 필요성 없이 복잡한 프로그램을 기계어로 짜는 것은 그냥 '잉여짓'이다. 특히 크로스 플랫폼을 고려한다면 CPU 마이크로아키텍처 간 이식성이 0인 기계어와 어셈블리어는 고려 대상이 전혀 되지 못한다.
70~80년대에는 아직 어셈블리나 기계어를 직접 건드리기도 했는데 이 당시는 개인용 PC의 CPU가 느려터진데다가 컴파일러가 아직 멍청하던 때라 최적화를 하지 않으면 요구 성능을 만족시키지 못 할 정도로 느렸기 때문이다. 예를 들어 게임의 경우 어셈블리어를 동원하지 않으면 플레이에 지장을 줄 정도의 프레임드랍이 일어나거나 심하면 아예 로딩조차 할 수 없었다. 게다가 칩 제조사가 어셈블러를 제공하지 않는 경우도 있어서 이 경우에는 닥치고 기계어를 써야만 했다. 21세기 현대에 70년대 개발 환경을 체험해보고 싶다면 임베디드 프로그래밍쪽을 공부해보면 된다. 다만 임베디드 쪽도 컴파일러만큼은 최신 기술이 적용돼있어 임베디드 개발자도 기계어는 최적화할 때 잠깐 쓰는 정도이다. 물론 상기한 바대로 본인이 직접 칩을 설계했다면(주문형반도체) 본인이 직접 컴파일러를 만들지 않는 이상 기계어를 쓸 수밖에.
7. 여담
훨씬 복잡한 예제들을 보고 싶다면 계산기나 메모장의 실행파일을 텍스트 에디터로 열어보자. 메모장으로 열면 외계어 폭풍[24]에 절로 몸서리치게 될 것이다. 심지어는 C 처음 배울 때 만드는 Hello World 예제조차 열어 보면 수십 수백 줄은 사뿐히 넘어주신다.[25]존 폰 노이만은 제자들이 컴파일러와 고급언어를 만들라치면 '이렇게 은혜로운 기계를 가지고 연구는 안 하고 잔머리를 굴린다'고 노발대발했다고 한다. 본인은 0과 1로만 프로그래밍해서 프로그램을 잘 돌렸다고 한다.[26] 심각한 수학 빠돌이인 폰 노이만에게 숫자만으로 작동하는 기계어는 일종의 완벽한 그 무엇으로 보였을 것이다. 물론 폰 노이만이 21세기에 재림하더라도 기계어로 Win32 애플리케이션을 짜 보라고 하면 아마도 절대 못 할 것이다. 일단 타이핑해야 하는 0과 1의 갯수가 최소 수백만 개 이상이다. 초기 프로그램은 매우 단순했기 때문에 기계어가 가능했을 뿐.
8. 대중 매체에서
헛소리 시리즈의 쿠나기사 토모가 프로그래밍할 때 쓰는 언어가 이 기계어라는 설정이다. 쿠나기사 토모의 PC는 메인보드부터 자작이고 OS도 자작이라[27],기계어로 코딩하는 것도 딱히 이상하지는 않다. 쿠나기사가 사용하는 CPU가 현재 가장 많이 사용되는 x86이나 AMD64와 구조가 같을 거라는 보장 자체가 없기 때문이다. 아키텍처가 다른 CPU + 자작 OS 환경에서 본인이 어셈블리어나 고급언어를 만들어 두지 않았다면 닥치고 기계어로 코딩하는 수밖에 없을 수도 있다.Warhammer 40,000의 설정에서 아뎁투스 메카니쿠스의 테크-프리스트들은 Lingua-technis라는 이진 코드로 된 언어를 사용한다고 한다. 쉽게 말하자면 4만년대판 기계어로, 자연어에 존재하는 오류 없이 복잡한 기술 자료를 파일을 보내는 것 처럼 아주 간편하게 통신할 수 있다고 한다.
마션에서 마크 와트니가 지구와의 통신을 위해 로버를 해킹하는 과정에서 프로그램을 편집하기 위해 HEX 에디터를 활용해 기계어를 편집해 기능을 추가한다.
[1]
의도하지 않은 작업이 수행되는 경우는
버그라고 부른다.
[2]
퀄컴 스냅드래곤,
삼성 엑시노스 등
ARM 기반 CPU들이 대표적이다.
[3]
기계어 수준의 호환을 말한다.
[4]
인텔 코어 i 시리즈,
AMD RYZEN 시리즈 등
AMD64 기반 CPU들이 대표적이다.
[5]
정확히 말하면
어셈블러에 의해 생성되는
[6]
당연히 operator도 다르고, operand가 2개인 아키텍처도 있고 3개인 아키텍처도 있으며, 어디서부터 어디까지가 operator고 operand인지도 다르고, 애초에 아키텍처가 16비트인지 32비트인지 64비트인지에 따라서 명령어의 비트 길이부터 다르다. 어셈블리어 코드 형태로 보면, ADD SUB같은 기본적 명령어는 거의 똑같은 형태니 비슷하다고도 생각할 수 있겠지만, 비트스트림으로 보면 답이 없어진다.
[7]
모양과 키 배치가 동일한 키보드일 경우에 한정된다.
[8]
여기서 추상적 개념이라는 것은
추상화 같은 느낌이 아니라 "철수야, 와서 3번 자리에 앉아, 숟가락을 들고 네 앞에 있는 국을 향해 팔을 내린 후 손을 들어 국물이 흘리지 않도록 입을 가져다 댄 후...~"를 "철수야 자리에 앉아 밥먹어라" 이런 느낌이다.
[9]
정확히는 로봇을 설계하는 인간 엔지니어가 기능을 구현하기 쉬운 것이다.
[10]
어셈블리어와 일대일로 대응하는 코드
[11]
보다 엄밀히 말하자면 이진수 언어로밖에 작동하지 않으므로, 의미가 압축된 고급 언어로 명령을 전달했을 경우 이것을 기계어로 해석하는 과정이 별도로 필요하다. 복잡한 로직을 가진 프로그래밍 언어를 기계어로 해석하려면 어쩔 수 없이 비효율적 과정이 끼어들게 되며, 이것은 작업속도를 저하시키는 이유가 된다. 요즘에는 그나마 컴파일러의 성능이 좋아지면서 속도저하가 눈에 띄지 않을 뿐이다.
[12]
CPU의 클럭 속도가 달라진다는 예상 없이 짠 프로그램을 하위호환성은 있지만 클럭 속도가 더 빠른 CPU로 구동했을 때, 작동 속도가 너무 빨라 제대로 사용하지 못하는 현상이 대표적인 예이다. 90년대 초에는 이런 현상을 해결하기 위해 클럭을 기존 CPU 수준으로 낮춰놓은 상태로 동작하되
터보 버튼을 눌러서 해당 CPU에서 지원하는 최고 속도로 높일 수 있게 하였다.
[13]
디지털 신호처리기
[14]
단 C언어 컴파일러의 프론트엔드는 이미 있는 걸 쓰고 백엔드의 코드 제네레이터 부분만 만든다. 그 외 렉서, 파서, AST옵티마이저 등은 건드리지 않고 있는 것 그대로 쓴다.
[15]
극단적으로 표현하면 문자열 치환 로직과 변환표만 있으면 만들 수 있는 수준이다.
[16]
어떻게 치환하는지는 앞서 언급했듯이 CPU마다 다르며 CPU 제조사의 홈페이지의 매뉴얼에 자세하게 서술되어 있으므로 그걸 토대로 치환하면 된다. 말그대로 단순 치환이므로 컴퓨터공학적 지식이 필요한 작업은 아니다.
[17]
흔히 '디지털'이라 불리는 기기들에 쓰이는 프로그램을 짜는 것을 말한다. 실생활에서 자주 볼 수 있는 전자레인지, 세탁기, 디지털 시계나 공장에서 흔히 볼 수 있는 스마트 팩토리 시스템 같은 것이 임베디드 개발의 결과물이다.
[18]
그나마도 컴퓨팅 기술의 발달로 어셈블리어를 쓸 일은 정말 없어졌다. 어지간한 임베디드 개발도 C언어로 하는 경우가 많다.
[19]
당연하게도 에뮬레이터나 버추얼라이저로 수행해야 하는 파일들은 친절하게
어셈블리어 따위로 구성돼 있지 않다.
[20]
그렇다고 C, C++이 절대 쉬운 건 아니다.
[21]
물론 100% 기계어로 만든 건 아니고, 고급 언어에 해당하는 명령 블록이 다수 사용되었다.
[22]
예로 든 MIPS 아키텍처의 경우는 각각의 2진수 필드들이 깔끔하게 4비트나 8비트로 떨어지지도 않기 때문에(MIPS의 R-type 명령어(가산 계열 명령어)들은 op필드 6비트, rs/rt/rd 필드 5비트, shamt필드 5비트, funct필드 6비트로 구성되어 있다.) 오히려 16진수로 표현하면 뭔 소리인지 더욱 알기 힘들다.
[23]
Reversing: Secrets of Reverse Engineering 中
[24]
다만 이는 기계어를 억지로
UTF-8 또는 그밖의 '텍스트용' 인코딩으로 가정하고 보여주기에 한자나 한글 같은
유니코드 할당 비율이 많은 문자나
� 기호로 뒤덮인 결과일 뿐이다. 즉
인코딩 에러에 불과하며 그냥 동영상이나 이미지 파일을 열어도 비슷하게 보인다. 기계어를 좀더 유의미하게 보고 싶다면 전용 헥스 에디터를 사용하거나
디셈블러를 사용해 어셈블리로 변환해야 한다.
[25]
하지만 엄밀히 말하면 실행파일(윈도에선 .exe 파일)은 기계어만으로 되어 있는 게 아니라, PE 이미지라고 해서 운영체제가 램에 올릴 때 참고하는 정보들도 함께 들어있는 파일이다. 이 외에도 여러 이미지 형태가 있다.
[26]
참고로 폰 노이만은 영어사전을 통째로 달달 외웠을 정도로 날고 기던 인류 역사상 최고의 천재 중 한 명이었다.
[27]
작중에서는 일단 가상머신을 함께 사용하는 것으로 보인다.
[28]
Error Correction Code 혹은 Error Correcting Code 데이터속 오류를 검출하는 코드 혹은 그것을 정정까지 할수있는 코드를 말한다. 또한 그 기능이 있는 하드웨어를 의미하기도 한다.