string이 지원하는 기능들중 string.empty, null, "" 와의 차이가 궁금해져 몇가지 확인을 해보았습니다.
string을 초기화 시키는 방법에는 대표적으로 3가지가 있습니다.
string a = string.Empty;
string b = "";
string c = null;
null을 지원한다는것을 통해 알수 있듯이
string은 참조타입의 클래스를 사용한 참조자료형 입니다.
즉 a,b는 각각의 메모리를 할당해주고 힙에 비어있는 문자 값을 넣어 놓고
해당 주소를 가르키게 해 놓은것이고
c는 껍데기만 만들어 준 것입니다(new가 없으니 힙 할당을 안하고 깡통주소를 가르키게 됩니다.)
첫번째 확인작업으로 a,b,c의 길이를 출력봤습니다.
Console.WriteLine(a.Length);
Console.WriteLine(b.Length);
Console.WriteLine(c.Length);
예상대로 a와 b는 0을 출력해주고
c는 NullRefException을 던져줍니다.
두번째 확인작업으로 a,b,c를 출력해봤습니다.
Console.WriteLine(a);
Console.WriteLine(b);
Console.WriteLine(c);
이번에도 c는 널참조예외를 던져줄것 같았지만
세가지 케이스 모두 정상적으로 아무것도 없는 값들이 출력이 됐습니다.
찾아보니 Console.WriteLine에는 Null상태인 string클래스의 객체를 따로 핸들링해서
아무것도 없는 값을 출력시켜주는것 같습니다.(깡통주소핸들링)
사실 Student 클래스를 선언해주고
Student stu = null;
을 해준후 stu를 출력해보면 마찬가지로 아무것도 없는 값이 출력됩니다.
클래스 객체를 생성해주고 null을 넣어주면 메모리 할당을 하지 않고 0(깡통주소)을 가르키게 되는데 해당 상태를 가진 객체들은 모두 Console.WriteLine에서 같은 반응을 보입니다.
세번째 확인작업은 string.Empty와 ""의 추가적인 비교입니다.
Console.WriteLine(string.Empty == "");
Console.WriteLine(object.ReferenceEquals(a, b));
결과는 True, Ture
""와 string.Empty는 같은기능을 한다는것을 확인하였습니다.(""를 사용하는게 성능측면에서는 좋다는말이 있기는 합니다..)
두번째 줄이 특이합니다.
string은 클래스이기 때문에 string a = "abc"; 식으로 생성 하게 된다면
내부적으로는 new처럼 메모리를 할당하고 a의 스택값에서 해당 주소를 가르키게 된다고 생각하였으나
특이하게도 반만 맞는 얘기입니다.
처음 a를 생성해서 abc를 넣어준다면 위 과정이 일어나지만
그 이후 string b = "abc"; 를 하게 된다면
b를 만들면서 새로 힙영역을 할당하여 abc를 넣어주는것이 아니라..
내부적으로 abc라는 문자열이 이미 힙영역에 존재하는 것을 확인하고
그 문자열을 가지고 있는 주소값을 b가 가르키게 해줍니다.
(정확히는 스트링풀의 해당문자열이 있는경우 힙영역을 새로 할당하지 않습니다)
unsafe
{
TypedReference tr = __makeref(a);
IntPtr ptr = **(IntPtr**)(&tr);
Console.WriteLine(ptr);
tr = __makeref(b);
ptr = **(IntPtr**)(&tr);
Console.WriteLine(ptr);
}
해당 참조변수가 가르키는 힙주소를 직접 출력해보면 둘다 같은값이 나옵니다.
string객체를 가지고 몇가지 테스트를 더 해봤습니다.
string a = "abc";
string b = "abc";
위에서 확인했듯이 a와 b의 주소값은 같은 상황입니다.
b += "d";
b에 "d"를 추가해 b를 "abcd"로 바꾸어 보았습니다.
단순하게 생각하면 주소값이 같으므로 b를 "abcd"로 바꾸어주면 a도 "abcd"로 바뀌어야 합니다.(물론 경험을 통해 아니라는것은 알고있습니다)
결과는 당연히 a는 "abc" 그대로 유지 b는 "abcd"로 변경입니다.
unsafe
{
TypedReference tr = __makeref(a);
IntPtr ptr = **(IntPtr**)(&tr);
Console.WriteLine(ptr);
tr = __makeref(b);
ptr = **(IntPtr**)(&tr);
Console.WriteLine(ptr);
}
주소값을 출력해보면 b의 주소가 바뀌어있습니다.
즉 string 객체에 같은 문자열을 넣어주면 힙영역에 새로 할당하지 않고 주소를 공유하게 됩니다.
하지만 문자열의 값이 변경되게 된다면 그때 힙을 새로 할당하게됩니다.
또한 한번 힙이 할당 된 이후에는 다시 값이 같아져도 같은주소를 가르키게 되는 일은 없어집니다.
즉 string의 값을 변경시켜주면 주소값을 따라가서 힙영역의 데이터를 바꿔주는것이 아닌
매번 재할당을 하고 가비지컬렉터가 수거해가는 작업이 생깁니다.
(이때문에 자주 값이 변경되야하는 문자열은 string보다는 한번에 넉넉하게 힙을 할당해주는 stringbuilder를 사용하는것이 좋습니다.)
마지막으로 string의 new키워드로 메모리를 할당해보았습니다.
string a = "abc";
char[] temp = { 'a', 'b', 'c' };
string b = new string(temp);
a와 b 모두 "abc"의 값을 갖고있지만
주소값을 출력해보니 서로 다르게 출력됩니다.
'c#' 카테고리의 다른 글
[c#] lock 사용법 및 예제 (0) | 2020.08.03 |
---|---|
[c#] 스레드(Thread)사용법 및 예제 (4) | 2020.07.30 |
[c#] Try Catch 사용법 및 예제 (0) | 2020.07.28 |
[c#] static과 singleton사용예제 (0) | 2020.07.07 |
c# delegate, event 사용법 및 사용예제 (3) | 2020.05.25 |
댓글