1주차 - Chapter 1~3

Jan 14, 2023

Contents


Chapter 1 컴퓨터 구조 시작하기

컴퓨터 구조의 중요성

💡
컴퓨터 구조를 알면 프로그래밍에 관한 문제가 발생했을 때 적극적으로 방법을 찾을 수 있으며, 시스템 설계 시 CPU, 메모리 등 각종 하드웨어 사양을 반영하여 설계할 수 있다.

컴퓨터의 구성 요소

  • 컴퓨터는 명령어 데이터만 읽고 처리할 수 있다.
    • 컴퓨터는 명령어로 일련의 동작을 수행하고, 그 동작으로 데이터가 처리된다.
 
  • 오늘날의 컴퓨터는 네 가지 핵심 부품으로 구성되어 있다.
      1. CPU : 컴퓨터의 가장 중요한 부품으로, 내부 요소로 ALU, CU, 여러 종류의 레지스터가 있다.
          • ALU (Arithmetic and Logic Unit; 산술 논리 연산 장치) : 명령어와 데이터를 가지고 연산 작업을 수행하는 기능을 수행한다.
          • CU (Control Unit; 제어 장치) : 제어 신호를 컴퓨터 시스템 전반에 제공하여, CPU와 입출력 장치 사이의 데이터 흐름을 제어한다.
          • 레지스터 (Register) : CPU 내부에 위치한 작은 용량의 메모리로, CPU에서 연산하거나 메모리에 읽기/쓰기 연산을 하기 위해 필요한 명령어 및 데이터를 임시로 저장하기 위한 장치
      1. 메모리 : 명령어와 데이터를 저장한다.
          • 번지 : 메모리는 대개 1바이트의 번지로 구분되어 있다.
            • 메모리의 번지에는 명령어와 데이터 중 하나만 저장된다.
            • CPU가 메모리에 접근하여 읽기, 쓰기 연산을 할 때 메모리의 어느 위치에 접근해야 하는지를 지정하고자 할 때 번지 개념이 사용된다.
      1. 입출력 장치 : 마우스, 키보드, 모니터, 네트워크 장치와 같이 컴퓨터 외부에 연결되어 컴퓨터가 외부로부터 데이터를 입력받거나 출력하기 위한 장치
      1. 보조 기억 장치 : 메모리를 보조하기 위한 저장장치
          • 메모리의 단점
              1. 용량 대비 가격이 비싸다.
              1. 메모리는 휘발성이기 때문에, 전원이 꺼지면 메모리에 저장되어 있던 데이터는 모두 소멸된다.
          • 특징
            • CPU 레지스터나 메모리에 비해 더 많은 데이터를 저장할 수 있지만, 속도가 둘에 비해 매우 느리다.
            • 비휘발성 메모리 → 전원이 꺼져도 데이터는 유지된다.
 
  • 메인보드와 버스
    • 메인보드에는 시스템 버스를 비롯한 여러 종류의 버스가 있다.
    • 시스템 버스
      • 컴퓨터의 네 가지 핵심 부품 (CPU, 메모리, 입출력 장치, 보조 기억 장치) 간 데이터 이동 및 장치 제어를 담당하는 버스
      • 시스템 버스는 데이터 버스, 주소 버스, 제어 버스로 이루어져 있다.
      • CPU에서 메모리 쓰기 연산하는 것을 예시로 들면 다음과 같다.
        • 데이터 버스 : 메모리에 저장할 값을 보냄
        • 주소 버스 : 메모리의 어느 번지에 저장할 것인지를 나타내는 번지값을 보냄
        • 제어 버스 : ‘메모리 쓰기’ 제어 신호를 보냄
 

 

Chapter 2 데이터

0과 1로 숫자를 표현하는 방법

  • 컴퓨터는 0과 1, 두 가지 숫자만 이해할 수 있다. 따라서, 0과 1 중 하나를 표현할 수 있는 가장 작은 단위는 이진수 하나, 비트 단위다.
    • 1비트는 로 2개의 정보 (0, 1)를 표현할 수 있고, 2비트는 로 4개의 정보 (00, 01, 10, 11)를 표현할 수 있는 것이다.

정보 단위

  • 일반적으로 데이터의 크기를 표현하는 데에는 더 큰 단위를 사용한다. 8개의 비트를 하나로 묶은 바이트 단위가 바로 그것이다. 1바이트는 8비트며, 곧 이다. 256가지의 정보를 표현할 수 있는 단위인 1바이트를 기본적으로 사용한다.
  • 바이트는 메모리에서의 번지 개념으로 쓰인다.
  • 높은 단위로는 1000의 거듭제곱에 따라 붙는다. (Byte, KB, MB, GB, TB, …) 또한, 1024의 거듭제곱에 따라 붙는 단위로는 KiB, MiB, GiB, TiB, …와 같은 단위가 있는데, KiB의 경우 킬로바이트가 아니라 키비바이트라 읽는다.

2진수

  • 0과 1로만 숫자를 표현하는 방법
  • 1에서 그 다음 숫자로 올라갈 때 자리올림이 발생한다.
  • 음수 표현하기 - 2의 보수
    • 2의 보수를 이야기하기 전에, 1의 보수를 살펴보자.
    • 1의 보수는 변환하고자 하는 2진수의 모든 숫자 0과 1을 반대로, 1과 0으로 바꾸면 된다.
      • 2의 보수는 여기서 변환한 결과에서 1을 더하면 된다.
    • 어떤 2진수와 또 다른 2진수 사이에 뺄셈 연산을 할 때에는, - 기호가 붙는 피연산자를 2의 보수로 바꾼 후 두 수를 덧셈 연산하면 된다.
    • 그렇다면, 10진수와 10진수를 뺄셈한 결과와 2진수와 2진수의 2의 보수를 덧셈한 결과를 비교해 보자.
      • 1010이 -가 붙는 피연산자이므로, 2의 보수로 바꿔주었다.
      • 결과값은 비트 개수가 작은 쪽 피연산자의 비트 개수를 따라가기 때문에, 결과값은 11010이었으나 비트 개수는 4개가 되어야 하므로 MSB (Most Significant Bit)를 제거하여 1010이 되었다.
      • 따라서, 10진수의 결과값도 10이고 2진수의 결과값도 10이다.
    • 2의 보수를 원래 값으로 되돌리고자 할 때에는, 1을 빼고 → 모든 값의 0과 1을 다시 바꿔주면 된다.
 

16진수

  • 2진수를 4개씩 묶어 표현하는 방법
  • 한 자리에서 표현 가능한 숫자는 총 15개로, 0~9, 그리고 10진수 10부터는 A~F로 표기한다.
 

0과 1로 문자를 표현하는 방법

  • 문자 집합 : 컴퓨터가 인식하고 표현할 수 있는 문자의 모음 → 컴퓨터는 문자 집합에 속한 문자만 이해할 수 있음
  • 문자 인코딩 : 문자를 컴퓨터가 이해 가능한 문자 코드로 바꾸는 것 → 컴퓨터가 문자 집합에 속한 문자를 이해하려면, 문자를 매핑된 문자 코드 (2진수 형태)로 변환해야 한다.
  • 문자 디코딩 : 문자 코드를 사람이 이해 가능한 문자로 바꾸는 것
 

아스키 코드

  • ASCII란 초기 문자 집합 중 하나로, 영문자와 숫자, 그리고 제어 문자를 비롯한 특수 문자가 포함된다.
  • 표현할 수 있는 정보의 가짓수는 7비트, 즉 128개를 표현할 수 있다.
    • 이후 표현할 수 있는 정보를 늘리기 위해 1비트를 추가한 확장 아스키가 도입되었지만 256개가 최대로 한계가 있었다.
  • 한계 - 한글을 포함한 다양한 종류의 문자를 표현할 수 없음 → 유니코드의 도입으로 해결
 

한글 인코딩의 두 가지 방식

  1. 완성형 인코딩 : 완성된 하나의 글자 (ex) 가, 나, 다 등)에 고유한 숫자를 붙이는 방법
  1. 조합형 인코딩 : 초성 (ㄱ, ㄴ, ㄷ 등), 중성 (ㅏ, ㅑ, ㅓ 등), 종성 (ㄱ, ㄴ, ㄷ 등)에 고유한 비트열을 붙여, 각각의 비트열의 조합으로 나온 문자가 최종적인 코드가 되는 방법
 

EUC-KR

  • 표현 가능 범위는 2바이트, 즉 65,536개 표현 가능
  • EUC-KR은 완성형 인코딩 방식으로 한글 인코딩을 수행한다.
    • 총 2,350개 정도의 한글을 표현할 수 있지만, 모두 표현하는 것은 불가능하다.
    • cp949 인코딩으로 이를 보완하였지만 역부족
 

유니코드와 UTF-8

  • 모든 문자 표현 가능
  • ASCII와 EUC-KR과 같이 문자별로 고유한 값 지정
  • 인코딩할 문자에 따라서 표현 범위가 결정됨 (최소 1바이트 ~ 최대 4바이트)
 

 

Chapter 3 명령어

소스 코드와 명령어

프로그래밍 언어의 구분

  • 사용하는 주체에 따른 구분
    • 고급 언어 : 사람이 읽고 이해할 수 있는 언어 (프로그래밍 언어들)
    • 저급 언어 : 컴퓨터가 읽고 이해할 수 있는 언어 (기계어 - 0, 1)
    • 어셈블리어 : 고급 언어와 저급 언어 사이에 있는 중간격의 언어로, CPU가 어떤 동작을 실행하도록 하는 명령어 (ex) MIPS 아키텍처에서의 어셈블리 명령어는 add, sub, jmp, bne 등이 있다.)
    • 💡
      컴파일러는 고급 언어를 어셈블리어로 변환하고, 컴파일 과정에서 최종적으로는 기계어와 같은 저급 언어로 변환되어 컴퓨터가 실행할 수 있는 파일로 소스 코드를 변환하는 역할을 수행한다.
 
  • 작성된 소스 코드가 처리되는 방식에 따른 구분
    • 컴파일 언어 : 소스코드 전체를 한꺼번에 읽고 처리하는 언어
      • 컴파일하지 않으면 소스코드는 실행될 수 없다.
      • 대표적으로 C/C++, Java, Kotlin, Swift, Dart, Typescript 등이 있다.
    • 인터프리터 언어 : 소스코드 한 줄마다 읽고 처리하는 언어
      • 대표적으로 Python, Javascript, R 등이 있다.
 

프로그램이 실행되기까지의 과정

  1. 전처리 (Preprocessing) : 전처리기 명령어 (#include, #define, #pragma, …) 및 라이브러리와 소스 코드를 하나로 묶는 작업
  1. Assemble : 전처리한 소스 파일을 기계어로 변환하여 object 파일 생성
  1. Linking : 기계어로 변환된 하나 이상의 object 파일들을 묶어 하나의 실행 파일 생성
  1. Loading : 실행 파일을 메모리에 적재하여 운영체제에서 동작하는 프로세스 생성
 

명령어의 구조

명령어의 구조

💡
명령어는 CPU 아키텍처의 ISA (Instruction Set Architecture; 명령어 집합)에 따라 조금씩 또는 크게 달라진다!
  • 명령어는 Opcode, Operand 두 가지로 이루어져 있다.
    • 일반적으로 Opcode는 가장 큰 비트 자리부터 있다. Operand는 그 다음 자리부터 있다.
    • Operand는 0~3개가 올 수 있다. 명령어에 Operand가 n개 있다고 하면 이러한 명령어를 n-주소 명령어라고 한다.
    • 명령어의 구조는 Opcode, Operand 두 가지로 이루어져 있지만, 명령어의 목록이나 종류, 길이, 그리고 명령어별 필요한 Operand의 목록은 CPU 아키텍처의 ISA에 따라 달라진다.
 
  • Opcode : 명령어가 어떤 동작을 수행하는지 결정
    • Opcode에는 크게 네 종류가 있다.
        1. 데이터 전송
        1. 산술/논리 연산
        1. 제어 흐름 변경
        1. 입출력 제어
 
  • Operand : 명령어가 어떤 동작을 수행할 때 필요로 하는 위치
    • 위치에는 어떤 레지스터나 메모리 주소가 올 수 있다.
      • 실제 계산에 쓸 데이터가 저장된 메모리 주소를 유효 주소라고 한다.
 

주소 지정 방식

💡
명령어의 Operand에 어떤 위치가 명시될 때, 해당 위치를 찾는 방법을 주소 지정 방식 (Addressing Mode)이라고 한다.
  • 즉시 주소 지정 방식 (Immediate Addressing Mode) → 실제 값이 상수값
    • 메모리 접근 0회
    • 연산에 사용할 데이터를 Operand에 직접 명시하는 방식 (MIPS에서 immediate 값)
    • 직접 명시하는 값의 표현 범위가 명령어의 길이에 의해 제한되는 단점이 있지만, 다른 주소 지정 방식과 달리 어떤 레지스터나 메모리에 접근하는 과정이 필요없기 때문에 명령어의 실행 속도가 가장 빠르다.
 
  • 직접 주소 지정 방식 (Direct Addressing Mode) → 실제 값이 변수값, 주소값이 상수값
    • 메모리 접근 1회
    • Operand에 유효한 메모리 주소를 직접 명시하는 방식
    • 표현 가능한 메모리 주소 범위가 명령어의 길이에 의해 제한된다.
    • 주로 제어 흐름 변경 명령어에서 쓰인다. (jump, branch, …)
 
  • 간접 주소 지정 방식 (Indirect Addressing Mode) → 실제 값, 주소값 모두 변수값
    • 메모리 접근 2회
    • Operand에 실제로 접근할 주소값이 위치한 메모리 주소를 저장 (포인터의 포인터 느낌)
    • Operand에 명시된 주소값으로 접근하면 명령어에서 쓸 실제 값이 아닌 실제 값이 위치한 메모리 주소를 찾을 수 있다. 이후 찾은 주소로 다시 메모리에 접근하면 실제 값을 찾을 수 있다.
    • 실제 값을 찾으려고 메모리에 두 번 접근하는 연산을 수행해야 하기 때문에, 실행 시간이 비교적 오래 걸린다. 대신 표현 가능한 주소의 범위는 가장 넓다.
    • 직접 주소 지정 방식과 비교 (표현 범위 중심으로)
      • 예시 : 32비트 고정 길이 (0~31번째 비트)의 명령어가 있다고 하자. 여기에서 MSB (Most Significant Bit)부터 6비트까지, 즉 26~31번째 비트를 Opcode라고 하고 0~25번째 비트는 명령어에서 사용할 메모리 주소를 저장하는 Operand라고 하자. 메모리 내부의 주소 하나는 최대 32비트 길이의 주소를 저장할 수 있다고 하자.
      • 직접 주소 지정 방식에서는 Operand에서 메모리 주소를 직접 명시하므로, 해당 방식에서의 메모리 주소 표현 범위는 명령어의 전체 길이 32비트에서 Opcode 6비트를 뺀 26비트이므로 , 즉 0x0~0x3FFFFFF다.
      • 간접 주소 지정 방식에서는 Operand가 명시하는 메모리 주소가 최종적으로 접근하고자 하는 주소가 아닌, 그 주소가 위치한 주소다. 따라서, Operand가 명시한 메모리 주소로 접근했을 때 확인할 수 있는 메모리 주소의 표현 범위는 , 즉 0x0~0xFFFFFFFF다.
 
  • 레지스터 주소 지정 방식 (Register Addressing Mode)
    • Operand에 실제 레지스터를 직접 명시하는 방식
 
  • 레지스터 간접 주소 지정 방식 (Register Indirect Addressing Mode)
    • 실제 데이터를 메모리에 저장하고, 저장된 데이터가 위치한 메모리 주소를 Operand에 명시하는 방식
        1. 어떤 데이터를 메모리에 저장
        1. 메모리 내에서 저장된 데이터가 위치한 주소를 레지스터에 저장
        1. Operand에 해당 레지스터를 명시
 

레지스터 주소 지정 방식과 레지스터 간접 주소 지정 방식의 차이점

💡
피연산자의 레지스터에 실제 값이 들어가는지 vs 실제 값이 위치한 메모리 주소값이 들어가는지
 

스택과 큐

  • 스택 : Last In First Out (LIFO) 방식의 자료구조로, 들어오는 방향으로만 요소가 들어오고 나갈 수 있다. 따라서, 스택에서 요소를 제거하고자 할 때 가장 나중에 들어온 요소가 가장 먼저 제거된다.
    • 스택의 대표적인 연산으로는 Push, Pop 연산이 있다.
      • Push : 스택의 최상단에 요소를 삽입한다.
      • Pop : 스택의 최상단에 있는 요소를 제거한다.
 
  • 큐 : First In First Out (FIFO) 방식의 자료구조로, 들어오는 방향에서는 요소가 들어오고 나가는 방향에서는 요소가 나갈 수 있다. 따라서, 큐에서 요소를 제거하고자 할 때 가장 먼저 들어온 요소가 가장 먼저 제거된다.
    • 큐는 앞뒤가 나뉘어져 있다. 요소를 삽입하는 부분을 뒷부분, 요소가 제거되는 부분을 앞부분이라고 한다.
    • 큐의 대표적인 연산으로는 Enqueue, Dequeue 연산이 있다.
      • Enqueue : 큐의 뒷부분에 요소를 삽입한다.
      • Dequeue : 큐의 앞부분에 있는 요소를 제거한다.
 
 

문제풀이

notion image
notion image
notion image