Skip to Content
독학사독학사 3단계프로그래밍언어론10. 자료형과 형 시스템: 강/약, 정적/동적, 형 검사

이번 문서의 목표: 자료형을 원시형·구조형·사용자 정의형으로 분류하고, 강형/약형과 정적/동적 형 시스템의 차이를 같은 연산의 실행 결과로 직접 구분하며, 형 검사·형 추론·형 변환(암묵적/명시적)의 개념과 흔한 함정을 설명할 수 있게 된다.

왜 형 시스템을 따로 배우는가

09편에서 형 바인딩이 정적인지 동적인지(선언 시 고정되는지, 값이 바뀔 때마다 갱신되는지)를 다뤘습니다. 그런데 “형이 언제 바인딩되는가”와 “형이 얼마나 엄격하게 지켜지는가”는 서로 다른 질문입니다. 예를 들어 두 언어 모두 동적으로 형이 바인딩되더라도, 한 언어는 문자열과 숫자를 섞으면 즉시 오류를 내고 다른 언어는 알아서 변환해 계산을 진행할 수 있습니다. 이 “얼마나 엄격한가”를 다루는 것이 형 시스템(type system, 형 시스템)이며, 독학사 시험에서 “다음 중 형 시스템에 대한 설명으로 옳은 것은”류의 문항이 자주 나오는 만큼 이번 편에서 개념 사이의 경계를 정확히 그립니다.

쉽게 말하면: 형 바인딩이 “이 변수가 어떤 종류의 값을 담는가를 언제 정하는가”라면, 형 시스템은 “서로 다른 종류의 값을 섞으려 할 때 얼마나 깐깐하게 검사하는가”입니다.

1. 자료형의 분류 — 원시형·구조형·사용자 정의형

프로그래밍 언어가 다루는 자료형(data type)은 크게 세 층으로 나눌 수 있습니다.

  • 원시형(primitive type, 원시형): 언어가 기본으로 제공하는, 더 이상 다른 자료형의 조합으로 쪼갤 수 없는 최소 단위입니다. 정수형(int), 부동소수점형(float, double), 문자형(char), 불리언형(bool) 등이 해당합니다. 하드웨어가 직접 지원하는 표현 방식과 가까운 경우가 많습니다.
  • 구조형(structured type, 구조형): 원시형이나 다른 구조형을 여러 개 조합해 만드는 자료형입니다. 배열·리스트·레코드(구조체)·공용체·집합 등이 여기 속하며, 각각의 설계·위험·언어별 차이는 11편에서 깊이 다룹니다. 이번 편에서는 “형 시스템” 관점에서 이 구조형들이 강형/약형·정적/동적 검사와 어떻게 얽히는지만 짚습니다.
  • 사용자 정의형(user-defined type, 사용자 정의형): 언어가 미리 준비해 둔 것이 아니라 프로그래머가 직접 새 이름과 구조를 정의하는 자료형입니다. 열거형(enum, 이름 붙은 상수들의 집합), 클래스(class, 객체지향 언어에서 데이터와 동작을 함께 정의), 구조체 정의(예: C의 struct 선언 자체) 등이 해당합니다. 사용자 정의형을 이용해 프로그래머가 문제 영역의 개념을 코드로 직접 표현할 수 있게 되며, 이는 17편(추상 자료형)·18편(OOP 지원)에서 캡슐화·클래스와 함께 더 깊이 다룰 주제입니다.

2. 강형(strong typing)과 약형(weak typing) — 같은 연산, 다른 결과

강형 언어(strongly typed language)는 서로 다른 자료형의 값을 연산에 함께 쓰려 할 때 컴파일러나 인터프리터가 암묵적인 변환을 허용하지 않고 오류로 처리하는 경향이 강한 언어입니다. 약형 언어(weakly typed language)는 반대로 서로 다른 자료형을 알아서 변환해 연산을 계속 진행시키는 경향이 강한 언어입니다.

쉽게 말하면: 강형은 “형이 다르면 안 섞어줘, 네가 직접 맞춰”라고 말하는 깐깐한 규칙이고, 약형은 “일단 어떻게든 맞춰서 계산해 줄게”라고 말하는 느슨한 규칙입니다.

이 차이를 같은 의도의 연산(문자열 "5"와 숫자 3+로 결합하려는 시도)을 서로 다른 두 언어로 실행해 대조합니다.

// JavaScript — 약형에 가까운 언어 let 결과 = "5" + 3; console.log(결과); console.log(typeof 결과);
53 string
# Python — 강형에 가까운 언어 결과 = "5" + 3 print(결과)
TypeError: can only concatenate str (not "int") to str
구분JavaScript(약형에 가까움)Python(강형에 가까움)
"5" + 3의 처리숫자 3을 암묵적으로 문자열 "3"으로 변환한 뒤 이어붙여 "53"을 만든다문자열과 정수를 섞을 수 없다고 판단해 즉시 TypeError(형 오류)를 발생시킨다
프로그래머의 부담편리하지만, 의도치 않은 자동 변환으로 버그가 생기기 쉽다번거롭지만, 형이 안 맞는 실수를 실행 즉시 알려준다

결과 해석: 두 언어 모두 "5"가 문자열이고 3이 정수라는 사실은 똑같이 알고 있습니다. 차이는 형이 다른 값끼리 연산을 시도했을 때 언어가 취하는 태도입니다. JavaScript는 “어떻게든 계산을 진행시켜 주는 것”을 우선해 정수를 문자열로 바꿔 이어붙이지만, Python은 “형이 다르면 프로그래머의 실수일 가능성이 크다”고 보고 계산을 멈춥니다. 자주 틀리는 점: 강형/약형과 정적/동적 형 시스템(다음 절)을 같은 축이라고 착각하기 쉽습니다. Python은 동적(변수 선언에 형을 명시하지 않고, 실행 중 값에 따라 형이 결정됨) 이면서 동시에 강형(형이 다르면 암묵적 변환 없이 오류)입니다. 이 둘은 서로 독립된 축입니다.

3. 정적 형 시스템 vs 동적 형 시스템 — 언제 검사하는가

형 검사(type checking, 형 검사)란 프로그램에서 어떤 연산에 사용된 값들의 형이 그 연산에 허용되는 형과 맞는지 확인하는 작업입니다. 이 검사가 언제 이루어지는가로 정적 형 시스템과 동적 형 시스템을 나눕니다.

  • 정적 형 시스템(static type system): 프로그램을 실행하기 전, 컴파일 시점에 모든 변수·표현식의 형을 확정하고 형 오류를 미리 찾아냅니다. C, Java, TypeScript, Rust가 해당합니다.
  • 동적 형 시스템(dynamic type system): 형 검사를 프로그램이 실제로 그 연산을 실행하는 순간에야 수행합니다. Python, JavaScript, Ruby가 해당합니다.
// Java — 정적 형 시스템: 컴파일 시점에 오류가 잡힌다 int 개수 = 5; String 이름 = "홍길동"; int 합계 = 개수 + 이름; // 컴파일 오류: int와 String을 더할 수 없음 (프로그램 실행 자체가 되지 않음)
# Python — 동적 형 시스템: 이 코드가 실제로 실행되는 줄까지 가야 오류를 안다 def 함수1(): print("함수1 시작") def 함수2(x): return x + "문자열" # 이 줄이 호출되지 않으면 오류가 드러나지 않는다 함수1() # 함수2(5)를 호출하지 않으면 프로그램은 오류 없이 끝까지 실행된다
함수1 시작
구분정적 형 시스템동적 형 시스템
형 검사 시점컴파일 시(실행 전)실행 시(그 코드 줄이 실제로 실행될 때)
오류 발견 시점코드를 실행하기도 전에 전부 발견실행 경로가 그 코드를 지나가야만 발견(위 예시처럼 호출하지 않으면 끝까지 안 드러남)
대표 언어C, Java, TypeScript, RustPython, JavaScript, Ruby
장점배포 전 형 오류를 대부분 걸러내 신뢰성이 높다형을 매번 선언하지 않아도 되어 코드 작성이 빠르고 유연하다
단점형을 매번 선언해야 해 코드가 다소 장황해진다흔치 않은 실행 경로에 숨은 형 오류가 배포 후에야 드러날 수 있다

자주 틀리는 점: “정적 형 시스템은 형 오류가 전혀 안 난다”가 아닙니다. 컴파일 시점에 잡을 수 있는 형 오류를 미리 잡아줄 뿐이며, 형변환을 잘못 강제하거나(다음 절) 배열 경계를 벗어나는 등 컴파일러가 판단할 수 없는 오류는 정적 형 언어에서도 실행 시점에 발생합니다.

4. 형 검사와 형 추론

정적 형 시스템에서 컴파일러가 형을 검사하려면 우선 모든 변수의 형을 알아야 합니다. 전통적으로는 프로그래머가 int x;처럼 형을 직접 명시해야 했지만, 최근 언어는 형 추론(type inference, 형 추론)을 지원해 초기화 값을 보고 컴파일러가 형을 스스로 알아내게 합니다.

// Java 10 이상 — var는 형 추론을 쓰지만 여전히 정적 형이다 var 개수 = 5; // 컴파일러가 초기값 5를 보고 int로 추론 개수 = "문자열"; // 컴파일 오류! 개수의 형은 이미 int로 확정되었다
오류: incompatible types: String cannot be converted to int

자주 틀리는 점: “형 추론을 쓰면 동적 형 언어다”라고 착각하기 쉽지만, 위 예시처럼 var로 선언해도 형은 초기화되는 순간 정적으로 한 번 확정되고 이후 다른 형의 값을 대입하면 컴파일 오류가 납니다. 형 추론은 “형을 누가(프로그래머 vs 컴파일러) 적어 넣는가”의 문제이지, “형이 언제까지 고정되는가”의 문제(정적/동적 형 바인딩)와는 다른 축입니다. C++의 auto, TypeScript의 타입 추론도 같은 성격입니다.

5. 형 변환 — 암묵적 변환(coercion)과 명시적 변환(casting)

형 변환은 한 자료형의 값을 다른 자료형의 값으로 바꾸는 것입니다. 누가 그 변환을 지시했는지에 따라 두 가지로 나뉩니다.

  • 암묵적 변환(coercion, 암묵적 변환): 프로그래머가 요청하지 않았는데도 언어가 문맥에 맞춰 자동으로 수행하는 변환. 2절의 "5" + 3 예시에서 JavaScript가 3"3"으로 바꾼 것이 대표적입니다.
  • 명시적 변환(casting, 명시적 변환): 프로그래머가 (int), (double)처럼 직접 지시해 수행하는 변환.

형 변환은 크게 정보를 잃지 않는 넓힘 변환(widening conversion)과 정보를 잃을 수 있는 좁힘 변환(narrowing conversion)으로도 나뉩니다.

int 정수값 = 300; char 문자값 = (char)정수값; /* 명시적(강제) 좁힘 변환: char는 보통 1바이트(-128~127 또는 0~255) */ printf("%d\n", 문자값);
44
단계설명
원래 정수값300int는 보통 4바이트라 300을 그대로 표현 가능
char로 좁힘 변환300을 8비트(256으로 나눈 나머지)로 자름 → 300 mod 256 = 44char는 1바이트(8비트)라 300을 표현할 자리가 없어 상위 비트가 잘려나감
출력44원래 의도한 300이 아니라 잘려나간 44가 출력됨

결과 해석: 명시적으로 (char)를 써서 “이 변환을 원한다”고 컴파일러에게 알렸기 때문에 컴파일은 정상적으로 되지만, char가 담을 수 있는 범위(1바이트)보다 300이라는 값이 크기 때문에 상위 비트가 잘려나가 전혀 다른 값(44)이 됩니다. 자주 틀리는 점: “명시적으로 캐스팅했으니 안전하다”가 아닙니다. 캐스팅은 “이 변환을 허락해 달라”는 프로그래머의 지시일 뿐, 그 변환이 데이터 손실 없이 이루어짐을 보장해 주지는 않습니다. 좁힘 변환은 컴파일러가 문법적으로는 통과시켜도 값이 깨질 위험을 항상 프로그래머가 직접 검토해야 합니다.

6. 언어별 형 시스템 정리

지금까지 배운 두 축(강형/약형, 정적/동적)을 겹쳐 실제 언어들을 분류하면 다음과 같습니다.

언어정적/동적강형/약형비고
C정적약형에 가까움포인터·형변환을 상당히 자유롭게 허용해 형 안전성이 느슨하다
Java정적강형형 추론(var)을 지원해도 여전히 정적·강형
Python동적강형형 선언은 없지만 형이 다르면 암묵적 변환 없이 오류를 낸다(2절 참고)
JavaScript동적약형에 가까움형이 다른 값끼리도 상당 부분 암묵적으로 변환해 연산을 진행한다
Haskell정적강형형 추론이 매우 강력해 형 선언을 거의 생략할 수 있으면서도 정적·강형을 유지한다

이 표에서 보듯 정적/동적강형/약형은 서로 독립된 두 축입니다. 시험에서 “동적 형 언어는 모두 약형이다”처럼 두 축을 하나로 묶어 단정하는 보기가 자주 오답으로 등장하므로, Python(동적이면서 강형)이라는 반례를 기억해 두면 함정을 피할 수 있습니다.

핵심 정리

  • 자료형은 원시형(더 쪼갤 수 없는 기본 단위)·구조형(조합으로 만든 자료형, 11편에서 심화)·사용자 정의형(프로그래머가 새로 정의)으로 분류한다.
  • 강형은 형이 다른 값의 연산을 오류로 막고, 약형은 암묵적으로 변환해 계산을 진행시킨다 — "5" + 3이 JavaScript에서는 "53", Python에서는 오류가 되는 것이 그 예다.
  • 정적 형 시스템은 컴파일 시 형 오류를 미리 찾고, 동적 형 시스템은 그 코드가 실제로 실행되는 순간에야 형 오류를 발견한다.
  • 형 추론은 프로그래머 대신 컴파일러가 형을 알아내는 것일 뿐, 형이 정적으로 고정된다는 사실 자체는 바뀌지 않는다.
  • 형 변환은 언어가 자동으로 하는 암묵적 변환과 프로그래머가 지시하는 명시적 변환(캐스팅) 으로 나뉘며, 좁힘 변환은 캐스팅해도 값이 잘려나가는 함정이 있다.
  • 정적/동적과 강형/약형은 서로 독립된 축이다 — Python은 동적이면서 강형인 대표적 반례다.

마무리 복습

문제 14지선다
서로 다른 자료형의 값을 연산에 함께 쓰려 할 때, 암묵적인 변환 없이 오류로 처리하는 경향이 강한 언어를 가리키는 용어는?
문제 24지선다
JavaScript에서 '5' + 3을 실행하면 '53'이라는 문자열이 나오고, Python에서 같은 의도의 연산 '5' + 3을 실행하면 오류가 나는 이유로 가장 적절한 것은?
문제 34지선다
정적 형 시스템과 동적 형 시스템의 차이를 옳게 설명한 것은?
문제 44지선다
Java에서 다음처럼 형 추론 문법을 쓴 뒤 컴파일 오류가 나는 이유는?
var 개수 = 5;
개수 = 문자열값;
문제 54지선다
int 정수값 = 300;에서 (char)정수값으로 명시적으로 형 변환했더니 원래 의도와 다른 값(예: 44)이 나왔다. 이 상황이 보여주는 것으로 가장 적절한 것은?
문제 64지선다
Python과 JavaScript의 형 시스템에 대한 설명으로 옳은 것은?

참고 자료

Last updated on