혼공컴운

[혼공컴운] 3장. 명령어

게으른 the lazy 2024. 7. 6. 18:05

https://en.wikipedia.org/wiki/Machine_code_monitor

3. 명령어

  • 컴퓨터가 다루는 정보는 데이터와 명령어로 구성된다.
  • 이번 장은 명령어다.
  • 데이터만 있어서는 아무것도 할 수 없다.
  • 100과 200만 주면, 이걸 더해야 할지 곱해야 할지 알 수 없다.
  • 무언가 시켜야 한다. 이것을 우리는 명령어라고 부른다.

3.1 소스 코드와 명령어

  • CPU가 일을 하기 위해서는 모든 것이 0과 1로 변환되어야 한다.
  • 데이터는 물론 명령어도 마찬가지이다.
  • 분명 파이썬 코드는 영어로 쓰여 있다.
  • 그렇다면 파이썬 코드도 어떤 과정을 거쳐 2진수로 변환될 것이다.
  • 그런데 뭔가 좀 묘하다.
  • 데이터든 명령어든 2진수로 변환되는데
  • CPU는 이것이 데이터인지 명령어인지 어떻게 알까?
  • 그것을 알기 위해 명령어의 번역 과정에 대해 알아보자.

3.1.1 저급 언어와 고급 언어

  • 두 줄 요약
  • 저급 언어: 기계어, 어셈블리어
  • 고급 언어: 그 외 전부

  • 사실 진정한(?) 의미의 저급언어는 기계어 뿐이다.
  • 정말로 0과 1로만 이루어진 것을 기계어라고 부른다.

출처: https://marketbusinessnews.com/financial-glossary/machine-code/

  • CPU는 이것을 보고 바로 작업을 수행할 수 있지만,
  • CPU의 이해도와 인간의 이해도는 반비례한다.
  • 그래서 나온 것이 어셈블리어이다.

출처: https://medium.com/@jleveewhite/an-introduction-to-assembly-language-8144ce1dfb0e

  • 이제 겨우 읽을 수는 있게 되었다.
  • 어셈블리어를 만든 것은 폰 노이만의 제자였던 캐슬린 부쓰가 만들었다.
    • 여성이다. 2022년에 작고했다.
  • 당연히 생산성 때문에 만들었을텐데,
  • 사실 명령의 시퀀스 레벨에서는 답답하긴 매한가지였을 것이다.
  • 그래서 C, Java, Fortran 같은 고급 언어들이 만들어졌다.
  • 참고로 저급언어라고 어셈블리어를 무시하면 안되는게,
  • 2024년 7월 기준 tiobe index 순위가 13위다.
    • 12위가 Swift이고 14위가 매트랩이다.
    • 17위가 Rust, 19위가 Kotlin이다.
  • 사물인터넷에 쓰이는 초소형 기기의 수요 때문이라고 한다. (said 나무위키)
  • 최최최적화가 필요하다면 어셈블리어를 쓸 줄 알아야 한다고 한다.

3.1.2 컴파일 언어와 인터프린트 언어

  • 고급 언어는 사람에게 가까운 언어이다.
  • 파이썬은 정말로 영어처럼 읽을 수 있다.
  • 다만 고급 언어는 CPU가 바로 이해하지 못한다.
  • 따라서 CPU가 이해하는 저급 언어로 번역해야 한다.
  • 서두에서 CPU가 명령어와 데이터를 어떻게 구분할까 하는 질문을 던졌는데,
  • 어셈블리어를 보면 명령어와 데이터가 구분되어 있는 것을 볼 수 있다.
  • 우리가 적는 고급 언어의 코드는 이렇게 명령어와 데이터가 구분된 형태로 번역되어야 한다.
  • 번역에는 두 가지 방식이 있다: 컴파일과 인터프리트
  • 간단히 말해, 컴파일은 책 번역, 인터프리트는 실시간 통역이다.

  • C언어가 대표적인 컴파일 언어이다.
  • C언어 코드를 실행하려면 코드 전체를 번역부터 한다.
  • 번역 과정에서 문제가 발견되지 않으면, 그제서야 실제로 코드가 실행된다.
  • 아주 큰 프로그램은 컴파일만 몇 시간씩 걸리기도 한다.
  • 대신 일단 컴파일 되면 실행은 빠르다.
  • 그런데 번역 과정에서 에러가 뜨면 다시 컴파일 해야 한다.

  • 파이썬이 대표적인 인터프리트 언어이다.
  • 한줄씩 번역-실행 과정을 거친다.
  • 문제가 있는 코드가 발견되면 거기서 멈춘다.
  • 한줄마다 번역-실행을 하므로 컴파일 언어에 비해 느리다.
  • 대신 디버깅은 상대적으로 편하다.
  • 다만 파이썬은 완전한(?) 인터프리트 언어는 아니다.
  • 실제로는 바이트코드로 컴파일하고, 컴파일 된 것을 인터프리터가 다시 번역한다.
  • 자세한 내용은 이 글을 읽어보자.

3.1.3 목적 파일과 실행 파일

  • 컴파일 된 결과는 목적 파일에 저장된다.
  • 목적 파일은 그 자체로는 실행 파일이 아니다.
  • 따라서 몇 가지 추가 정보를 적어주어야 실행 가능한(executable) 파일이 된다.
    • 프로그램 시작 지점(entry point)을 지정
    • 표준 라이브러리의 실제 코드 삽입
    • 메모리에 로드 될 주소로 코드를 재배치
    • 운영체제가 파일을 실행 파일로 인식하고 관리하기 위한 정보 추가
  • 목적 파일이 여러 개라면 이것들을 연결하는 작업도 필요한데,
  • 이것을 링킹(linking)이라고 부른다.
  • 요즘은 대부분 IDE에서 컴파일과 링크를 동시에 실행한다.

3.2 명령어의 구조


3.2.1 연산 코드와 오퍼랜드

  • 하나의 명령어는 연산 코드와 오퍼랜드(피연산자)로 구성된다.
  • 연산 코드는 '더해라', '빼라', '저장해라' 등 '행위'를 지정한다.
  • 기본적인 연산코드는 네 가지가 있다.
    • 데이터 전송
    • 산술/논리 연산
    • 제어 흐름 변경
    • 입출력 제어

  • 오퍼랜드는 '100', '200', '3번지' 등의 '값'이다.
  • 오퍼랜드는 없을 수도 있고 여러 개 있을 수도 있다.
  • 실제로는 오퍼랜드에 값보다 주소를 담을 때가 더 많다.
  • 이유는 간단하다. 명령어의 길이를 일정하게 만들고, 대용량 데이터를 쉽게 처리하기 위함이다.

3.2.2 주소 지정 방식

  • 그렇다면 오퍼랜드에 주소를 어떻게 넣을까?
  • 몇 가지 방식이 있다.
  • 즉시 주소 지정 방식(immediate addressing mode)
    • 연산에 사용할 데이터를 오퍼랜드 필드에 직접 넣는 것이다.
    • 사실 주소가 쓰이지 않는데 왜 '주소'라는 이름이 들어가는지 모르겠다.
  • 직접 주소 지정 방식(direct addressing mode)
    • 오퍼랜드에 메모리 주소를 직접 넣는 것이다.
  • 간접 주소 지정 방식(indirect addressing mode)
    • 데이터가 담겨 있는 곳의 주소를 담고 있는 곳의 주소를 오퍼랜드에 적는 방식이다.
    • 포인터를 말하는 것일까?
    • 솔직히 직접 주소 지정 방식과 무슨 차이인지 모르겠다.
    • 메모리가 데이터를 갖고 있든 주소를 갖고 있는, 메모리의 주소는 같은 길이 아닌가?
    • '주소'라는 단어를 계속 쓰니까 게슈탈트 붕괴 온다.
  • 레지스터 주소 지정 방식(register addressing mode)
    • 데이터가 메모리가 아니라 레지스터에 담겨있는 경우,
    • 레지스터의 주소를 오퍼랜드에 적는 방식이다.
  • 레지스터 간접 주소 지정 방식(register indirect addressing mode)
    • 사용할 데이터를 메모리에 담고,
    • 레지스터에는 그 메모리 주소가 담기고,
    • 오퍼랜드에는 그 레지스터의 주소가 담긴다.
    • 레지스터는 CPU 내에 있으므로 접근이 빠르다.
    • 그래서 간접 주소 지정 방식보다 빠르다.
  • 명령어의 구조와 주소 지정 방식에 대해서는 이 글이 도움이 될 듯 하다.

3.2.3 스택과 큐

  • 연산 코드 중 데이터 전송에 해당되는 것으로,
  • MOVE, STORE, LOAD(FETCH), PUSH, POP 등이 있다.
  • 이 중 PUSH와 POP은 스택에 데이터를 넣거나 가져오는 것을 말한다.
  • 갑자기 스택과 큐를 설명해서 좀 뜬금없다는 생각이 들었다.
  • 여기서 말하는 스택이 메모리의 스택 영역을 말하는 것일까?
  • 어쨌든, 스택과 큐는 아래와 같이 비유할 수 있다.
    • 스택: 책을 쌓아둔 것이다.
      • 데이터는 맨 위에 추가된다.
      • 데이터는 맨 위의 것만 뺄 수 있다.
    • 큐: 화장실이다.
      • 먼저 들어간 것이 먼저 나온다.