소스코드
- 소스 코드(source code)는 컴퓨터 프로그램이나 소프트웨어의 원시 코드로, 사람이 이해하고 작성할 수 있는 텍스트 형식의 코드를 말한다.
- 소스 코드는 프로그래머가 애플리케이션 또는 소프트웨어를 개발할 때 사용하는 코드로, 일련의 명령문과 구문을 포한다.
- 소스 코드는 프로그래밍 언어의 구문 규칙에 따라 작성되며, 주석(comment)과 공백을 포함하여 가독성을 높일 수 있다.
- 소스 코드는 컴파일러나 인터프리터를 통해 기계어로 변환되어 실행 파일이나 프로그램이 생성된다.
- 프로그래밍에서 소스 코드는 프로그램의 핵심이며, 소프트웨어의 동작을 정의하는데 사용된다.
- 따라서 소스 코드의 품질과 구조가 소프트웨어의 성능과 유지 보수성에 중요한 영향을 미친다.
고급 언어
- 고급 언어는 사람이 이해하기 쉬운 형태로 작성된 프로그래밍 언어(소스코드)를 말한다.
- 이러한 언어는 일반적으로 사람이 문제를 해결하고 알고리즘을 구현하는 데 사용된다.
- 고급 언어로는 Python, Java, C++, JavaScript 등이 있다.
이러한 언어는 문법이나 구조가 사람이 읽고 이해하기 쉬우며, 추상화 수준이 높아서 개발자가 문제를 해결하는 데 집중할 수 있도록 도와준다.1) 컴파일 언어 :
- 컴파일 언어는 소스 코드를 한 번에 전체적으로 컴파일하여 실행 파일(바이너리 코드)로 변환한다.
- 이 변환 작업은 컴파일러라는 특별한 프로그램에 의해 이루진다.
- 컴파일 언어의 대표적인 예로는 C, C++, Java 등이 있다.
- 컴파일 언어의 장점은 실행 속도가 빠르다는 것이며, 한 번 컴파일된 코드는 다른 환경에서도 실행될 수 있다.
2) 인터프리트 언어 :
- 인터프리트 언어는 소스 코드를 한 줄씩 읽어들여 즉시 실행한다.
- 소스 코드는 특별한 프로그램인 인터프리터에 의해 해석되고 실행된다.
- 인터프리터 언어의 대표적인 예로는 Python, JavaScript, Ruby 등이 있다.
- 인터프리터 언어의 장점은 개발과 디버깅이 빠르고 쉽다는 것이며, 일반적으로 높은 이식성을 제공한다.
- 그러나 실행 시간에 해석이 발생하므로 실행 속도가 컴파일 언어보다 느릴 수 있다.
*이식성 :
해당 언어로 작성된 프로그램이 서로 다른 운영 체제나 하드웨어 아키텍처에서도 실행될 수 있는 능력
ex) Python으로 작성된 프로그램은 Windows, macOS, Linux 등 다양한 운영 체제에서 실행될 수 있다.*모든 언어가 컴파일언어와 인터프리트 언어 둘로 명확히 구분되는 것은 아니다. 둘은 고급 언어가 저급 언어로 변환되는 대표적인 예시 중 하나다.
저급 언어
- 저급 언어는 기계가 직접 실행할 수 있는 형태로 작성된 프로그래밍 언어(소스코드)를 말한다.
- 이러한 언어는 기계어나 어셈블리어와 같이 컴퓨터의 하드웨어와 밀접하게 관련되어 있다.
- 저급 언어로 작성된 프로그램은 고급 언어에 비해 실행 속도가 빠를 수 있으며, 하드웨어와 직접적으로 상호작용할 때 유용하다.
- 하지만 저급 언어는 고급 언어보다 이해하기 어렵고 프로그래밍하는 데 더 많은 노력이 필요하다.
기계어
기계어는 컴퓨터가 직접 이해하고 실행할 수 있는 0과 1로 구성된 명령어로, CPU가 직접 실행할 수 있다.
어셈블리어
기계어의 인간 친화적 버전으로, 기계어 명령어를 기억하기 쉬운 기호나 연산 코드로 표현한다.
*일반적으로 고급 언어로 작성된 프로그램은 컴파일러나 인터프리터를 통해 저급 언어로 변환되어 실행된다. 이러한 변환 단계를 통해 고급 언어의 편리함과 저급 언어의 효율성을 모두 얻을 수 있다.
명령어
명령어의 구조
"더해라 100과 120을"
명령어는 크게, 연산 코드(OpCode)와 피연산자(Operand)로 이루어져 있다.
- 연산 코드(OpCode) : '더해라'
- 피연산자(Operand) : '100과, 120을'
연산 코드(OpCode)
- 연산 코드는 특정 작업을 수행하는 명령을 나타내는 코드.
- CPU가 수행할 연산의 종류를 정의한다. 예를 들어, 덧셈, 뺄셈, 로드, 저장 등의 연산을 나타낼 수 있다.
- 연산 코드는 하나의 명령어에 여러 개가 있을 수도 있고, 하나도 없을 수 있다.
Ex)
- ADD : 덧셈 연산을 수행하라.
- SUB : 뺄셈 연산을 수행하라.
- MOV : 데이터를 한 위치에서 다른 위치로 이동시켜라.
- LOAD : 메모리에서 데이터를 로드하라.
- STORE : 데이터를 메모리에 저장하라.
- PUSH : 스택에 데이터를 저장하라.
- POP : 스택의 최상단 데이터를 가져와라.
- JUMP : 특정 주소로 실행 순서를 옮겨라.
피연산자 (Operand)
피연산자는 연산 코드가 작동하는 데이터.
피연산자는 레지스터, 메모리 주소, 상수 값 등일 수 있다. 피연산자는 연산의 대상이 되는 값을 나타낸다.Ex)
- 레지스터 (Register) : AX, BX, CX 등
*레지스터(Register) : CPU 내부의 작은 저장장치- 메모리 주소 (Memory Address) : [0x1000], [BP+4] 등
- 상수 값 (Immediate Value) : 10, 0xFF 등
*피연산자에 데이터 주소를 담는 이유 : 명령어 내에서 표현할 수 있는 데이터의 크기가 제한되기 때문이다(2의 6승). +오퍼랜드의 수가 많을수록 줄어든다.
*유효 주소(Effective Address) : 연산에 사용할 데이터가 저장된 위치
명령어 주소 지정 방식(Addressing Modes)
- 컴퓨터 아키텍처에서 명령어가 피연산자(Operand)에 접근하는 방법을 나타내는 방식.
*아키텍처(Architecture) : 컴퓨터 시스템이나 프로세서가 동작하는 방식과 그 설계를 의미한다.- 주소 지정 방식은 특정 메모리 위치에 접근하거나 레지스터 값을 사용하여 데이터를 조작하는 방법을 결정한다.
- 주소 지정 방식들은 각각의 장단점을 고려하여 프로그래밍 언어나 어셈블리어에서 적절히 선택되어야 한다.
- 프로그램의 성능과 효율성을 고려하여 주소 지정 방식을 결정하는 것이 중요하다.
Ex)
즉시 주소 지정 방식(Immediate Addressing)
연산에 사용할 데이터를 오퍼랜드 필드에 직접 명시한다
가장 간단한 형태의 주소 지정 방식이다
연산에 사용할 데이터의 크기가 작아질 수 있지만, 빠르다.장점
- 빠른 접근 : 명령어 자체에 데이터 상수가 포함되어 있어 CPU가 즉시 접근하여 사용할 수 있다.
- 간편한 사용 : 데이터 상수를 명령어에 직접 포함시키므로, 데이터를 로드하는 추가적인 명령어가 필요없다.
단점
- 고정된 값 : 상수 값은 명령어가 실행될 때 변경할 수 없으며, 고정된 값으로 제한된다.
- 메모리 접근 제한 : 대부분의 즉시 값은 명령어 길이에 제한이 있어서 크기가 제한될 수 있다.
직접 주소 지정 방식 (Direct Addressing)
오퍼랜드 필드에 유효 주소를 직접적으로 명시한다.
장점
- 명령어가 : 직접 메모리 주소를 지정하기 때문에 간단하고 직관적이다.
- 직접 접근 : 데이터에 직접 접근할 수 있어 일반적으로 빠른 속도를 제공한다.
단점
- 제한된 유연성 : 메모리 주소가 하드코딩되어 있어 실행 중에 주소를 변경할 수 없다.
- 코드의 복잡성 : 많은 메모리 주소가 하드코딩되면 코드의 유지보수와 이식성이 낮아질 수 있다.
*하드코딩(Hardcoding) : 소프트웨어 개발에서 특정 값이나 설정을 코드에 직접 포함시키는 것을 의미한다. ex) 상수, 문자열, 파일 경로 등..
간접 주소 지정 방식 (Indirect Addressing)
오퍼랜드 필드에, 유효 주소의 주소를 명시한다.
장점
- 복잡한 구조 처리 : 복잡한 데이터 구조를 다룰 때 특히 유용하다.
- 유연성 : 레지스터나 다른 메모리 위치에 저장된 주소를 통해 접근할 수 있어 데이터 접근의 유연성이 높다.
단점
- 추가적인 명령어 필요 : 실제 데이터 접근에 약간의 오버헤드가 발생할 수 있다.
- 실행 시간의 증가 : 간접 접근이 직접 접근보다 실행 시간이 더 길어질 수 있다.
*오버헤드 : 어떤 작업을 수행하기 위해 필요한 추가적인 비용이나 부담 혹은 비효율성을 의미한다.
레지스터 주소 지정 방식(Register Addressing)
연산에 사용할 데이터가 저장된 레지스터를 명시한다.
메모리에 접근하는 속도보다 레지스터에 접근하는 것이 빠르다.
*메모리는 CPU밖에 존재하고, 레지스터는 CPU내부에 존재한다.장점
- 최적화된 속도 : 레지스터는 CPU 내부에 있어 가장 빠른 메모리 접근 시간을 제공한다.
- 간단한 명령어 : 명령어 길이가 짧고, 실행 속도가 빠르다.
단점
- 한정된 개수 : CPU에 사용 가능한 레지스터의 개수에 제한이 있기 때문에, 많은 데이터를 처리할 때 제한적일 수 있다.
- 비용 증가 : 추가적인 레지스터를 필요로 하거나, 레지스터 사용을 최적화하기 위한 복잡성이 증가할 수 있다.
레지스터 간접 주소 지정 방식(Register Indirect Addressing)
연산에 사용할 데이터를 메모리에 저장하고, 그 주소를 저장한 레지스터를 오퍼랜드 필드에 명시한다.
장점
- 유연성 : 메모리 주소가 레지스터에 저장되어 있어, 실행 중에 동적으로 메모리 위치를 결정할 수 있다.
- 간접 접근의 이점 : 간접 주소 지정 방식의 유연성을 레지스터 수준에서 활용할 수 있다.
단점
- 복잡성 : 몇 가지 추가적인 명령어 사이클이 필요할 수 있다.
- 성능 저하 : 직접 주소 지정 방식보다 실행 시간이 더 길어질 수 있다.
*CPU가 메모리를 접근하는 속도는 다른 내부 메모리(레지스터, 캐시 메모리)에 비해 상대적으로 느리다. 이로 인해 메모리 접근을 최소화하는 것이 전체적인 시스템 성능과 속도를 향상시키는 데 중요하다.
C언어의 컴파일 과정
C 언어의 컴파일 과정은 전처리, 컴파일, 어셈블, 링크의 네 단계로 이루어져 있으며, 각 단계마다 소스 코드 파일이 다른 형태로 변환되어 최종적으로 실행 가능한 프로그램이 생성된다.
이러한 과정을 통해 C 언어로 작성된 프로그램이 컴퓨터에서 실행될 수 있다.
1. 전처리(Preprocessing)전처리 단계에서는 소스 코드 파일을 가공하여 컴파일러가 실제로 처리할 수 있는 중간 형태로 변환한다.
주로 #include, #define, #ifdef와 같은 전처리 지시자를 처리하며, 헤더 파일의 내용을 복사하여 소스 코드 파일에 포함시키고, 매크로를 확장한다.
*외부에 선언된 다양한 소스코드, 라이브러리를 포함(#include), 프로그래밍 편의를 위해 작성된 매크로 변환(#define), 컴파일할 영역 명시(#ifdef, #if)전처리 단계는 다음과 같이 수행된다.
- 소스 코드 파일과 해당하는 헤더 파일을 결합하여 단일 소스 파일로 만든다.
- 전처리 지시자에 따라 매크로를 확장하거나 조건부 코드를 처리한다.
- 결과적으로 확장된 소스 코드가 생성된다.
2. 컴파일(Compilation)
컴파일 단계에서는 전처리된 소스 코드를 저급 언어(어셈블리어)로 번역한다.
컴파일 단계는 다음과 같이 수행된다.
- 전처리된 소스 코드를 어셈블리 코드로 변환한다.
- 언어의 문법 규칙을 따라 기계어 명령어로 번역한다.
- 컴파일러가 생성한 어셈블리 코드 파일(주로 .obj 파일 형태)이 생성된다.
3. 어셈블(Assembly)
어셈블리어를 기계어로 변환한다.
이 단계에서는 실제 CPU가 이해할 수 있는 바이너리 코드로 변환된다.
목적코드를 포함하는 목적 파일(Object file)이 된다.
*목적파일 vs 실행파일 : 두 파일 모두, 기계어로 이루어져 있으나, 다른 파일이며, 목적 파일(Object file)은 링킹(linking)을 거친 이후에 실행파일이 된다.어셈블 단계는 다음과 같이 수행된다.
- 어셈블러가 어셈블리 코드를 기계어로 번역하여 오브젝트 코드(Object code)라고 불리는 바이너리 파일(주로 .o 파일 형태)을 생성한다.
4. 링크(Linking)
링크 단계에서는 여러 개의 오브젝트 코드 파일과 라이브러리들을 결합하여 실행 가능한 프로그램 파일을 생성한다.
이 과정에서는 각 오브젝트 코드 파일이나 라이브러리가 필요로 하는 함수나 변수의 위치를 결정하고, 이들을 모두 하나의 실행 파일로 통합한다.링크 단계는 다음과 같이 수행된다.
- 컴파일된 오브젝트 코드 파일들과 라이브러리들을 결합하여 실행 파일을 생성한다.
- 필요한 라이브러리들을 포함하여 실행 파일이 완성된다.
- 최종적으로 실행 가능한 바이너리 파일(일반적으로는 .exe 혹은 바이너리 형식의 실행 파일)이 생성된다.