파이썬(Python)은 데이터 타입에 무척 관대하다. 변수를 선언할 때 타입을 명시할 필요도 없고, 정수(int)의 크기가 아무리 커져도 메모리가 허락하는 한 무한대로 숫자를 담아낸다. 반면, 하드웨어를 직접 제어하던 C 언어의 유전자를 물려받은 C++은 데이터를 담는 '그릇의 크기'를 개발자가 1바이트 단위로 깐깐하게 지정해주어야 한다.
오늘은 C++의 기본 자료형(정수와 실수)을 다루면서, 파이썬에서는 신경 쓸 필요 없었던 메모리 크기 제어와 C++만의 특징들을 정리해 보았다.
1. 정수형과 실수형, 그리고 데이터 손실(Truncation)
변수에 정수(소수점 없음)와 실수(소수점 있음)를 담는다는 기본 개념은 어느 언어나 똑같다. 하지만 C++에서는 데이터를 담는 그릇의 크기가 엄격하게 나뉘어 있다.
- 정수형: short(2바이트), int(4바이트), long(4바이트), long long(8바이트)
- 실수형: float(4바이트), double(8바이트)
파이썬에서는 int 하나로 퉁치던 것들을 C++에서는 데이터의 최댓값을 예상해서 알맞은 바이트의 자료형을 선택해야 한다. 만약 이 규칙을 무시하고 자료형을 섞어 쓰면 어떻게 될까?
#include <iostream>
int main() {
float a = 3.14;
int b = 3.14; // 실수 값을 정수형 변수에 할당
std::cout << "a: " << a << ", b: " << b << std::endl;
// 출력 결과 -> a: 3.14, b: 3
return 0;
}
위 코드를 실행해 보면 b에는 소수점이 잘려나간 3만 저장된다. 파이썬이었다면 자동으로 b를 float으로 취급해 줬겠지만, C++은 "네가 정수형(int) 그릇을 준비했으니, 소수점 아래는 강제로 잘라버리겠다"라고 판단한다. 이를 '암시적 형변환(Implicit Type Conversion)'에 의한 데이터 손실이라고 부르며, 실무에서 버그를 유발하는 단골손님이므로 변수 선언 시 타입 매칭에 항상 주의해야 한다.
2. 부호의 통제: signed 와 unsigned
파이썬에는 없는, C와 C++에만 존재하는 아주 중요한 키워드가 바로 unsigned다.
기본적으로 모든 정수형 변수는 음수, 0, 양수를 모두 표현할 수 있는 signed 상태다. (굳이 signed int라고 적지 않아도 기본값이다.) 하지만 변수 앞에 unsigned를 붙이면 **"이 변수는 절대 음수를 가지지 않는다"**고 컴파일러에게 선언하는 것이다.
unsigned int a;
unsigned short b;
unsigned long c;
unsigned long long d;
🤔 왜 굳이 unsigned를 쓸까?
- 표현 범위 2배 증가: 4바이트 int는 대략 -21억 ~ +21억까지 표현한다. 여기서 음수를 포기하고 unsigned int로 선언하면, 양수 쪽으로 가용 범위가 몰리면서 0 ~ 약 42억까지 담을 수 있게 된다.
- 논리적 오류 방지: 사람의 나이, 배열의 인덱스 크기, 파일의 용량 등은 태생적으로 음수가 될 수 없다. 이때 unsigned를 사용하면 실수로 음수 값이 들어오는 것을 시스템 차원에서 원천 차단할 수 있다.
3. 시스템 한계치 확인: climits 라이브러리
파이썬의 무한한 정수와 달리, C++의 자료형은 한계치가 정해져 있다. 만약 int의 최댓값인 21억에 1을 더하면, 숫자가 0이나 음수로 훅 떨어져 버리는 오버플로우(Overflow)가 발생한다.
따라서 내 시스템에서 각 자료형이 정확히 몇 바이트인지, 최댓값이 얼마인지 확인하는 것은 매우 중요하다. 이때 사용하는 것이 <climits> 라이브러리다. ( C 언어의 <limits.h> 헤더 파일이 C++로 넘어오면서 앞에 'c'가 붙은 <climits>로 이름만 바뀌었을 뿐, 안에 들어있는 INT_MAX 같은 매크로 기능은 똑같다.)
#include <iostream>
#include <climits> // C 스타일의 한계치 매크로를 담은 헤더
using namespace std;
int main() {
int n_int = INT_MAX;
short n_short = SHRT_MAX;
// sizeof 연산자로 메모리 크기(바이트) 확인
cout << "int는 " << sizeof(n_int) << "바이트이다." << endl;
cout << "이 바이트의 최댓값은 " << n_int << "이다." << endl;
return 0;
}
💡 [추가 심화] 모던 C++에서의 한계치 확인: <limits>
강의에서는 <climits>와 INT_MAX 같은 매크로를 사용했지만, C++11 이후의 모던 C++(Modern C++)에서는 <limits> 헤더의 std::numeric_limits 템플릿 사용을 훨씬 더 권장한다.
매크로 방식은 C 언어의 잔재이며 타입에 안전하지 않기 때문이다. 객체지향적인 C++에 맞춰 코드를 작성한다면 아래와 같이 쓰는 것이 더 세련된 방식이다. ( <limits> 헤더의 std::numeric_limits<int>::max()는 C 언어에 없는 100% C++ 전용 문법입니다. C 언어에는 템플릿(< >)이나 객체, 네임스페이스(std::)라는 개념 자체가 없기 때문이다. )
#include <iostream>
#include <limits> // 모던 C++ 방식의 limits 헤더
int main() {
// INT_MAX 대신 이렇게 템플릿 형태로 타입을 전달하여 사용한다.
std::cout << "int 최댓값: " << std::numeric_limits<int>::max() << std::endl;
std::cout << "double 최댓값: " << std::numeric_limits<double>::max() << std::endl;
std::cout << "unsigned int 최댓값: " << std::numeric_limits<unsigned int>::max() << std::endl;
return 0;
}
4. 문자형(char) 배열과 널(Null) 종결자
C++의 근본적인 문자열 처리는 문자의 연속된 배열(Array)로 이루어진다. 이때 컴퓨터에게 "문자열이 여기서 끝난다"는 것을 명시적으로 알려주는 작업이 필수적이다.
#include <iostream>
using namespace std;
int main() {
// 문자를 직접 배열로 나열할 때는 끝에 반드시 '\0' (Null 문자)를 넣어주어야 한다.
char b[] = { 'a', 'b', 'c', '\0' };
// 큰따옴표("")를 사용한 문자열 리터럴
// 눈에 보이지 않지만 "abc" 끝에는 자동으로 '\0'가 포함되어 메모리에 4바이트가 할당된다.
char c[] = "abc";
return 0;
}
만약 배열 끝에 \0(널 문자)를 빼먹고 출력을 시도하면, 컴퓨터는 종료 신호를 받지 못해 메모리를 넘어서 쓰레기값(Garbage Data)이 나올 때까지 계속 읽어버린다. 실무에서는 훨씬 안전한 std::string 객체를 쓰지만, 저수준 메모리를 다룰 때는 문자열 끝에 항상 \0가 존재한다는 사실을 잊어선 안 된다.
5. bool 자료형
bool은 오직 true(1)와 false(0)만 가질 수 있는 논리 자료형이다. 그런데 이 엄격해 보이는 타입에 엉뚱한 정수를 넣으면 컴파일러가 아주 관대하게(?) 반응한다.
#include <iostream>
int main() {
bool a = 0; // 0 (false)
bool b = 1; // 1 (true)
// 논리형 변수에 10이라는 정수를 억지로 넣는다면?
bool c = 10;
std::cout << a << " " << b << " " << c << std::endl;
// 출력 결과: 0 1 1
return 0;
}
bool 형 변수에 10이나 -5 같은 엉뚱한 값을 넣어도 에러를 뱉지 않는다. 대신 "0은 거짓, 0이 아닌 모든 데이터는 무조건 참(1)"으로 강제 변환해 버린다. 조건문을 짤 때 의도치 않은 정숫값이 섞여 들어오면 무조건 true로 통과되어 버리는 치명적인 논리 버그가 발생할 수 있으니 유의해야 한다.
'C++' 카테고리의 다른 글
| 6. C++의 진입장벽 포인터(Pointer)와 동적 할당: 도대체 왜 쓰는 걸까? (0) | 2026.03.03 |
|---|---|
| 4. 복합 데이터형: 깐깐한 배열(Array) 규칙부터 구원자 'string'까지 (0) | 2026.02.27 |
| 3. 'const'와 형변환 'static_cast' (0) | 2026.02.26 |
| 1. 왜 C++인가? 기본 구조부터 변수와 메모리의 이해까지 (0) | 2026.02.24 |