프로그래밍 언어
프로그래밍 언어의 발전 과정
기계어
CPU가 직접 해독하고 실행할 수 있는 유일한 언어
0과 1의 이진 코드로 표현된다.
- 장점: 번역 과정 불필요, 매우 빠른 실행 속도
- 단점
- 가독성 저하: 이진 코드는 인간이 이해하기 매우 어렵다.
- 디버깅 어려움: 코드 작성과 오류 수정에 많은 시간 소요
- 하드웨어 의존성: CPU 종류에 따라 기계어가 달라 호환성 부재
- 수동 메모리 관리: 메모리 주소 직접 지정 및 관리 필요
0A3F # 메모리 주소 0x3F의 데이터를 레지스터 R1으로 로드
0B40 # 메모리 주소 0x40의 데이터를 레지스터 R2로 로드
F112 # 레지스터 R1과 R2의 값을 합산하여 레지스터 R0에 저장
E341 # 레지스터 R0의 값을 메모리 주소 0x41에 저장
FFFF # 프로그램 실행 정지
어셈블리 언어
기계어 명령을 사람이 이해하기 쉬운 기호(니모닉)로 일대일 대응시킨 언어
기호를 통해 프리미티브나 메모리 주소를 표현한다.
- 개선점
- 기계어 대비 가독성 향상으로 코드 이해 및 작성 용이
- 메모리, 입출력 장치, 레지스터 직접 접근 가능
- 식별자 사용으로 메모리 주소 직접 사용 부담 감소
- 한계:
- CPU 구조에 대한 깊은 이해 필요
- 여전히 기계 종속적이다. CPU 종류마다 어셈블리어가 다름
- 어셈블러를 통한 기계어 변환 과정 필수
기계어 코드 (가상) | 어셈블리 프로그램 (가상) | 설명 |
---|---|---|
0A3F |
LOAD REG_A, DataVal |
DataVal 값을 레지스터 REG_A에 적재 |
0B40 |
LOAD REG_B, TaxRate |
TaxRate 값을 레지스터 REG_B에 적재 |
F1AB |
ADD_INT ACCUM, REG_A REG_B |
REG_A와 REG_B를 더해 ACCUM 레지스터에 저장 |
E341 |
STORE ACCUM, ResultAddr |
ACCUM 값을 ResultAddr 위치에 저장 |
FFFF |
HALT |
프로그램 실행 중단 |
운영체제 커널, 디바이스 드라이버, 임베디드 시스템, 고성능 게임 엔진 등 하드웨어 밀착 제어나 극한의 최적화가 필요한 분야에서 제한적으로 사용된다.
고급 언어
기계 중심 언어의 낮은 생산성과 높은 개발 난이도로 인해 인간 친화적이고 기계 독립적인 언어의 필요성이 높아지며 등장한 고급 프로그래밍 언어
기계 독립성
특정 하드웨어 구조에 얽매이지 않고 프로그램 작성이 가능해진 것이 핵심 특징이다. 작성된 소스 코드는 컴파일러 또는 인터프리터를 통해 특정 기계에 맞는 기계어로 번역된다.
- 이식성: 동일 소스 코드를 다른 종류의 컴퓨터에서 최소한의 수정으로 실행 가능
- 언어 표준화 기구(ANSI, ISO 등)가 언어 명세를 정의하여 이식성 강화
추상화 수준의 향상
프로그래머가 컴퓨터 내부의 복잡한 작동 방식 대신 문제 해결 논리에 집중하도록 높은 수준의 추상화를 제공한다. (예: total = price * quantity
)
- 자연어와 유사한 문법으로 가독성 증진
- 함수, 클래스 등 강력한 추상화 도구로 복잡한 시스템 설계 지원
번역기의 유형
- 컴파일러: 소스 코드 전체를 분석하여 목적 코드나 실행 파일로 일괄 번역. 번역 시간은 걸리지만 실행 속도는 빠르다. (예: C, C++, Go)
- 인터프리터: 소스 코드를 한 줄씩(또는 작은 단위로) 해석하며 즉시 실행. 개발 중 테스트는 용이하나 실행 속도는 일반적으로 느리다. (예: Python, JavaScript, Ruby)
Java, C# 등은 소스 코드를 플랫폼 독립적인 중간 코드(바이트코드)로 컴파일한 후, 가상 머신(JVM, CLR)이 이를 인터프리트하거나 JIT(Just-In-Time) 컴파일하여 실행한다.
정형적 언어
번역기가 모호함 없이 코드를 해석하도록 문법 규칙이 엄격하게 정의된 언어. 자연어와 달리 중의적 해석이 불가능하다. 대부분의 프로그래밍 언어가 이에 해당한다.
프로그래밍 언어의 세대별 분류
추상화 수준과 개발 목적에 따라 다음과 같이 세대별 구분이 가능하다.
- 1세대: 기계어
- 2세대: 어셈블리어
- 3세대: 고급 절차/객체지향 언어 (예: C, C++, Java, Python)
- 4세대: 특정 응용 분야 특화 언어 (예: SQL, MATLAB)
- 5세대: 인공지능 연구용 논리/제약 기반 언어 (예: Prolog)
시간이 흐를수록 하드웨어 고려사항보다는 문제 해결 자체에 집중하는 방향으로 발전했다.
프로그래밍 패러다임
프로그래밍 언어는 기반이 되는 프로그래밍 패러다임에 따라 분류할 수 있다. 이는 프로그램 구성 및 문제 해결의 근본적인 접근 방식이다.
명령형 패러다임
"어떻게(How)" 문제를 해결할지에 초점. 프로그램 상태를 변경하는 명령들을 순차적으로 실행하여 결과를 도출한다. 알고리즘의 절차를 명시적으로 기술한다.
(예: C, Pascal)
선언형 패러다임
"무엇을(What)" 원하는지에 초점. 목표 상태나 결과의 속성을 기술하고, 구체적 실행 절차는 시스템에 위임
(예: SQL, HTML[[1]], Prolog)
[[1]]: HTML은 엄밀히 프로그래밍 로직을 기술하는 언어라기보다는 문서의 구조와 내용을 기술하는 마크업 언어로 분류된다. 하지만 어떻게 보여줄지가 아닌 무엇이 있는지를 선언한다는 측면에서 선언형 패러다임의 넓은 범주에 포함시켜 설명하기도 한다.
함수형 패러다임
프로그램을 순수 수학적 함수들의 조합으로 구성. 부수 효과 없는 순수 함수 사용 지향. 상태 변화보다 함수 간 입출력 연결 중시
(예: LISP, Haskell)
객체지향 패러다임 (OOP)
데이터(속성)와 이를 처리하는 함수(메서드)를 묶은 객체를 기본 구성 단위로 사용. 객체 간 메시지 교환을 통해 문제 해결. 캡슐화, 상속, 다형성이 주요 특징
(예: Java, C++, Python, Swift)
Python, C++, Java 등 현대의 많은 언어는 여러 패러다임의 특징을 수용하여 개발자가 상황에 맞게 선택할 수 있도록 지원한다. 이를 다중 패러다임 언어라 한다.
프로그램의 기본 요소
변수와 자료형
- 변수: 프로그램 실행 중 값을 저장하고 참조하기 위해 사용되는 메모리 공간에 부여된 상징적 이름 (예:
userCount = 25
에서userCount
)- 메모리 내 특정 위치를 지칭하며, 저장된 값은 변경 가능하다.
- 정적 타입 언어(C, Java 등)에서는 사용 전 선언이 필수이며, 이때 자료형을 명시한다.
- 자료형: 변수가 저장할 데이터의 종류와 해당 데이터에 적용 가능한 연산의 집합을 정의. 데이터가 메모리에 표현되는 방식 결정
- 기본 타입: 언어 자체 제공 기본 자료형
- 정수: 소수 부분 없는 수 (예:
100
,-3
) - 실수: 소수 부분 포함 수 (예:
3.14159
,-0.001
) - 문자: 단일 글자 (예:
'X'
,'한'
) - 부울: 참(True) 또는 거짓(False) 논리 값
- 정수: 소수 부분 없는 수 (예:
- 기본 타입: 언어 자체 제공 기본 자료형
데이터 구조
여러 데이터를 효율적으로 저장하고 관리하기 위한 논리적 구성 형태
- 배열: 동일한 자료형의 원소들을 연속된 메모리 공간에 순차 저장하는 구조. 다차원 배열 구성 가능
- 구조체: 서로 다른 자료형의 변수(멤버, 필드)들을 논리적으로 하나로 묶어 새로운 자료형을 정의하는 방식
상수와 리터럴
- 리터럴: 소스 코드에 고정된 값으로 직접 표현된 데이터 자체 (예:
elapsedTime = totalDuration * 60;
에서60
이 리터럴)- 문제점
- 코드 내 직접 사용된 리터럴은 의미 파악이 어렵다.
- 동일 리터럴 값 변경 시 모든 출현 위치 수정 필요, 유지보수 난해
- 문제점
- 상수: 리터럴 값에 의미 있는 이름을 부여한 것. 프로그램 실행 중 값 변경 불가
(예:const double PI_VALUE = 3.1415926535;
)- 장점
- 이름을 통해 값의 의미 명확화, 코드 가독성 향상
- 값 변경 시 상수 정의부만 수정하면 일괄 적용, 유지보수성 증대
- 장점
배정문
변수가 가리키는 메모리 위치에 값을 할당하는 명령문
예: finalAmount = principal + interest;
principal
변수 값과interest
변수 값을 더한다.- 덧셈 결과를
finalAmount
변수의 메모리 공간에 저장한다.
- 연산자 우선순위: 한 표현식 내 여러 연산자 사용 시 실행 순서 결정 규칙 (예: 곱셈/나눗셈이 덧셈/뺄셈보다 우선) 괄호
()
로 명시적 순서 지정 가능 - 중복 정의 (오버로딩): 동일 연산자 기호가 피연산자 자료형에 따라 다른 연산 수행
10 + 5
: 정수 덧셈 (결과:15
)"User" + "_" + "Name"
: 문자열 연결 (결과:"User_Name"
)
제어문
프로그램 실행 흐름을 기본 순차 구조에서 벗어나 분기하거나 반복하도록 제어하는 명령문
- 구조적 프로그래밍: 제어 흐름을 순차, 선택, 반복 세 가지 기본 구조 조합으로 표현
- 주요 제어 구조
- 순차: 코드 작성 순서대로 실행
- 선택: 조건식 결과(참/거짓)에 따라 실행 경로 결정 (예:
if-else
,switch-case
) - 반복: 특정 조건 만족 동안 코드 블록 반복 실행 (예:
for
,while
) - 제어 추상화: 복잡한 제어 로직을 함수 호출 등으로 단순화
- 병렬 처리: 여러 제어 흐름 동시 진행
goto
문- 프로그램 내 특정 레이블로 실행 흐름 강제 이동
- 장점: 이론상 모든 제어 흐름 구현 가능
- 단점
- 코드 흐름 추적을 어렵게 하여 스파게티 코드 유발 가능성. 이는 디버깅과 수정을 매우 어렵게 한다.
- 프로그램 지역성 저해로 CPU 캐시 효율 감소 우려
프로그램 단위
복잡한 프로그램을 효율적으로 개발, 관리하려면 전체를 기능별 모듈로 나누는 전략이 필수적이다.
- 프로그램 단위는 부프로그램, 서브루틴, 프로시저, 메서드, 함수 등 다양하게 지칭한다.
단계적 개선법
주어진 문제를 해결 가능한 더 작은 하위 문제들로 반복 분해하고, 각 하위 문제 해결책들을 조합하여 원래 문제를 해결하는 설계 기법
- 장점
- 복잡도 감소: 잘 정의된 작은 문제 처리 용이
- 정확성 향상: 단위별 독립적 테스트 및 검증 용이
- 재사용성 증가: 잘 설계된 단위는 타 프로젝트 재활용 가능
- 재귀적 해결 용이: 문제 구조가 자기 유사 형태일 때 효과적
- 병렬 개발/처리: 다수 개발자 단위 분담 개발 또는 단위 동시 실행 가능
함수
특정 작업을 수행하기 위해 관련된 일련의 명령문들을 묶어 이름을 부여한 단위
- 함수 내부 구현을 알 필요 없이 이름과 입출력 인터페이스만으로 사용 가능한 제어 추상화 제공
- 함수 사용 이점은 단계적 개선법 이점과 유사 (복잡도 감소, 정확성, 재사용성, 모듈성 등)
함수와 제어 흐름
- 호출: 함수 호출문 만나면 해당 함수 호출
- 제어 이동: 현재 실행 위치(복귀 주소) 저장 후, 호출된 함수 시작 지점으로 제어 이동
- 함수 실행: 함수 본문 코드 순차 실행
- 반환: 함수 실행 완료 시(보통
return
문), 저장된 복귀 주소로 제어 복귀. 결과값 반환 가능
변수 참조 범위 (스코프)
변수 이름이 유효하게 인식되고 사용될 수 있는 코드 영역
- 지역 변수: 함수 내부 선언, 해당 함수 내에서만 유효
- 함수 외부 접근 불가
- 함수 시작 시 생성, 종료 시 소멸
- 서로 다른 함수 내 동일 이름 지역 변수 사용 가능
- 전역 변수: 함수 외부, 프로그램 최상위 레벨 선언
- 프로그램 전체 영역 접근 가능
- 프로그램 시작 시 생성, 프로그램 실행 동안 유지
- 주의점: 여러 함수에서 전역 변수 공유 및 수정 시 상태 변화 추적 어렵고, 의도치 않은 상호작용으로 오류 발생 가능
매개변수
함수 호출 측(Caller)과 호출된 함수(Callee) 간 데이터 전달 매개체
- 매개변수 사용으로 함수 일반화, 코드 재사용성 향상
- 형식 매개변수: 함수 정의 시 선언되는 변수. 함수 내부에서 사용될 입력 값의 이름과 타입 지정
# 형식 매개변수: base, exponent
def power(base, exponent):
# ...
pass
- 실질 매개변수 (인자): 함수 호출 시 전달하는 실제 값이나 변수. 형식 매개변수에 대응되어 값 전달
# 실질 매개변수 (인자): 2, 10
result = power(2, 10)
데이터 전달 방식
실질 매개변수 값을 형식 매개변수로 전달하는 방식
값에 의한 전달 (Pass by Value)
- 실질 매개변수의 값을 복사하여 형식 매개변수에 전달
- 함수 내 형식 매개변수 값 변경이 원본 실질 매개변수에 영향 없음
- 단점: 큰 데이터 전달 시 복사 오버헤드 발생 가능
# 값에 의한 전달 개념
def try_to_modify_number(num):
num = num + 5
print(f"함수 내부 값: {num}") # 함수 내에서 변경
original_number = 20
try_to_modify_number(original_number)
print(f"함수 외부 원본 값: {original_number}") # 원본 유지
# 출력:
# 함수 내부 값: 25
# 함수 외부 원본 값: 20
참조에 의한 전달 (Pass by Reference)
- 실질 매개변수의 메모리 주소(참조)를 형식 매개변수에 전달
- 형식 매개변수는 원본 데이터를 직접 가리킴
- 함수 내 형식 매개변수를 통한 데이터 변경이 원본 실질 매개변수에도 반영
- 장점: 큰 데이터 전달 시 주소값만 전달하므로 효율적
- 단점: 함수가 의도치 않게 원본 데이터 수정 위험
# 참조에 의한 전달 개념
def add_element_to_list(data_list):
data_list.append("newItem")
print(f"함수 내부 리스트: {data_list}") # 함수 내에서 리스트 변경
my_data_list = ["item1"]
add_element_to_list(my_data_list)
print(f"함수 외부 원본 리스트: {my_data_list}") # 원본 리스트도 변경됨
# 출력:
# 함수 내부 리스트: ['item1', 'newItem']
# 함수 외부 원본 리스트: ['item1', 'newItem']
값을 반환하는 함수
- 함수가 수행한 작업 결과를 호출한 곳으로 되돌려주는 기능
- 대부분 언어에서
return
키워드 사용 - 값 반환 없는 함수도 있으며, C/Java 등은 반환 타입
void
명시
# 함수 반환
def multiply_numbers(x, y):
product = x * y
return product # 결과 반환
calculation_result = multiply_numbers(7, 6) # 반환값 42가 변수에 저장
print(calculation_result)
언어 구현
작성된 고급 언어 소스 코드가 실제 컴퓨터에서 실행되려면 언어 구현 시스템에 의한 복잡한 처리 과정이 필요하다.
언어 구현의 주요 단계
- 언어 명세 정의: 문법(Syntax), 의미(Semantics), 제공 기능(표준 라이브러리 등) 공식 정의.
- 번역기 개발 (컴파일러/인터프리터): 소스 코드를 기계어나 중간 형태로 변환하는 프로그램 개발.
- 런타임 시스템 구축: 프로그램 실행 시 필요한 환경 제공 (메모리 관리, 입출력, 예외 처리 등).
- 코드 최적화: 생성된 코드의 효율성 증대를 위한 변환.
- 디버깅 및 테스팅 도구 제공: 오류 탐색 및 수정, 품질 검증 지원 도구.
- 문서화 및 배포: 사용자용 매뉴얼, 튜토리얼 작성 및 번역기/도구 배포.
언어의 번역 과정

고급 언어 소스 코드를 기계가 이해 가능한 형태로 변환하는 과정. 컴파일러는 일반적으로 다음 단계를 거친다.
어휘 분석
소스 코드를 문자 단위로 읽어 문법적 의미 최소 단위인 토큰으로 분할하는 단계
- 어휘 분석기가 토큰을 키워드, 식별자, 연산자 등으로 분류한 후 분류 코드로 인코딩한 토큰 스트림을 구문 분석기로 전달한다.
구문 분석
토큰 스트림이 언어의 문법 규칙에 맞는지 검사하고 코드 구조 파악하는 단계
- 구문 분석 행위를 파싱(Parsing), 행위자를 파서(Parser)라고 하며, 파싱의 결과를 코드 구조는 계층적 트리 형태인 Parse tree로 표현한다.
- 심볼 테이블: 파싱 중 발견되는 식별자(변수, 함수 등)의 속성(이름, 타입, 범위, 메모리 위치 등) 저장 및 관리 자료 구조. 이후 의미 분석 및 코드 생성 시 참조. (예:
a - b / c
에서 연산자 의미 결정을 위해a, b, c
타입 정보 필요) - 의미 분석: 구문적으로는 올바르나 의미상 맞지 않는 오류 검출
- 타입 검사: 연산 피연산자 타입 호환성 확인 (예: 문자열에 산술 연산 시도 검출)
- 선언 검사: 변수/함수 사용 전 선언 여부 확인
- 유효 범위 규칙 검사: 변수/함수가 정의된 유효 범위 내 사용 여부 확인
- 타입 변환 처리: 필요시 암시적/명시적 타입 변환 규칙 적용
- 강한 타입 규칙: 타입 불일치 엄격 검사, 오류 처리
- 약한 타입 규칙: 타입 불일치 시 컴파일러가 암묵적 타입 변환(Coercion) 시도
- 타입 강제 변환 (캐스팅): 프로그래머가 명시적으로 타입 변환
코드 생성
의미 분석 완료된 중간 표현과 심볼 테이블 정보 바탕으로, 목표 아키텍처 실행 가능 기계어 또는 어셈블리 코드 생성하는 단계
- 코드 최적화: 생성 코드의 실행 속도 향상 또는 메모리 사용량 감소를 위해 코드를 의미적으로 동일하면서 더 효율적 형태로 변환
객체지향 프로그래밍 (OOP)
현대 소프트웨어 개발에서 널리 사용되는 객체지향 패러다임의 핵심 개념이다.
객체지향 패러다임의 본질
- 실세계 개체를 속성(데이터)과 행위(메서드)를 가지는 객체로 모델링하고, 객체 간 상호작용으로 프로그램 구성
- 데이터와 이를 조작하는 절차를 하나의 단위(객체)로 묶어 관리, 프로그램 모듈성, 재사용성, 유지보수성 향상 목표
- 객체지향적 문제 해결 과정
- 문제 영역 객체 식별 (예: 온라인 서점 시스템 - 고객, 도서, 주문)
- 각 객체의 속성과 행위 정의 (예: 도서 객체 - 제목, 저자, 가격 속성 / 재고확인, 정보조회 메서드)
- 객체 간 관계 설정 및 메시지 전달(메서드 호출)을 통한 상호작용 구현
클래스와 객체
- 클래스: 동일 종류 객체들이 공유하는 속성과 행위를 정의한 설계도 또는 틀
- 객체: 클래스에 정의된 내용을 바탕으로 메모리에 실제 생성된 실체(인스턴스)
- 속성: 객체의 특징이나 상태 데이터
- 행위/동작: 객체가 수행 가능한 기능이나 연산
# Python 클래스와 객체
class Vehicle: # 'Vehicle' 클래스 정의
# 클래스 변수 (모든 Vehicle 객체 공유)
vehicle_type = "Generic Transport"
# 생성자 메서드: 객체 생성 시 초기화
def __init__(self, id_number, max_speed):
# 인스턴스 변수 (각 Vehicle 객체 고유 값 저장)
self.id_number = id_number # self는 생성되는 객체 자신
self.max_speed = max_speed
self.current_speed = 0
# 인스턴스 메서드: 객체 행위 정의
def accelerate(self, amount):
if self.current_speed + amount <= self.max_speed:
self.current_speed += amount
else:
self.current_speed = self.max_speed
print(f"Vehicle {self.id_number}: Speed is now {self.current_speed} km/h.")
def get_status(self):
return f"ID: {self.id_number}, Type: {self.vehicle_type}, Speed: {self.current_speed}/{self.max_speed} km/h"
# 객체(인스턴스) 생성
car1 = Vehicle("V001", 180)
truck1 = Vehicle("T001", 120)
Vehicle.vehicle_type = "Land Vehicle" # 클래스 변수 변경은 모든 인스턴스에 영향 (만약 인스턴스에서 오버라이드 안했다면)
# 객체 속성 접근 및 메서드 호출
car1.accelerate(50)
print(truck1.get_status())
# 출력 예시:
# Vehicle V001: Speed is now 50 km/h.
# ID: T001, Type: Land Vehicle, Speed: 0/120 km/h
OOP의 주요 특징
상속
기존 클래스(부모 클래스)의 속성과 메서드를 새로운 클래스(자식 클래스)가 물려받아 사용하는 메커니즘
- 코드 재사용: 부모 클래스 코드 중복 작성 불필요
- 계층 구조 형성: 클래스 간 'is-a' 관계 표현, 프로그램 구조 명확화
- 확장 용이성: 기존 기능 수정 또는 새 기능 추가 용이
- 유지보수 효율: 공통 기능 부모 클래스 관리, 수정 시 파급 효과 제어 용이
- 메서드 오버라이딩: 자식 클래스에서 부모로부터 물려받은 메서드를 자신에 맞게 재정의
다형성
상속 관계에서 서로 다른 클래스 객체들이 각자의 방식으로 다르게 동작하는 것
- 유연성 및 확장성: 코드 변경 없이 새 자식 클래스 추가로 시스템 기능 확장 가능
- 코드 간결성: 다양한 타입 객체를 동일 방식으로 처리, 코드 간결화
캡슐화
객체의 속성과 해당 데이터를 조작하는 메서드를 하나로 묶고, 객체 내부 구현 세부 사항을 외부로부터 감추는(정보 은닉) 원칙
- 외부에서는 객체가 공개한 인터페이스를 통해서만 객체 상태 접근 및 변경 제한
- 접근 제어자: 캡슐화 지원, 클래스 멤버(변수, 메서드) 외부 접근 가능 범위 지정 키워드
public
: 어디서든 접근 가능protected
: 동일 패키지 또는 상속받은 자식 클래스에서 접근 가능private
: 해당 클래스 내부에서만 접근 가능- (Python은 키워드 대신
_
(보호된 멤버 컨벤션)나__
(비공개 멤버 이름 장식) 같은 이름 규칙 사용)
병렬 처리
물리적으로 동시에 여러 작업 실행. 멀티 코어 CPU에서 각 코어가 다른 작업 동시 수행하는 것
- 활성화: 병행/병렬 처리 기본 단위. 운영체제 수준에서는 프로세스, 프로세스 내에서는 스레드가 대표적
- 대부분 프로그래밍 언어는 스레드 생성/관리, 동기화 등을 위한 라이브러리나 문법 제공
레퍼런스
“이 글은 『컴퓨터 과학 총론』(Brookshear & Brylow, 2019)의 내용을 토대로 재구성된 자료입니다.”
💻 이 글은 컴퓨터시스템개론 시리즈의 일부입니다.
← 이전글: 네트워크 | 다음글: 소프트웨어 공학 →