'이것이 C#이다 개정판'의 chapter 3을 정리한 글입니다.
변수 (Variable)
C#에서는 변수의 초기화를 강제합니다.
초기화되지 않은 변수를 사용하면 컴파일러가 에러 메시지를 내면서 실행 파일을 만들어주지 않습니다.
힙 (Heap)
힙은 스택과 달리 저장된 데이터를 스스로 제거하는 메커니즘을 가지고 잊지 않습니다.
대신 CLR의 가비지 컬렉터(Garbage Collector)가 존재합니다. 더 이상 데이터를 참조하는 곳이 없을 때 가비지 컬렉터가 사용되지 않는 데이터의 메모리를 제거하는 역할을 하게 됩니다.
힙을 사용하는 이유를 간단하게 말하자면, 스택 메모리 영역은 코드 블록이 사라지는 시점에 제거되기 때문에, 이러한 한계를 없애고 싶을 때 사용합니다.
프로그래머가 데이터를 원하는 동안 살리고 싶을 때, 또 다른 메모리 영역을 CLR이 제공하는 것입니다.
데이터 형식 : 정수형
메모리의 효율적으로 사용하기 위해, 자료형의 크기와 범위를 고려해야 합니다.
| 데이터 형식 | 설명 | 크기(Byte) | 담을 수 있는 값의 범위 |
| byte | 부호 없는 정수 | 1 (8bit) | 0 ~ 255 |
| sbyte | signed byte 정수 |
1 (8bit) | -128 ~ 127 |
| short | 정수 | 2 (16bit) | -32,768 ~ 32,767 |
| ushort | unsigned short 부호 없는 정수 |
2 (16bit) | 0 ~ 65,535 |
| int | 정수 | 4 (32bit) | -2,147,483,648 ~ 2,147,483,647 |
| uint | unsigned int 부호 없는 정수 |
4 (32bit) | 0 ~ 4,294,967,295 |
| long | 정수 | 8 (64bit) | -9,223,372,036,854,775,808 ~ 9,223,372,036,854,775,807 |
| ulong | unsigned long 부호 없는 정수 |
8 (64bit) | 0 ~ 18,446,744,073,709,551,615 |
| char | 유니코드 문자 | 2 (16bit) |
[2의 보수법]
| 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
위와 같은 비트로 이루어진 sbyte 형식의 변수는 -127과 -1중 어떤 값을 가질지를 알기 위해서는 '2의 보수법'을 알아야 합니다.
정답부터 말하자면, -1이 정답입니다.
가장 앞의 비트가 부호 비트라고 생각을 하면, 정답은 -127이 되야하지 않나라고 생각할 수 있지만, 그렇지 않습니다.
Signed 변수는 MSB(Most Significant bit)라고 하는 양수, 음수를 표현하는 부호 비트 1bit를 가집니다.
하지만, -127이 아닌 이유는 sbyte는가 '2의 보수법'이라는 알고리즘을 채택하여 음수를 표현하기 때문입니다.
※ sbyte뿐 아니라, short, int, long의 경우에도 2의 보수법 알고리즘을 채택한다.
[2의 보수법을 이용해서 음수를 표현하는 방법]
-1을 예로 들면,
① 먼저 수 부분 비트를 채운다.
| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
② 전체 비트를 반전시킨다.
| 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 |
③ 반전된 비트에 1을 더한다.
| 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
이런 식으로 -1을 표현하게 됩니다.
[예제]
using System;
using System.Reflection.Metadata.Ecma335;
using static System.Console;
namespace IntegralTypes
{
class MainApp
{
static void Main(string[] args)
{
byte a = 255; // 0b_1111_1111 또한 동일
sbyte b = (sbyte)a;
WriteLine(a); // 255
WriteLine(b); // -1
}
}
}
오버플로우(Overflow)와 언더플로우(Underflow)
[오버플로우]
각 데이터 형식의 최대값을 넘어가는 데이터를 저장할 때, 오버플로우가 발생하면서 아래와 같은 현상이 발생합니다.
- byte의 MaxValue(최대값 255) + 1
| 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | + | 1 |
↓
| 1 | || | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
byte는 8개의 비트만 담을 수 있으므로 넘쳐 흐른 왼쪽의 비트는 버리고, 오른쪽 8개의 비트만 보관합니다.
그래서 최대값을 가진 byte형 변수가 오버플로우가 발생하면 0이 되는 것입니다.
[언더플로우]
각 데이터 형식의 최대값을 넘어가는 데이터를 저장할 때, 오버플로우가 발생하면서 아래와 같은 현상이 발생합니다.
- sbyte의 MinValue(최대값 -128) - 1 → (-128) + -(1)
| 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
+
| 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
↓
| 1 | || | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
언더플로우의 상황에서도 역시,
byte는 8개의 비트만 담을 수 있으므로 넘쳐 흐른 왼쪽의 비트는 버리고, 오른쪽 8개의 비트만 보관합니다.
그래서 최소값을 가진 byte형 변수가 언더플로우가 발생하면127이 되는 것입니다.
[예시]
using System;
using static System.Console;
namespace IntegralTypes
{
class MainApp
{
static void Main(string[] args)
{
uint a = uint.MaxValue;
WriteLine(a);
a = a + 1;
WriteLine(a);
int b = int.MinValue;
WriteLine(b);
b = b - 1;
WriteLine(b);
}
}
}
데이터 형식 : 실수형
| 데이터 형식 | 설명 | 크기(Byte) | 담을 수 있는 값의 범위 |
| float | 단일 정밀도 부동 소수점 형식 (6 ~ 7개의 자릿수만 표현 가능) |
4 (32bit) | -3.402823e38 ~ 3.402823e38 |
| double | 복수 정밀도 부동 소수점 형식 (15 ~ 16개의 자릿수만 표현 가능) |
8 (64bit) | -1.79769313486232e308 ~ 1.79769313486232e308 |
| decimal | 28 ~ 29자리 데이터를 표현할 수 있는 소수 형식 |
16 (128bit) | ±1.0 x 10e-28 ~ ±7.9 x 10e28 |
부동 소수점 형식에는 float와 double 두 가지가 있지만, float보다는 double을 사용하는 것을 권합니다.
double이 float에 비해 메모리를 두 배로 사용하지만, 그만큼 float에 비해 데이터의 손실이 적기 때문입니다.
(표현 가능한 자릿수를 비교하면, float은 double에 비해 실제 값에 대한 오차가 더 클 수 밖에 없습니다.)
double 형식을 사용했는데도 데이터의 손실이 우려된다면, decimal 형식을 사용할 수 있습니다.
decimal의 한계마저 넘어서는 데이터를 처리해야 한다면, 그때는 직접 그 데이터를 처리할 수 있는 알고리즘을 담은 복합 데이터 형식을 직접 작성해야 합니다.
[예시]
using System;
using static System.Console;
namespace IntegralTypes
{
class MainApp
{
static void Main(string[] args)
{
float a = 3.141_592_653_589_793_238_462_643_383_279f; // F도 사용 가능
double b = 3.141_592_653_589_793_238_462_643_383_279; // d나 D도 사용 가능
decimal c = 3.141_592_653_589_793_238_462_643_383_279m; // M도 사용 가능
WriteLine(a); // 3.1415927
WriteLine(b); // 3.141592653589793
WriteLine(c); // 3.1415926535897932384626433833
}
}
}
데이터 형식 : 논리형
true나 false를 저장할 때, 실제로 필요한 메모리 크기는 1bit이지만, 컴퓨터가 기본적으로 다루는 데이터의 크기가 바이트 단위이기 때문에 1bit만 저장하려 해도 1byte가 저장이 됩니다.
데이터 형식 : object 형식
object 형식은 어떤 데이터이든지 다룰 수 있는 데이터 형식입니다.
그럴 수 있는 모든 데이터 형식(기본 데이터 형식이나 모든 복합 데이터 형식)이 자동으로 object 형식으로부터 상속받게 한 것이다.
따라서 컴파일러는 어떤 형식의 데이터라도 object에 담아 처리할 수 있습니다.
하지만, 부호 있는 정수 형식, 부호 없는 형식, 부동 소수점 형식, decimal 형식 등 각자 데이터 형식을 처리하는 방식이 모두 다른데,
object 형식은 어떻게 모든 데이터를 다룰 수 있는지, 이에 대한 의문점이 생길 수 있습니다.
이 의문점을 해결하기 위해서, '박싱'과 '언박싱'의 개념을 알아야 합니다.
[박싱과 언박싱]
- 박싱 (Boxing)
- 값 형식의 데이터를 참조 형식으로 할당(변환)하는 것
- 스택 메모리에는 데이터의 주소를 참조하고, 실제 값은 힙 메모리에 할당됩니다.
- 박싱을 통해 값 형식을 메모리에 할당된 객체(Object 타입)로 변환하여 참조 형식처럼 다룰 수 있습니다.
- 박싱의 작동 구조
- 값 형식 데이터를 힙에 할당 : 값 형식 데이터를 힙에 새로운 객체로 할당합니다.
- 객체 생성 : 이 객체는 해당 값 형식의 데이터를 포함합니다.
- 데이터 복사 : 값 형식의 데이터를 복사하여 힙에 할당된 객체에 저장합니다.
- 참조 생성 : 값 형식을 가리키는 참조가 생성됩니다.
- 언박싱 (Unboxing)
- 박싱된 객체를 참조 형식에서 다시 값 형식의 데이터로 할당(변환)하는 것
- 힙에 저장된 객체에서 값을 꺼내와 스택에 저장하고, 이 과정에서 타입 검사와 캐스팅이 필요합니다.
- 언박싱의 작동 구조
- 참조 가져오기 : 박싱된 객체의 참조를 가져옵니다.
- 값 추출 : 힙에 저장된 객체에서 값 형식 데이터를 추출합니다.
- 데이터 복사 : 이 데이터를 스택에 있는 값 형식 변수에 복사합니다.
- 유의할 점
- 단순히 스택에서 힙으로의 값 복사가 박싱은 아니라는 것을 유의해야 합니다.
- 반대로 참조 형식을 값 형식으로 바꾸븐게 무조건 언박싱은 아니라는 것입니다.
- 언박싱은 박싱된 데이터를 다시 값 형식으로 돌려놓는 것입니다.
즉, 박싱이 있어야 언박싱이 있을 수 있다.
- 언박싱은 박싱된 데이터를 다시 값 형식으로 돌려놓는 것입니다.
- 장점
- 유연성 : 값 형식으로 변환하여 다양한 컬렉션이나 메서드에서 사용할 수 있습니다.
- 단점
- 성능 저하 : 박싱과 언박싱은 힙 메모리 할당과 가비지 컬렉션을 유발하므로 성능 저하가 발생할 수 있습니다.
- 런타임 오류 가능성 : 언박싱을 할 때, 타입 불일치로 인한 예외가 발생할 수 있습니다.
[예제]
using System;
using static System.Console;
namespace IntegralTypes
{
class MainApp
{
static void Main(string[] args)
{
int a = 123;
object b = (object)a; // a에 담긴 값을 박싱해서 힙에 저장
int c = (int)b; // b에 담긴 값을 언박싱해서 스택에 저장
WriteLine(a); // 123
WriteLine(b); // 123
WriteLine(c); // 123
double x = 3.1414213;
object y = x; // x에 담긴 값을 박싱해서 힙에 저장
double z = (double)y; // y에 담긴 값을 언박싱해서 스택에 저장
WriteLine(x); // 3.1414213
WriteLine(y); // 3.1414213
WriteLine(z); // 3.1414213
}
}
}
'C#' 카테고리의 다른 글
| [C#] 메소드 (0) | 2025.04.11 |
|---|---|
| [C#] Switch, 반복문, 점프문 (0) | 2025.04.10 |
| [C#] 연산자 (2) | 2025.04.07 |
| [C# 기본 정리 3] 형 변환, Nullable, var, 전역변수, 문자열 (0) | 2025.04.04 |
| [C# 기본 정리 1] 컴파일러와 인터프리터, C#의 컴파일 (0) | 2025.04.01 |