[카카오테크 부트캠프] CS 프리코스 03. 명령어

주영진·2026년 5월 5일
post-thumbnail

이번 장에서는 개발을 하면서 작성된 소스 코드들이 어떻게 컴퓨터를 동작시키는 명령어로 변환되는지에 대해 다룬다.

1. 핵심 개념 3개

  • 작성된 소스 코드들은 인간이 이해하는 고급 수준의 언어이고, 컴퓨터는 내부적으로 이 언어들을 저급 언어(기계어, 어셈블리어)로 변환하여 받아들인다.
  • 고급 수준의 언어는 저급 언어로 변환되는 방식에 따라 컴파일 언어, 인터프리터 언어, 그리고 이 둘의 장점을 합친 하이브리드 언어로 변환된다.
  • 컴퓨터의 명령어는 연산 코드(명령)와 operand(명령에 의해 움직이는 데이터)로 이루어지고, operand field에는 주로 사용될 데이터의 주소가 담긴다.

2. 내 말로 설명하기

개발할 때 쓰이는 프로그래밍 언어(ex. JAVA, Python, C)는 고급 수준의 언어이고, 컴퓨터는 이걸 저급 언어로 변환하여 받아들인다.

즉, 컴퓨터가 이해하고 실행하는 언어를 저급 언어라고 한다. 저급 언어에는 기계어와 어셈블리어가 있다.

  • 기계어는 0과 1로 이루어진 명령어로 구성된 저급 언어이다.

  • 0과 1로 이루어진 기계어를 읽기 편한 형태로 번역한 저급 언어가 어셈블리어이다.

경우에 따라서 어셈블리어를 직접 소스 코드에 넣어서 코드를 작성하는 경우도 있다.

고급 언어는 저급 언어로 변환되는 방식에 따라 컴파일 언어, 인터프리터 언어로 구분된다.

  • 컴파일 언어: 컴파일 언어는 작성된 소스코드가 '컴파일러'에 의해 저급 언어로 변환되고, 이 과정을 '컴파일' 이라고 한다.

컴파일러는 소스 코드를 한 번에 통째로 번역한다. 쉽게 말하자면 컴파일 과정은 번역본을 한번에 다 미리 만들어두고 읽는 방식으로 이해하면 된다. 대표적인 컴파일 언어에는 C, C++, Go등이 있다.

  • 인터프리터 언어: 소스 코드가 인터프리터에 의해 한 줄씩 읽어가며 바로바로 실행되는 언어이다. 따라서 소스 코드 전체가 저급 언어로 변환될 때까지 기다릴 필요가 없게 된다.

매번 읽어들여야 하기에 실행 속도는 상대적으로 느리지만, 즉각적으로 코드 실행 결과를 확인할 수 있어 개발 속도는 상대적으로 빠르다. 대표적인 인터프리터 언어에는 python, Javascript 등이 있다.

위 그림처럼 컴파일 언어의 경우, 전체 소스 코드 중 단 하나의 에러만 있어도 컴파일러가 즉시 중지하고 전체 소스 코드의 저급 언어 변환을 시도하지 않는다. 반면에 인터프리터 언어는 한줄한줄 읽어들이기 때문에 에러가 있는 부분 전까지는 코드가 실행된다.

컴파일 언어와 인터프리터 언어 방식의 장점을 합친 하이브리드 언어 형태도 존재한다.

이러한 언어는 소스 코드를 바로 기계어로 변환시키지 않고 '바이트코드'로 1차 컴파일을 한 후에 가상 머신이 이를 읽어 실행하는 방식이다. 대표적인 언어에 Java(JVM 위에서 돌아간다), Kotlin이 있다.

명령어는 '연산 코드''오퍼랜드(operand)'로 구성된다.

수행할 연산이 연산 코드이고, 연산에 사용될 데이터 또는 저장된 위치가 operand이다.

따라서 위와 같은 어셈블리어도 연산 코드와 operand로 구성되어 있다는 것을 확인할 수 있다. operand에는 연산에 사용될 데이터 보다는 연산에 사용될 데이터가 저장된 '위치'가 주로 담겨서 다른 말로 '주소 필드'라고도 한다. 그 이유는 전체 명령어가 담길 수 있는 용량이 제한되어 있기 때문에 operand에 담길 수 있는 데이터의 크기 또한 제한되기 때문이다.

따라서 위 이미지처럼 실제 저장되는 위치는 따로 두고, opearnd에는 주소만 담기게 되는 것이다. 위 이미지에서 실제 데이터가 저장된 실제 위치인 '10번지'가 '유효 주소'가 된다.

이처럼 명령어 연산에 사용될 데이터가 저장된 위치(주소)를 찾는 방법을 '명령어 주소 지정 방식'이라고 한다.

여러 가지 대표적인 명령어 주소 지정 방식들을 알아보자.

  • 즉시 주소 지정 방식: 연산에 사용될 데이터를 opearnd field에 직접적으로 명시하는 방식이다. 앞서 말했듯이 저장될 데이터의 크기가 작아져야 하지만 속도는 빠르다.

  • 직접 주소 지정 방식: opearnd field에 유효 주소를 직접적으로 명시하는 방법이다.

  • 간접 주소 지정 방식: operand field에 유효 주소의 주소를 명시하는 방식이다. 앞선 방식들에 비해 속도는 느려진다.

  • 레지스터 주소 지정 방식: 연산에 사용될 데이터가 저장된 레지스터를 명시한다. 레지스터는 CPU 내부에 위치하기 때문에 메모리에 접근하는 속도보다 훨씬 빠르게 접근 가능하다. 따라서 속도가 빠르다는 방식이 있다.

  • 레지스터 간접 주소 지정 방식: 연산에 사용될 데이터를 메모리에 저장해두고, 그 메모리 주소를 저장한 레지스터를 opearnd field에 명시하는 방식이다.

연산 코드의 종류는 크게 데이터 전송, 산술/논리 연산, 제어 흐름 변경, 입출력 제어가 있다.

기본적으로 연산 코드의 종류와 생김새는 CPU마다 다르기 때문에, 외우려고 하지 말고 어떤 유형이 있는지 정도만 파악하고 넘어가자.

  1. 데이터 전송

  1. 산술/논리 연산

  1. 제어 흐름 변경

CALL과 RETURN의 경우 코드를 짜다가 함수를 '호출'해서 그 함수로 이동해서 함수를 실행하게 되는게 'CALL'이고, 그 함수를 실행하다 return값을 반환하고 다시 원래 실행하던 흐름의 주소로 돌아가서 계속 코드가 실행되는게 'RETURN'이라고 생각하면 될 것 같다.

  1. 입출력 제어

C언어의 컴파일 과정

C언어는 preprocessor(전처리기), compiler, assembler, linker 이 순서를 통해 컴파일이 이루어진다.

이 과정은 '컴파일 과정'을 통해 한 번에 이루어지지만, 내부적으로는 위 순서를 거쳐서 이루어지는 것이다.

  1. 전처리 과정: 본격적인 컴파일 작업 전에 처리할 작업들을 일컫는다.

외부 선언된 라이브러리(ex. <stdio.h>)를 불러오는 과정도 전처리에서 일어난다.

위와 같은 간단한 문자열을 출력하는 C코드의 전처리 과정만 끝난 '.i'의 확장자를 갖는 파일을 확인해보면,

이와 같이 stdio.h 라이브러리의 모든 소스 코드를 다 불러오게 된다.

  1. 컴파일 과정: 컴파일러가 컴파일을 진행 즉, 전처리 완료된 소스 코드를 저급 언어(어셈블리어)로 변환한다.

위 이미지가 아까 그 간단한 C코드를 컴파일한(.s 확장자) 어셈블리어로 변환된 모습이다.

  1. 어셈블 과정: 어셈블리어를 기계어로 변환한다. 목적 코드를 포함하는 목적 파일이 된다.

위의 파일이 .o 확장자의 어셈블 과정이 이루어져 기계어로 변환된 모습이다.

  1. 링킹 과정: 목적 파일을 실행 파일로 만드는 과정이다.

위처럼 서로를 참조하는 여러 파일들을 묶어주는 작업이 링킹이고, 이 과정을 거쳐 비로소 실행 파일(.exe)가 된다. 목적 파일, 실행 파일 둘 다 기계어로 작성된 파일이지만, 링킹 과정을 무조건 거쳐야 실행 파일이 된다.

3. 헷갈려서 AI에게 확인한 부분

헷갈린 부분보다는 강의를 듣다가 어셈블리어를 소스 코드에 직접 쓰는 개발의 경우나, 개발자도 있다고 해서 이 부분을 좀 알아보았다.

중간에 어셈블리어를 끼워 넣는 것을 'inline assembly'라고 부르며, 현대 컴파일러 환경에서 자주 쓰진 않지만 몇 가지 활용되는 경우가 있다.

시스템/kernel을 만드는 프로그래머나, 실제 기기(자동차, IoT)와 같은 아주 작은 칩을 개발하는 개발자, 게임 개발자와 같이 특정 목적을 위해 매우 제한적인 환경에서의 개발을 할 때, CPU를 극한으로 쥐어짜거나 하드웨어를 직접 조작해야하는 상황일 때 주로 인라인 어셈블리를 한다고 한다.

4. 개발 또는 일상과 연결해 본 예시

작성자 또한 Java를 주로 쓰는 백엔드 개발을 공부하고 있는 사람으로써, 단 한 줄 에러의 위험성을 잘 알고 있다. 그게 '컴파일 과정'때문이라는 것을 오늘 강의를 들으며 다시 한 번 체감할 수 있었다. 실제 서버 코드를 작성할 때 단 한 곳만 삐끗해도 서버 전체가 작동하지 않는 것을 여러 번 경험봤다..

5. 추가로 알아보고 싶은 내용

작성자는 주로 Java를 활용한 백엔드 개발을 주로해서, Java의 자세한 컴파일 과정에 대해서도 알아보았다.

자바는 2단계의 컴파일 과정을 거친다.

  1. 소스 코드 파일(.java) 파일을 자바 컴파일러인 javac가 소스 코드를 번역한다.

이 과정에서 C언어처럼 진짜 기계어가 아닌, JVM만 읽을 수 있는 ByteCode라는 중간 형태의 파일로 변환한다. 이 파일의 확장자가 .class이다.

2. JVM이 실행되어 .class 파일을 메모리로 불러와 실제 CPU가 알아들을 수 있는 기계어로 실시간 번역을 하면서 실행한다.

이 과정에서는 한줄씩 실행되는 인터프리터 방식과 덩어리째 번역하는 방식이 혼용된다.

전공자 선택 작성: 심화 질문

  • 이 개념은 왜 필요한가?: 개발자는 소스 코드를 작성함으로써 프로그램을 개발하는 사람이다. 스스로 작성한 코드를 컴퓨터에서 어떻게 받아들이고 번역하여 실행하는지를 알아야 더욱 좋은 코드를 작성할 수 있을 것 같다.

  • 비전공자에게 설명한다면 어떤 순서로 설명할까?: 한 언어를 다른 언어로 '번역'하는 과정에 빗대어 여러가지 '번역'의 방법과, '명령'의 방법에 빗대어 설명하면 더 쉽게 이해시킬 수 있을 것 같다.

profile
'개발사(社)' (주)영진

0개의 댓글