리터럴(Literal)과 상수(Constant)의 차이: 프로그래밍 언어별 비교와 역사

수학에서는 숫자가 상수인데, 프로그래밍에서는 숫자가 리터럴.

들어가며

프로그래밍에서 ‘변하지 않는 것’을 표현하는 방법은 여러 가지가 있습니다. 그중에서도 가장 기본이 되는 개념이 리터럴(Literal)과 상수(Constant)입니다. 이 두 용어는 종종 혼용되지만, 엄밀히 말하면 서로 다른 개념을 나타냅니다. 이 글에서는 리터럴과 상수의 차이점, 그 역사적 배경, 그리고 다양한 프로그래밍 언어에서의 활용 방법에 대해 알아보겠습니다.

리터럴(Literal)이란?

리터럴은 소스 코드에 직접 표현된 고정된 값을 의미합니다. 즉, 프로그램 내에서 변하지 않는 데이터 그 자체를 가리킵니다.

int x = 5;          // 5는 정수 리터럴
double pi = 3.14;   // 3.14는 부동소수점 리터럴
char grade = 'A';   // 'A'는 문자 리터럴
String name = "John"; // "John"은 문자열 리터럴
boolean isValid = true; // true는 불리언 리터럴

리터럴의 역사

‘리터럴’이라는 용어는 라틴어 ‘literalis’에서 유래했으며, ‘문자 그대로의’라는 의미를 가집니다. 프로그래밍 언어에서 이 용어가 사용되기 시작한 것은 초기 컴파일러와 인터프리터의 개발 시기로 거슬러 올라갑니다.

1950년대 FORTRAN과 같은 초기 프로그래밍 언어에서는 이미 숫자와 문자를 직접 코드에 표현하는 방식이 존재했습니다. 그러나 ‘리터럴’이라는 용어가 공식적으로 사용되기 시작한 것은 1960년대 ALGOL과 같은 언어부터입니다. 이후 C, Pascal 등의 언어를 거쳐 현대 프로그래밍 언어에 이르기까지 리터럴은 코드 표현의 기본 요소로 자리 잡았습니다.

상수(Constant)란?

상수는 프로그램 실행 중에 값이 변하지 않는 변수를 의미합니다. 상수는 이름을 가지며, 그 이름에 값(보통 리터럴)이 할당됩니다.

// Java에서의 상수 선언
final double PI = 3.14159;
final int MAX_USERS = 100;

// C++에서의 상수 선언
const double PI = 3.14159;
const int MAX_USERS = 100;

상수의 역사

‘상수’라는 개념은 수학에서 오랫동안 사용되어 온 개념으로, 변하지 않는 값을 의미합니다. 프로그래밍에서 상수의 개념이 도입된 것은 1960년대 ALGOL 등의 언어부터입니다. ALGOL에서는 const 키워드를 사용해 상수를 선언할 수 있었습니다.

C 언어(1972년)에서는 처음에 상수를 선언하는 전용 키워드가 없었으나, 전처리기 지시문인 #define을 통해 상수와 유사한 기능을 구현했습니다.

#define PI 3.14159

이후 C++에서는 const 키워드가 도입되었고, Java에서는 final 키워드를 사용하게 되었습니다. 각 언어마다 상수를 표현하는 방식은 조금씩 다르지만, 변하지 않는 값을 표현한다는 본질은 같습니다.

리터럴과 상수의 차이점

1. 정의 측면

  • 리터럴: 소스 코드에 직접 표현된 값 자체
  • 상수: 변하지 않는 값을 가진 명명된 변수

2. 표현 측면

  • 리터럴: 3, ‘A’, “Hello”, true와 같이 값 그 자체로 표현
  • 상수: final int MAX_VALUE = 100과 같이 키워드와 변수명으로 표현

3. 메모리 측면

  • 리터럴: 프로그램의 상수 풀(constant pool)이나 코드 영역에 저장
  • 상수: 변수와 같이 메모리의 데이터 영역에 저장

4. 사용성 측면

  • 리터럴: 한 번 사용하거나 의미가 명확한 값에 적합
  • 상수: 반복적으로 사용되거나 의미 부여가 필요한 값에 적합

어셈블리 언어에서의 리터럴과 상수

어셈블리 언어에서는 고급 프로그래밍 언어와 달리 리터럴과 상수의 구분이 다소 모호합니다. 이는 어셈블리 언어가 하드웨어에 더 가까운 저수준 언어이기 때문입니다.

어셈블리에서의 숫자 표현

어셈블리 언어에서 숫자는 다음과 같이 다양한 방식으로 사용됩니다:

  1. 즉시값(Immediate value): 명령어에 직접 포함된 숫자값 MOV AX, 42 ; 42는 즉시값(immediate value)
  2. 상수 정의: 어셈블리에서는 일반적으로 EQU, .CONST 또는 유사한 지시어를 사용하여 상수를 정의합니다 MAX_COUNT EQU 100 ; MAX_COUNT라는 상수를 100으로 정의

어셈블리에서의 리터럴과 상수 구분

어셈블리에서 더 명확하게 구분하자면:

  • 리터럴: 어셈블리 코드에 직접 나타나는 숫자 값 자체 (10, 0xFF 등) MOV AX, 10 ; 10은 리터럴
  • 상수: 어셈블리 지시어(directive)를 통해 정의된, 이름이 있는 불변 값 BUFFER_SIZE EQU 1024 ; BUFFER_SIZE는 상수 MOV BX, BUFFER_SIZE ; 컴파일러는 BUFFER_SIZE를 1024로 대체

어셈블리에서 숫자는 즉시값(immediate value)으로 사용될 때 컴파일 시점에 고정되므로, 이런 의미에서 ‘상수’로 볼 수도 있습니다. 그러나 고급 언어의 용어 정의를 적용한다면, 어셈블리 코드에 직접 나타나는 숫자는 리터럴로 보는 것이 더 정확합니다.

다양한 프로그래밍 언어에서의 리터럴과 상수

C/C++

C와 C++에서는 상수를 선언하는 방법이 여러 가지 있습니다:

// C에서의 상수 선언 (전처리기 사용)
#define PI 3.14159

// C/C++에서의 상수 선언 (const 키워드 사용)
const double PI = 3.14159;

// C++11부터 지원하는 컴파일 타임 상수
constexpr int MAX_BUFFER = 1024;

리터럴은 다음과 같이 표현됩니다:

int a = 10;             // 10은 정수 리터럴
double b = 3.14;        // 3.14는 부동소수점 리터럴
char c = 'A';           // 'A'는 문자 리터럴
const char* s = "Hello"; // "Hello"는 문자열 리터럴

C++에서는 사용자 정의 리터럴(User-defined literals)도 지원합니다:

// C++11 이상
Distance operator"" _km(long double val) {
    return Distance(val * 1000);
}

auto distance = 5.0_km; // 사용자 정의 리터럴

Java

Java에서는 final 키워드를 사용하여 상수를 선언합니다:

// 인스턴스 상수
final double PI = 3.14159;

// 클래스 상수
static final int MAX_CONNECTIONS = 100;

// 인터페이스에서의 상수 (public static final이 기본)
interface Constants {
    int MAX_USERS = 1000;
}

Java에서의 리터럴:

int a = 10;          // 10은 정수 리터럴
long b = 100L;       // 100L은 long 타입 리터럴
double c = 3.14;     // 3.14는 부동소수점 리터럴
char d = 'A';        // 'A'는 문자 리터럴
String e = "Hello";  // "Hello"는 문자열 리터럴
boolean f = true;    // true는 불리언 리터럴

Python

Python에서는 상수를 선언하는 특별한 키워드가 없지만, 관례적으로 대문자 변수명을 사용하여 상수를 표현합니다:

# Python에서의 "상수" (관례적)
PI = 3.14159
MAX_CONNECTIONS = 100

# 모듈 수준의 "상수"
# constants.py
MAX_USERS = 1000

Python에서의 리터럴:

a = 10        # 10은 정수 리터럴
b = 3.14      # 3.14는 부동소수점 리터럴
c = 'A'       # 'A'는 문자 리터럴
d = "Hello"   # "Hello"는 문자열 리터럴
e = True      # True는 불리언 리터럴
f = None      # None은 None 리터럴
g = [1, 2, 3] # [1, 2, 3]은 리스트 리터럴
h = {'a': 1}  # {'a': 1}은 딕셔너리 리터럴

JavaScript

JavaScript에서는 const 키워드를 사용하여 상수를 선언합니다:

// JavaScript에서의 상수
const PI = 3.14159;
const MAX_CONNECTIONS = 100;

JavaScript에서의 리터럴:

let a = 10;              // 10은 숫자 리터럴
let b = 'A';             // 'A'는 문자 리터럴
let c = "Hello";         // "Hello"는 문자열 리터럴
let d = true;            // true는 불리언 리터럴
let e = null;            // null은 null 리터럴
let f = undefined;       // undefined는 undefined 리터럴
let g = [1, 2, 3];       // [1, 2, 3]은 배열 리터럴
let h = {name: "John"};  // {name: "John"}은 객체 리터럴
let i = /\d+/;           // /\d+/는 정규식 리터럴
let j = `Hello ${name}`; // `Hello ${name}`은 템플릿 리터럴

리터럴과 상수의 활용

매직 넘버(Magic Number) 제거

프로그래밍에서 의미 없는 숫자(매직 넘버)를 직접 사용하는 것은 좋지 않은 관행입니다. 이런 경우 의미 있는 이름의 상수를 사용하면 코드의 가독성과 유지보수성이 향상됩니다.

// 좋지 않은 예 (매직 넘버 사용)
if (status == 404) {
    System.out.println("페이지를 찾을 수 없습니다.");
}

// 좋은 예 (상수 사용)
public static final int HTTP_NOT_FOUND = 404;

if (status == HTTP_NOT_FOUND) {
    System.out.println("페이지를 찾을 수 없습니다.");
}

코드 유지보수성 향상

상수를 사용하면 프로그램 전체에서 동일한 값을 여러 곳에서 사용할 때 한 곳에서만 수정하면 되므로 유지보수가 용이해집니다.

// 세금 계산 프로그램
public class TaxCalculator {
    // 세율이 변경되면 여기만 수정하면 됨
    public static final double TAX_RATE = 0.1;
    
    public double calculateTax(double amount) {
        return amount * TAX_RATE;
    }
}

타입 안전성 향상

상수를 적절히 사용하면 타입 안전성도 향상됩니다. 특히 C++이나 Java와 같은 정적 타입 언어에서는 상수에 타입이 명시적으로 지정되므로, 타입 불일치로 인한 오류를 줄일 수 있습니다.

// 정수형 상수
public static final int MAX_RETRY_COUNT = 3;

// 문자열 상수
public static final String DEFAULT_ENCODING = "UTF-8";

열거형(Enum)을 통한 상수 그룹화

관련된 상수들은 열거형(Enum)으로 그룹화하면 코드의 가독성과 유지보수성이 더욱 향상됩니다.

// Java에서의 열거형 상수
public enum HttpStatus {
    OK(200),
    NOT_FOUND(404),
    INTERNAL_SERVER_ERROR(500);
    
    private final int code;
    
    HttpStatus(int code) {
        this.code = code;
    }
    
    public int getCode() {
        return code;
    }
}

// 사용 예
if (status == HttpStatus.NOT_FOUND.getCode()) {
    System.out.println("페이지를 찾을 수 없습니다.");
}

결론

리터럴과 상수는 프로그래밍에서 불변성을 표현하는 두 가지 중요한 개념입니다. 비록 언어마다 구현 방식은 다르지만, 핵심 개념은 동일합니다.

  • 리터럴은 소스 코드에 직접 표현된 값 자체를 의미합니다.
  • 상수는 변하지 않는 값을 가진 명명된 변수를 의미합니다.

이 두 개념의 차이를 간단히 요약하면 다음과 같습니다:

  • 공통점: 둘 다 변하지 않는 특성을 가집니다.
  • 차이점:
    • 상수는 변하지 않는 ‘변수’입니다.
    • 리터럴은 변하지 않는 ‘값’ 또는 ‘데이터’입니다.

어셈블리와 같은 저수준 언어에서는 이 두 개념이 다소 모호하게 혼용될 수 있지만, 고급 언어에서는 명확하게 구분됩니다.

잘 설계된 코드에서는 리터럴과 상수를 적절히 사용하여 코드의 가독성, 유지보수성, 그리고 의미 명확성을 향상시킵니다. 특히 매직 넘버와 같은 의미 없는 리터럴 값은 가급적 상수로 대체하여 코드의 품질을 높이는 것이 좋은 프로그래밍 관행입니다.

코멘트

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다