모든 프로그램은 컴퓨터 푸품이 실행한다.
“내가 어떤 코드를 실행했을 때 어느 정도의 연산이 필요한지, 어느 정도의연산이 필요한지, CPU 파워, 속도 , 저장관리 비용을 짐작할 수 있다.”
컴퓨터구조에는 1. 컴퓨터가 이해하는 정보
와 2. 컴퓨터의 네 가지 핵심 부품
이 존재한다.
프로그램을 이루는 두가지 정보는 데이터와 명령어다.
“컴퓨터는 명령어를 처리하는 기계"
아두이노, 라즈베리 파이와 같은 작은 컴퓨터부터 스마트폰, 노트북, 데스크톱, 서버 컴퓨터까지 다양한 종료의 컴퓨터에서 공통적인 핵심 부품
보조기억장치도 컴퓨터 외부에 연결되어 컴퓨터 내부와 정보를 교환할 수 있는 장치로 볼 수 있지않나?
⇒ 보조기억장치와 입출력장치를 ‘컴퓨터 주변에 붙어있는 장치’라는 의미에서 주변장치라 통칭하기도 한다. 다만, 보조기억장치는 다른 일반적인 주변장치와 달리 메모리를 보조한다는 특별한 기능을 수행하는 입출력 장치이다.
프로그램이 실행되려면 반드시 메모리에 저장되어 있어야 한다.
컴퓨터는 0과 1만 이해한다
모든 “양수/ 음수/ 소수/ 문자”를 0과 1로 표현하는 방법은?
- 양수 : 이진수
- 음수 : 대부분 차용하는 방법은 2의 보수
- 소수(분수) : 대부분 차용하는 방법은 부동소수점
- 문자 : 문자 집합 & 인코딩
- 문자 집합 : 컴퓨터가 이해할수 있는 문자들의 모임
- 인코딩 : 0과 1로 변환하는 과정
제일 작은 정보단위는 비트(bit)
1 바이트는 8개의 비트를 묶은 단위
워드(word)
CPU가 한 번에 처리할 수 있는 데이터 크기
- CPU가 한 번에 16비트를 처리할 수 있따면 1워드는 16비트, 한 번에 32 비트를 처리할 수 있다면 1워드는 32비트
- 하프 워드 : 워드의 절반 크기
- 풀 워드 : 1배 크기
- 더블 워드 : 2배 크기
CPU마다 워드 크기는다르지만 현대 컴퓨터의 워드 크기는 대부분 32비트 또는 64비트이다.
- 인텔의 x86 : 32비트 워드 CPU
- 인텔의 x64 : 64비트 워드 CPU
숫자가 1을 넘어가는 시점에 자리 올림을 하여 0과 1만으로 모든 숫자를 표현하는 방법
방식 | 십진수 | 이진수 |
---|---|---|
수학적 표기 방식 | 8 | 1000(2) |
코드상 표기 방식 | 8 | 0b100 |
1. 1의 보수를 취하고 (1의 보수 : 모든 이지순의 0과 1을 뒤집는다.)
2. 거기에 1을 더한다.
1의 보수 | 2의 보수 | |
---|---|---|
1011(2) | 0100(2) | 0101(2) |
0101(2) | 1010(2) | 1011(2) |
0과 1만으로 음수를 표현하면, 이진수로만 봐서는 양수와 음수를 구분하기 어렵지 않을까?
(양수로서의 0101(2)과 음수로서의 0101(2)이 구분가지 않는다.)⇒ 컴퓨터 내부에서 어떤 수를 다루 때, 이 수가 양수인지 음수인지 구분하기위해 CPU 내부내에서 특별한 정보를 저장하는 플래그 레지스터를 사용한다.
이진법은 0과 1로만 표현할 수 있기 때문에 너무 길어질 수가 있다.
1~9와 A~F로 모든 수를 표기하는 방법 (A =10, B=11, C=12, D=13, E=14, F=15)으로 15를 넘어가는 시점에 자리 올림하여 수를 표현하는 방법
십육진수와 이진수는 서로 변환도 쉽고 편하다.
십육진수 | 변환 | 이진수 |
---|---|---|
1A2B(16) | 0001(2) 1010(2) 0010(2) 1011(2) | 0001101000101011(2) |
이진수 | 변환 | 십육진수 |
---|---|---|
11010101(2) | D(16) 5(16) | D5(16) |
문자 집합(charater set) : 컴퓨터가 인식하고 표현할 수 있는 문자의 모음
문자 인코딩 : 문자를 0과1로 이루어진 컴퓨터가 이해할 수 있는 문자코드로 변환하는 과정
문자 디코딩 : 0과 1로 이루어진 문자코드를 사람이 이해할 수 있는 문자로 변환하는 과정
문자가 깨진다면?
- 지원되지않아서 컴퓨터가 이해할 수 없는 문자 집합
- 호환되지 않는 인코딩 방식으로 데이터를 불러 들인 경우
- 인코딩 방식과 디코딩 방식이 상이한 경우
초창기 문자 집합 중 하나로, 영어 알파벳과 아라비아 숫자, 일부 특수문자를 포함한다.
아스키 문자 집합에 0부터 27까지 수가 할당되어 아스키 코드로 인코딩 된다.
#include <stdio.h>
int main() {
printf("%d \n", 'a'); // 'a'의 ASCII 값 출력
printf("%c \n", 97); // ASCII 값 97에 해당하는 문자 출력
return 0;
}
아스키 코드는 아스키 문자 집합외의 다양한 언어의 문자를 표현할 수가 없다.
8비트의 확장 아스키가 등장했지만, 그 또한 표현할 수 있는 문자 갯수가 256개로 한정적이다.
완성형 인코딩 방식 기반의 한글 인코딩 방식
완성형 인코딩 방식 : 완성된 하나의 글자에 고유한 코드를 부여하는 인코딩 방식
조합형 인코딩 방식 : 초성, 중성, 종성을 위한 각각의 비트열을 할당하여 그것들의 조합으로 하나의 글자를 완성하는 인코딩 방식
여러 나라의 문자를 광범위하게 표현할 수 있는 통일된 문자 집합.
U+<코드 포인트>
4자리의 16진수로, 유니코드 문자에 부여된 고유한 수
유니코드 인코딩 방식에는 utf-8m utf-16,utf-32 등이 존재하며, 이 인코딩 방식은 유니코드의 코드 포인트를 인코딩한다.
컴퓨터는 명령어를 처리하는 기계
개발자가 작성한 소스코드가 어떻게 명령어로 변환될까?
컴퓨터는 소스코드를 이해할 수 있는가?
⇒ 컴퓨터는 명령어를 이해한다.
”소스코드는 실행되기 전에 명령어(재료는 데이터)로 변환되어서 실행된다.”
명령어와 데이터 : 컴퓨터가 이해하기 쉬운 언어, 저급언어
소스코드 : 개발자가 이해하기 쉬운 언어, 고급언어
컴퓨터가 직접 이해하고 실행할 수 있는 언어
CPU와 컴파일러에 따라 달라질 수 있다.
컴퓨터가 직접 이해하는 근원적인 언어로 0과 1의 명령어 비트로 이루어진 저급 언어
0과 1로 이루어진 기계어를 읽기 편한 형태로 번역한 저급언어
기계어 | 어셈블리어 |
---|---|
0101 0101 | push rbp |
0101 1101 | pop rbp |
1100 0011 | ret |
사람이 이해하고 작성하기 쉽게 만들어진 언어
성능면 : 컴파일 > 인터프리트
컴파일 언어로 작성된 소스 코드는 컴파일러에 의해 저급 언어로 변환된다. (결과로 목적코드 생성)
인터프리터에 의해 소스코드가 한 줄 씩 저급 언어로 변환되고 실행된다. (한 줄씩 목적코드 생성)
컴파일 / 인터프리트 오개념 주의
- 소스코드가 저급 언어로 변환되는 대표적인 방식일뿐, 컴파일 방식과 인터프리트 방식은 칼로 자르듯 구분되는 개념이 아니다.
- 컴파일 언어의 특성과 인터프리트언어의 특성을 다 갖춘 언어도 존재한다. (JAVA, python etc.)
목적 파일 : 목적 코드로 이루어진 파일, 컴퓨터가 이해하는 저급 언어
실행 파일 : 실행 코드로 이루어진 파일
목적 코드가 실행파일이 되기 위해서는 링킹이라는 작업을 거쳐야한다.
“소스 코드 ⇒(컴파일) ⇒ 목적 코드(목적 파일) ⇒ (링킹) ⇒ 실행 파일”
명령의 대상, 동작
“무엇을 대상으로 무엇을 수행하라.” ⇒ 오퍼랜드로 연산코드를 수행하라.
연산코드 | 오퍼랜드 | |
---|---|---|
무엇을 수행하라 | 무엇을 대상으로 | |
더해라 | 100과 | 120을 |
빼라 | 메모리 32번지 안의 값과 | 메모리 33번지 안의 값을 |
저장해라 | 10을 | 메모리 128번지에 |
명령어 : 연산코드 + 오퍼랜드
연산코드(연산자) : 오퍼랜드가 수행할 동작, ‘명령어가 수행할 연산’
오퍼랜드(피산연자, 주소필드) : ‘연산에 사용할 데이터’ 또는 ‘연산에 사용할 데이터가 저장된 위치’
오퍼랜드 필드에는 연산에 사용할 데이터를 직접 명시하기보다는 많은 경우 데이터가 저장된 위치(주소)를 명시한다.
연산코드에 대상이 되는 데이터가 명령어 상에 없을 때, 레지스터나 메모리에 데이터의 대상이 되는 곳에 찾아가는 방법
왜 데이터를 직접 명시하지 않고 위치를 명시할까?
⇒ 명령어의 길이는 한정되어있기 때문이다.
오퍼랜드가 많아 지게 되면, 각각의 오퍼랜드 필드 크기가 작아지게 된다. 하나의 오퍼랜드 필드로 표현할 수 있는 정보의 가짓수가 점점 줄어들게된다.
데이터가 아닌 데이터를 가리키는 레지스터나 메모리 주소가 담긴다면, 데이터의 크기는 오퍼랜드의 크기와 상관없이 하나의 메모리 주소에 저장할 수 있는 공간만큼 커진다.
유효주소 : 연산 코드에 사용할 데이터가 저장된 위치, 연산의 대상이 되는 데이터가 저장된 위치
연산에 사용할 데이터를 오퍼랜드 필드에 직접 명시하는 방식
데이터의 한계점이 존재하지만, 연산 속도는 그 어느 것보다 빠르다.
오퍼랜드 필드에 유효 주소를 직접적으로 명시하는 방식
표현할 수 있는 오퍼랜드 필드의 길이가 연산 코드의 길이만큼 짧아져 표현할 수 있는 유효 주소에 제한이 생길 수도 있다.
유효 주소의 주소를 오퍼랜드 필드에 명시하는 방식
직접 주소 지정방식보다 표현할 수 있는 유효 주소의 범위가 더 넓다.
두 번의 메모리 접근이 필요하기 때문에 즉시 지주소 지정 방식, 직접 주소 지정 방식보다 느리다.
직접 주소 지정 방식과 유사하다.
연산에 사용할 데이터를 저장한 레지스터를 오퍼랜드 필드에 직접 명시하는 방법
일반적으로 CPU 외부에 있는 메모리에 접근하는 것보다 CPU내부에 있는 레지스터에 접근하는 것이 더 빠르다.
하지만, 직접 주소 지정방식이기 때문에 표현할 수 있는 레지스터 크기에 제한 생길 수 있다.
연산에 사용할 데이터를 메모리에 저장하고, 그 주소(유효 주소)를 저장한 레지스터를 오퍼랜드 필드에 명시하는 방법
유효 주소를 찾는 과정이 간접 주소 지정방식과 유사하지만, 메모리에 접근하는 횟수가 한 번이기때문에 연산속도 더 빠르다.
💡 주소 지정 방식을 학습하면서 제일 먼저 떠오른 것은 자바스크립트 변수 메모리 할당이었다.
내가 생각하는 것으로 챗지피티한테 물어봤다.
“즉시 주소 지정방식과 간접 주소 지정방식을 보면서 자바스크립트가 원시타입과 참조타입을 저장하는 방식을 떠올랐어. 관련이 있을까?”
- 진도: Chapter 01 ~ 03
기본 미션: p. 51의 확인 문제 3번, p. 65의 확인 문제 3번 풀고 인증하기
선택 미션: p. 100의 스택과 큐의 개념을 정리하기
나중에 저장한 데이터를 가장 먼저 빼내는 데이터 관리방식 ( 후입선출; LIFO_Last In First Out)
연산 | 설명 |
---|---|
삽입(Push) | 스택에 데이터를 삽입 |
추출(Pop) | 스택에 데이터를 추출 |
class Stack {
constructor() {
// 스택을 초기화하고, 내부적으로 객체를 사용하여 요소들을 저장
this.items = {};
this.topIndex = 0; // 다음 요소가 들어갈 위치
}
//새 요소를 스택의 맨 위에 추가
push(item) {
this.items[this.topIndex] = item;
this.topIndex++;
}
//스택의 맨 위 요소를 제거하고 반환
pop() {
if (this.topIndex > 0) {
this.topIndex--;
const item = this.items[this.topIndex];
delete this.items[this.topIndex];
return item;
}
}
//스택의 맨 위 요소를 반환
peek() {
if (this.topIndex > 0) {
return this.items[this.topIndex - 1];
}
}
isEmpty() {
return this.topIndex === 0;
}
getLength() {
return this.topIndex;
}
}
가장 먼저 저장된 데이터부터 빼내는 데이터 관리 방식 (선입선출; FIFO_Fist In First Out)
class Queue {
constructor() {
this.items = {}; // 큐의 요소를 저장하는 객체
this.headIndex = 0; // 큐의 맨 앞 요소의 인덱스
this.tailIndex = 0; // 큐에 다음 요소가 추가될 인덱스
}
enqueue(item) {
this.items[this.tailIndex] = item; // 큐의 끝에 요소를 추가
this.tailIndex++; // tailIndex 증가
}
dequeue() {
if (this.headIndex !== this.tailIndex) {
// 큐가 비어있지 않은 경우에만
const item = this.items[this.headIndex]; // 맨 앞의 요소를 가져옴
delete this.items[this.headIndex]; // 해당 요소를 큐에서 제거
this.headIndex++; // headIndex 증가
return item; // 제거된 요소 반환
}
}
// 큐의 맨 앞 요소 반환 (제거하지 않음)
peek() {
return this.items[this.headIndex];
}
// 큐의 현재 길이 계산
getLength() {
return this.tailIndex - this.headIndex;
}
}
혼공단 스터디 1주차 범위에 맞게 컴퓨터공학 인터넷 강의를 먼저 듣고, 책을 1회독 한뒤에 정리했다.
글 내용에도 있다시피 메모리 지정 방식에서는 정말 머릿속으로 유레카를 외쳤다.
배치업데이트로 이루어지는 리액트 상태 변화가, 사실상 컴퓨터공학의 한 부분의 지식이라는 것을 알때도 컴퓨터 공학 지식을 쌓아야하는데 라는 고민이 앞섰었다.
리액트를 볼때마다 하나의 작은 운영체제가 아닐까? 라는 생각은 은연중에 했었기 때문이다.
아 물론, 어떤 점에서 그렇게 생각하냐고 물어본다면 정확하게는 말하지는 못하겠다. 6주뒤에는 술술나오지 않을까?
믿거나 말거나
담님 분철신청해서 책 늦게받은거치곤 열심히 작성하셨네용ㅋㅋㅋㅋ 잘보고갑니디👍🏻