01. 고급언어와 저급언어
우리가 작성한 소스코드는 어떻게 명령어로 변환될까요? 언어에는 고급언어와 저급언어가 있습니다.
흔히 JAVA / Python / C++ 언어 같은 언어는 개발자가 읽고 쓰기 편하게 만들어진 언어로 고급언어에 해당되고 이 고급언어는 컴퓨터 내부에서 그대로 실행되지 않고 저급언어 즉, 명령어로 변화되어 실행됩니다. 정리해보면
- [고급언어]
- 대부분의 프로그래밍 언어. 개발자가 이해하기 쉽게 만든 언어 - 컴퓨터가 이해할 수 없다.
- (e.g. C++, Python, JAVA)
- [저급언어]
- 컴퓨터가 이해하고 실행하는 언어 , 명령어로 이루어져 있다.
저급언어는 또 다시 기계어와 어셈블리어로 나뉩니다. 기계어는 0과 1로 이루어진 명령어로 구성된 저급언어로 가끔 16진수로도 표현되기도 합니다. 어셈블리어는 0과 1로 이루어진 기계어를 읽기 편한 형태로 번역한 저급언어입니다. 이 어셈블리어 자체를 그대로 소스코드로 사용하기도 합니다. 컴퓨터에 직접적으로 명령을 내리는 기계어와 다르게 어셈블러라는 번역기에 의해 기계어 명령으로 바꾼 후 컴퓨터에 명령을 내리게 됩니다. (여담으로 어셈블리어로 개발하는 분들,,, 멋있습니다.)
- [기계어]
- 0과 1로 이루어진 명령어로 구성된 저급언어
- [어셈블리어]
- 기계어를 읽기 편한 형태로 번역한 저급언어
프로그래밍언어인 고급언어가 컴퓨터가 이해할 수 있는 저급언어로 변환되는 방식에는 컴파일 방식과 인터프리트 방식 두가지가 있습니다.
- [컴파일 방식]
- (e.g. C언어)
- [인터프리터 방식]
- (e.g. 파이썬,Ruby,JavaScript)
우선, 컴파일 언어로 작성된 소스코드는 컴파일러에 의해서 컴파일 됩니다. 컴파일의 결과인 저급언어로 변환된 코드를 목적 코드라고 합니다.
인터프리트 언어는 인터프리터에 의해서 번역되어 소스코드가 한줄씩 실행됩니다.
컴파일언어는 이와 다르게 한줄씩이 아닌 처음부터 끝까지 훑어보고 '통째로' 컴파일 합니다. 하지만 인터프리터는 한줄 한줄 씩 실행되기 때문에 소스코드 전체가 저급언어로 변환되기까지 기다릴 필요가 없습니다. 컴파일언어는 통째로 컴파일하기 때문에 오류가 하나라도 있으면 저급언어로 컴파일을 안해주고 컴파일 에러를 보내고 컴파일을 중단해버리는 완벽주의자 녀석입니다. 쳇 ..,, 인터프리트 언어는 오류 전까지는 한줄한줄 실행되기 때문에 실행됩니다.
[컴파일 & 인터프리트 과정 ]
cpu 종류에 따라 컴파일러의 종류에 따라 저급언어는 달라질 수 있습니다. gcc는 컴파일러 종류 ARM은 CPU종류입니다.
그렇다고 모든 프로그래밍 언어들이 컴파일언어와 인터프리트 언어로 칼로 자르듯이 나누어지지는 않습니다.
02. 명령어의 구조
명령어의 대략적인 구조는
무엇을대상으로 + 무엇을 수행해라 = 명령어
이런 형식으로 구성되어 있습니다.
즉, '무엇을 대상'으로는 '연산에 사용될 데이터' 혹은 '연산에 사용될 데이터가 저장된 위치'를 의미하고' 무엇을 수행해라'는 수행할 연산이 됩니다.
명령어는' 연산코드 + 오퍼랜드'로 구성되어 있다고 하는데 여기서 오퍼랜드란 연산에 사용될 데이터 혹은 연산에 사용될 데이터가 저장된 위치를 말하며 거의 연산에 사용될 데이터가 저장된 위치를 가르키기 때문에 오퍼랜드 필드를 주소필드라고 부르곤 합니다. 오퍼랜드는 여러개일 수도 있고 없을 수도 있습니다.
연산코드는 명령어가 수행할 연산을 담고 있으며, 크게
1. 데이터 전송
2. 산술 / 논리 연산
3. 제어흐름 변경
4. 입출력 제어
로 나누어 집니다. 연산코드의 종류와 생김새는 CPU마다 다릅니다. 연산코드종류를 모두 외울 필요는 없습니다.
1. 데이터 전송
MOVE ->데이터를 옮겨라
STORE->메모리에 저장하라
LOAD(FETCH)->메모리에서 CPU로 데이터를 가져와라
PUSH->스택에 데이터를 저장하라
POP->스택의 최상단 데이터를 가져와라
여기서 스택이란 한쪽끝이 막혀있는 통과같은 자료구조로 Last In First Out(LIFO) 구조를 가지고 있습니다. LIFO는 예를 들면 사람이 꽉차있는 엘레베이터를 생각해보면, 만원이 된 엘레베이터는 마지막으로 탄사람이 내리게 됩니다. 이를 LIFO라고 합니다. 마지막으로 탄사람(top)이 나가는 것을 pop(삭제연산) 이라고 합니다.
큐는 양쪽 끝이 뚫려있는 것 같은 자료구조로 First In First Out (FIFO) 구조를 가지고 있습니다. FIFO는 아르바이트를 해본 경험이 있으시다면 아시듯이 선입선출입니다. 예를들어 먼저 온 철수(front)는 빵을 주문하였고, 그 다음에 도착한 영희(rear)는 커피를 주문하였습니다. 빵을 데우는 동안 영희의 커피를 먼저 만들어 영희에게 주면, 철수가 화를 낼 수도 있기 때문에 먼저 온 철수의 빵부터 주는 것이 알바 입장에서 속편합니다. 이를 먼저 온사람부터 먼저 주는 FIFO 구조라고 볼 수 있습니다.
2.산술/논리 연산
ADD/SUBTRACT/MULTIPLY/DIVIDE ->덧셈/뺄셈/곱셈/나눗셈을 수행하라
INCREMENT/DECREMENT->오퍼랜드에 1을 더하라/1을 빼라
AND/OR/NOT -> and or not 연산을 수행하라
COMPARE -> True/False 값을 비교해라
3. 제어흐름 변경
특정 메모리 주소로 점프에서 옮기는것을 말합니다.
JUMP-> 특정 주소로 실행 순서를 옮겨라
CONDITIONAL JUMP -> 조건에 부합할 때 특정 주소로 실행 순서를 옮겨라
HALT -> 프로그램의 실행을 멈춰라
CALL -> 되돌아올 주소를 저장한 채 특정 주소로 실행 순서를 옮겨라
RETURN -> call을 호출할 때 저장했던 주소로 돌아가라
4. 입출력 제어 명령어
왜 굳이 저장된 위치 주소를 쓸까요? 그것은 바로 명령어 내에서 표현할 수 있는 '데이터의 크기가 제한'되기 때문입니다. 메모리 주소로 저장을 하면 오퍼랜드 필드로 표현할 수 있는 데이터의 크기가 늘어나게 됩니다. 메모리 외에도 레지스터에도 연산할 데이터를 저장할 수 있습니다.
유효주소란 연산에 사용될 데이터가 저장된 위치입니다. 오퍼랜드 필드에 레지스터가 담길 수도 있고 메모리 주소가 담길 수도 있고 아싸리 연산코드에 사용될 데이터가 직접적으로 명시될 수도 있습니다. CPU입장에서는 연산에 사용될 데이터를 찾아서 실행시킬수 있어야 하는데 이러한 방식에는 여러 방식이 있습니다.
명령어 주소 지정방식은 CPU마다 차이가 있지만 연산에 사용할 데이터가 저장된 위치를 찾는 방법으로 유효주소를 찾는 방법입니다. 다양한 명령어 주소 지정방식들이 있습니다.
1. 즉시 주소 지정방식
연산에 사용할 데이터를 오퍼랜드 필드에 직접 명시합니다. 가장 간단한 형태의 주소 지정방식으로 연산에 사용할 데이터의 크기가 작아질 수 있지만 빠르다는 장점이 있습니다.
2. 직접 주소 지정방식
오퍼랜드 필드에 유효주소를 직접적으로 명시합니다. 유효 주소를 표현할 수 있는 크기가 연산코드만큼 줄어듭니다.
3. 간접 주소 지정방식
오퍼랜드 필드에 유효주소의 주소를 명시합니다. 앞선 주소 지정방식들에 비해 속도가 느립니다. CPU가 메모리를 뒤지고 뒤져야하기때문입니다.
4.레지스터 주소 지정방식 (유효주소 x)
연산에 사용할 데이터를 저장한 레지스터를 오퍼랜드 필드에 직접 명시합니다. 레지스터 이름이 와와햄이라면, 와와햄이 오퍼랜드 필드에 명시되는 것입니다. CPU는 CPU밖에 존재하고 있는 메모리에 접근하는 속도보다 내부에 존재하는 레지스터에 접근하는 것이 빠르기 때문에 메모리에 접근하는 방식에 비해 속도가 빠릅니다.
5. 레지스터 간접 주소 지정방식 (유효주소 o -> 레지스터에 있는 주소)
유효 주소를 저장한 레지스터를 오퍼랜드 필드에 명시합니다.
[C언어의 컴파일 과정 ]
전처리기 - 컴파일러 - 어셈블러 -링커
test.c -> test.exe (실행파일)
test.c -> test.i -> test.s -> test.o -> test.exe
[목적파일 vs 실행파일]
- 목적파일 & 실행파일 : 둘 다 기계어로 이루어진 파일
- 목적파일은 링킹(linking)을 거친 이후에나 실행파일(.exe)이 됩니다.
링킹이란 각기 다른 목적코드를 하나의 실행파일로서 묶어주는 과정입니다.
면접 질문예상
- C와 Java의 차이는 ?
- 가장 큰 차이는 C는 절차지향프로그램 언어이고 Java는 객체지향언어를 사용합니다. C언어는 대표적 절차지향프로그램 언어의 한 예로서 컴퓨터의 작업 처리방식과 유사하기 때문에 객체지향언어를 사용하는 것에 비해 더 빨리 처리되어 시간적으로 유리하나 Java는 인터넷의 분산환경에서 사용되도록 설계된 프로그래밍 언어로서 인터넷 환경기반의 프로그램을 만들고 수행 시킬 수 있는 응용프로그램을 만들 수 있습니다. 또한 객체지향프로그래밍언어의 특징으로 캡슐화,상속,다형성의 특징이 있습니다.
- Java와 JavaScript 중 어떤 언어가 더 좋은지? (둘이 걍 다른앤데 뭐가 더 좋다해야돼요,,, ㅜㅜ 진짜 몰라서 걍 차이만 적었습니다.)
- Java는 컴파일 언어이자 객체지향프로그래밍 언어이고, JavaScript는 스크립트 언어이자 객체기반스크립트언어입니다. 자바 스크립트는 코드가 한줄 한줄 실행되는 인터프리터 언어이기 때문에 문제를 빨리 발견할 수 잇습니다. (디버깅이 쉽다 == 오류의 원인을 찾고 해결하는 과정이 디버깅 ) 하지만 자바스크립트는 코드가 복잡할 수록 디버그 및 유지 관리가 어려워질 수 있습니다. 자바스크립트는 비동기적인 특성을 가지고 있기 때문에 여러 가지 작업을 비동기적으로 순차적으로 처리해야 할 때, 중첩된 콜백은 빠르게 복잡해지고 읽기 어려워집니다.
- 스크립트 언어와 컴파일 언어를 나열하고 차이점을 설명
- 먼저 스크립트 언어와 컴파일 언어의 가장 큰 차이점은 컴파일러 존재의 여부입니다. 여기서 스크립트 언어란 다른 응용프로그램 안에 삽입되어 해석되는 방식으로 덩치가 큰 소프트웨어에서 컴파일은 시간이 오래 소요되는 작업이므로 수정이 빈번하게 발생하는 부분에서는 소스코드를 한줄씩 읽어 바로 실행하는 인터프리터 방식이 효율적이다. 이에따라 스크립트 언어도 대부분 인터프리터 방식을 사용하는 인터프리터 언어라고 할 수 있다. 인터프리터 방식은 목적파일(기계어로 바꾸기) 을 생성하지 않고 런타임 뒤에 즉시 실행한다. 컴파일 과정 없이 바로 실행하기 때문에 플랫폼에 독립적이다(어느 운영체제에서도 동일한 결과를 얻을 수 있음) 단점으로는 빌드되어있는 컴파일 언어 프로그램보다 실행시간이 느리고(매번 한줄씩 번역해야해서) 코드를 열면 다 보이기때문에 보안에 취약하다는 단점이 있다.
- 컴파일이된 후 독립적으로 작동하는 하나의 완전한 응용프로그램 빌드가 완료된 파일은 실행속도가 빠르고 매번 번역할 필요 없이 실행 파일만 실행하면 되기 때문에 시간면에서 효율적이나 프로그램을 수정해야 할 경우 처음부터 빌드 과정을 다시 거쳐야 하기때문에 대규모 프로젝트에서는 생산성이 떨어진다.
- 컴파일 언어의 경우 컴파일러를 통해서 한 번 컴파일이 된 후에는 코드 수정 후 재컴파일을 하기 전 까지는 같은 결과를 나타내지만, 스크립트 언어는 실행될 때 바로 해석하게 되므로 코드 변경시 실행할 때마다 실행 결과가 바뀌게 된다
스크립트 언어 | 컴파일 언어 | |
장점 | - 대부분 인터프리터 언어이기 때문에 수정이 빈번하게 발생한다면 효율적이다. - 플랫폼에 독립적이다. |
- 컴파일 후 독립적으로 작동하는 하나의 완전한 응용프로그램 빌드가 완료된 파일은 실행속도가 빠르다. |
단점 | - 빌드되어 있는 컴파일 언어 실행파일보다 실행시간이 느리다 . - 코드를 열면 다 보이기 때문에 보안에 취약하다. |
- 프로그램을 수정해야 할 경우 처음부터 빌드 과정을 다시 거쳐야 하므로 대규모 프로젝트에서는 생산성이 떨어진다. |
- 스크립트 언어- 자바스크립트,파이썬,루비,jquery,PHP,ASP,펄,브이스크립트
- 컴파일 언어 - 어셈블리어 c c++ c# java
본 글은 인프런의 [혼자공부하는 컴퓨터 구조 + 운영체제 ] 인강을 참고하였습니다.
'CS > 운영체제' 카테고리의 다른 글
[운영체제] 프로세스와 스레드 (0) | 2023.08.19 |
---|---|
[운영체제] 운영체제 기초 (0) | 2023.08.19 |
[컴퓨터구조]입출력장치 [I/O device] (0) | 2023.08.18 |
[컴퓨터구조]CPU의 성능향상기법 (0) | 2023.08.14 |
[컴퓨터구조] CPU의 작동원리 (0) | 2023.08.11 |