기계어
고친 사람 github-actions[bot]
기계어는 프로세서에게 일을 시키는 명령입니다. 「두 수를 더해라」 같은 아주 작은 동작 하나가 명령 하나입니다. 사람이 쓴 코드는 누가 바꿔 주지 않으면 한 줄도 실행되지 않습니다. 그래서 프로그램이 도는 동안 프로세서가 실행하는 것은 언제나 기계어입니다.
쉽고 빠른 이해
기계어는 프로세서가 번역 없이 곧바로 실행하는 명령입니다. 「이 두 수를 더해라」처럼 아주 작은 동작 하나가 명령 하나입니다. 명령은 글자가 아니라 숫자로 적혀 있습니다.
프로세서는 회로로 만들어져 있어 미리 정해 둔 숫자에만 반응합니다. 사람이 읽는 문장은 넣어 줄 수 없으니, 누군가 그 숫자로 바꿔 주지 않으면 프로그램이 돌지 않습니다.
어떻게 도나:
- 컴파일러가 소스 코드를 기계어 명령으로 옮깁니다
- 옮긴 명령을 실행 파일에 담아 배포합니다
- 프로세서가 명령을 하나씩 읽어 실행합니다
대가는 프로세서 계열마다 명령을 적는 숫자가 다르다는 점입니다. 한 계열에 맞춰 만든 실행 파일은 다른 계열에서 안 돕니다. 사람이 읽기도 어려워 도구를 거쳐야 무슨 명령인지 보입니다.
상세
내가 건네는 쪽지에는 요리사의 손놀림이 차례대로 적혀 있을 수도, 먹고 싶은 음식 이름만 적혀 있을 수도 있습니다. 무엇을 건네든 부엌에서 움직이는 것은 요리사가 몸에 익힌 손놀림뿐입니다. 갈리는 것은 그 차례가 누구의 것인가입니다.
프로세서가 할 줄 아는 동작이 기계어입니다. 명령을 알아듣는 것은 기계 안의 CPU(Central Processing Unit, 중앙 처리 장치)입니다. 프로세서는 이 CPU 를 가리킵니다. 사람이 쓴 소스 코드는 손대지 않은 채로는 한 줄도 실행되지 않습니다.
명령이 하는 일
기계어 명령 하나는 아주 작은 동작을 가리킵니다. 두 수를 더하기, 메모리에서 값 하나를 읽어 오기, 다음에 실행할 곳을 다른 데로 옮기기 같은 것입니다.
명령은 두 부분으로 되어 있습니다. 무엇을 하라는 부분과 무엇을 가지고 하라는 부분입니다. 앞을 연산 부호, 뒤를 피연산자라고 부릅니다. 둘 다 숫자로 적히므로 명령 하나는 결국 수 몇 개입니다.
피연산자가 가리키는 값은 대개 레지스터에 들어 있습니다. 레지스터는 프로세서 안에 있는 아주 작은 저장 칸입니다. 명령이 다룰 값을 잠깐 올려 두는 데 씁니다. 메모리보다 프로세서에 가까이 붙어 있어 명령이 바로 꺼내 씁니다.
명령 하나가 이렇게 작다 보니 사람이 한 줄로 쓴 계산도 기계어로는 여러 명령이 됩니다. 값 둘을 읽어 오는 명령, 더하는 명령, 결과를 되돌려 쓰는 명령으로 갈라집니다.
명령을 처리하는 순환
프로세서는 프로그램을 한꺼번에 보지 않습니다. 명령 하나를 가져와 처리하고 다음 것을 가져오기를 되풀이합니다.
지금 어느 명령을 할 차례인지는 프로그램 카운터라는 레지스터가 들고 있습니다. 다음에 실행할 명령이 놓인 메모리 주소를 담아 두는 레지스터입니다. 명령을 하나 가져올 때마다 그다음 명령을 가리키도록 옮겨 갑니다.
flowchart TD
A["명령을 가져온다"] --> B["무슨 명령인지 해석한다"]
B --> C["시키는 동작을 한다"]
C --> D["프로그램 카운터를 다음 명령으로 옮긴다"]
D --> A
이 순환은 전원이 들어와 있는 동안 멈추지 않습니다. 프로그램이 끝나도 프로세서는 운영체제의 명령을 같은 식으로 이어서 처리합니다.
조건에 따라 갈라지는 코드도 이 순환 안에서 처리됩니다. 프로그램 카운터를 다음 명령이 아니라 다른 주소로 옮기는 명령이 따로 있습니다. 그 명령이 반복문과 조건문의 밑바탕입니다.
프로세서 계열마다 다른 명령어 집합
어떤 명령이 있고 그 명령을 어떤 숫자로 적는지는 프로세서 계열마다 정해져 있습니다. 명령을 모아 부를 때는 명령어라고도 합니다. 그 약속 전체를 명령어 집합이라고 부릅니다.
명령어 집합이 다르면 같은 동작도 다른 숫자로 적힙니다. 그래서 한 계열에 맞춰 만든 실행 파일은 다른 계열에서 실행되지 않습니다. 파일이 깨져서가 아니라 알아듣는 말이 달라서입니다.
책상 위 컴퓨터와 서버에는 x86 계열이 흔합니다. 휴대폰과 태블릿에는 ARM(Advanced RISC Machine) 계열이 들어갑니다. 같은 프로그램을 양쪽에서 돌리려면 양쪽 기계어로 각각 만들어 두어야 합니다.
이름에 든 RISC(Reduced Instruction Set Computer, 축소 명령어 집합 컴퓨터)는 명령의 종류를 적게 두고 하나하나를 단순하게 만드는 설계를 말합니다.
지금 쓰는 기계와 다른 계열의 기계어를 만들어 내는 것을 크로스 컴파일이라고 합니다. 노트북에서 만든 프로그램을 작은 임베디드 기기에 올릴 때 이렇게 합니다.
어셈블리어와 기계어
기계어는 숫자라서 사람이 펴 봐도 읽히지 않습니다. 그래서 명령마다 짧은 이름을 붙여 사람이 읽을 수 있게 적은 것이 어셈블리어입니다.
어셈블리어는 기계어 명령과 한 줄씩 짝을 이룹니다. 다른 언어처럼 한 줄이 여러 명령으로 풀리지 않습니다. 한 줄이 명령 하나로 옮겨집니다. 아래는 값 둘을 읽어 더한 뒤 되돌려 쓰는 대목을 사람이 읽는 꼴로 적어 본 것입니다.
mov r1, [x] // 메모리의 x 를 r1 로
mov r2, [z] // 메모리의 z 를 r2 로
add r1, r2 // r1 에 r2 를 더함
mov [y], r1 // 결과를 메모리의 y 로
네 줄이 하는 일은 y = x + z 한 줄과 같습니다. 사람이 쓰는 한 줄이 기계어로는 이렇게 늘어섭니다.
어셈블리어를 기계어로 바꾸는 프로그램이 어셈블러입니다. 거꾸로 기계어를 어셈블리어로 되돌리는 프로그램은 디스어셈블러입니다. 소스가 없는 실행 파일이 무슨 일을 하는지 들여다볼 때 뒤쪽을 씁니다.
소스 코드에서 기계어까지
사람이 쓴 코드가 기계어가 되는 길은 하나가 아닙니다. 언제 옮기느냐에 따라 셋으로 갈립니다.
첫째는 빌드 때 미리 다 옮겨 두는 길입니다. 컴파일러가 소스 코드를 기계어로 옮깁니다. 그 결과를 하나로 묶어 실행 파일로 만들어 둡니다. 실행할 때는 그 파일을 메모리에 올리기만 하면 됩니다.
둘째는 빌드 때 중간까지만 옮겨 두는 길입니다. 소스를 바이트코드까지 옮겨 둡니다. 바이트코드는 실제 프로세서가 아니라 가상 머신이 읽는 명령입니다.
가상 머신은 소프트웨어로 만들어 낸 기계 한 벌입니다. 실제 기계가 아니라 가상 머신에 맞춰 옮겨 두었으니, 가상 머신이 깔린 곳이면 어디서든 같은 파일이 돕니다.
가상 머신은 자주 도는 구간만 골라 실행 도중에 기계어로 옮기기도 합니다. 미리가 아니라 그때그때 옮긴다고 해서 이것을 JIT 컴파일(Just-In-Time Compilation, 제때 컴파일)이라고 합니다.
셋째는 아예 옮겨 두지 않는 길입니다. 인터프리터가 소스를 읽어 가며 곧바로 처리합니다. 이때 프로세서가 실행하는 기계어는 프로그램의 것이 아니라 인터프리터 자신의 것입니다.
flowchart TD
S["소스 코드"] --> A["빌드 때 옮긴다"]
S --> B["빌드 때 바이트코드까지"]
S --> C["옮겨 두지 않는다"]
A --> AM["프로그램의 기계어"]
B --> BM["가상 머신의 기계어"]
C --> CM["인터프리터의 기계어"]
AM --> P["프로세서가 실행한다"]
BM --> P
CM --> P
세 길은 옮기는 때가 다릅니다. 프로세서가 도는 동안 실행하는 것은 어느 길이든 기계어이고, 갈리는 것은 그것이 누구의 기계어인가입니다.
첫째 길에서 도는 것은 프로그램 자신의 기계어입니다. 셋째 길에서 도는 것은 인터프리터의 기계어입니다. 프로그램은 끝까지 기계어가 되지 않습니다.
둘째 길은 가상 머신의 기계어로 돕니다. JIT 컴파일을 얹으면 자주 도는 구간만 프로그램 자신의 기계어가 됩니다.
관련 항목
기계어 명령을 정의하는 규격
명령어 집합 · 명령어 집합 구조 · x86 · ARM · RISC-V · RISC · CISC
기계어를 실행하는 하드웨어 부품
CPU · 레지스터 · 프로그램 카운터 · 산술 논리 장치 · 명령어 파이프라인 · CPU 캐시
소스 코드를 기계어로 옮기는 도구
컴파일러 · 어셈블러 · 링커 · 목적 파일 · 크로스 컴파일 · 빌드
기계어를 사람이 읽는 꼴로 되돌리는 도구
디스어셈블러 · 어셈블리어 · 디버거 · 역공학 · 코어 덤프
기계어로 옮기기를 미루는 실행 방식
바이트코드 · 가상 머신 · 인터프리터 · JIT 컴파일 · AOT 컴파일 · WebAssembly
기계어가 담겨 배포되는 파일과 그 처리 단계
실행 파일 · ELF · 로더 · 공유 라이브러리 · ABI · 네이티브 코드
기계어를 적는 데 쓰는 단위와 표기
다른 이름: machine code · machine language · 기계 코드 · 기계 언어