null 은 대체 뭔가?
코딩공부를 시작한지 얼마 안된 사람에게 ‘null이란 무엇인가?’ 라고 질문 받는다면 개발자 거의 대부분이 ‘null 은 null 이다.’ 라고 대답할 것이다. 조금 더 구체적으로 설명하고자 하면 ‘없음’, ‘아무것도 아님’, ‘참조하고 있지 않음’ 등등을 나타내는 키워드라고 설명해줄 지도 모른다. 이런 개념들을 하나로 추상화한 개념이 null 이기 때문에 null을 모르는 초심자에게 해당 개념을 설명하는 것은 꽤나 어려운 일이다.
이번엔 그 null 에 대한 쓰잘데기 없는 이야기를 잠깐 해볼까 한다.
10억달러 짜리 실수
null 개념을 처음 도입한 Tony hoare 라는 분으로, 2009년에 null을 만든건 10억달러 짜리의 실수라고 말한 적이 있다. 이후 탄생한 수많은 프로그램들이 null 예외로 터져버려 발생한 손해가 그만큼 막심하다는 이야기다.
다만 Tony hoare 는 컴파일 타임에 null 예외를 잡아낼 수 있는 형태를 원했었는데 당시로서는 구현이 매우 힘들어 null을 도입하는 것으로 대체했다. Tony hoare 가 원했던 형태 - 컴파일 타임에 참조의 안정성을 보장하는 언어들은 2010년대 들어와서야 등장하기 시작한다.
null 의 실제 값
메모리 상에서 null 의 실제 값은 어떨까? null 이니 메모리 상에 존재하지 않는걸까? 그럴리는 없다. 왜냐면 null 은 변수에 할당 되는 것이고, 변수는 반드시 메모리상에 자신의 공간이 할당되어 있기 때문이다.
int a;
MyClass b;
변수는 선언하는 순간 메모리에 자기만의 공간이 할당된다. value 형태의 변수는 자신의 타입 사이즈만큼의 메모리 공간이 할당되며, 참조 타입의 변수는 포인터 사이즈만큼의 메모리 공간이 할당된다. 위에서 선언한 예제의 경우 a는 4바이트가 할당된다. b는 64bit 환경일 경우 8바이트의 공간이, 32bit환경일 경우엔 4바이트가 할당될 것이다.
익히 알다시피, 참조 타입의 변수는 실제로 가지고 있는 값은 인스턴스의 주소 값이다.
b = new MyClass();
위에서 new MyClass 하는 순간 인스턴스가 생성 되고, 해당 인스턴스의 주소 값이 변수 b의 메모리 공간에 기록된다. 만약 인스턴스가 10000번지에 생성이 되었다면 b의 메모리 공간에는 10000 이라는 값이 기록된다.
b = null;
그럼 b에 null 을 대입할 경우 b의 메모리 공간에는 어떤 값이 기록될까? 쉽게 생각나는 그 숫자, 바로 0 이 기록이 된다.
value 타입의 변수에 null을 쓸 수 없는건
일반적으로 강한 타입 체크를 하는 언어(java, c#등)에서는 값 타입의 변수에 null을 대입할 수 없다. 이유는 위에서 이야기한 것 처럼 null 은 실제로 메모리에는 0 으로 기록되기 때문인데, 값 타입의 변수는 자신의 메모리 공간에 코드 상에서 할당받는 값이 그대로 기록되기 때문이다.
int a = null;
이와 같은 경우 프로그래머의 의도는 null을 a에 대입하고 싶은 것이지 0을 대입하고 싶은 것이 아니다. 그러나 메모리에는 null이나 0이나 둘 다 0 으로 기록된다. 컴퓨터 입장에서는 메모리에서 값을 읽어올 때 프로그래머가 의도한 값이 0 인지 null인지 알 수 없으므로 값 타입의 경우엔 null 대입을 원천적으로 차단하는 것이다.
물론 이런 강타입 체크 언어들도 우회적으로 값타입에 null을 입력할 수 있는 방법을 제공하는 경우도 있으나, 그런 경우는 내부적으로 참조타입으로 변환한 후 처리하게 된다.
Null pointer exception
모든 개발자에게 가장 친숙한 예외인 Null pointer exception 은 OS레벨에서 발생되는 예외이다. 프로세스가 0 번지에 접근을 시도하면 OS 는 친절하게 Null pointer exception 을 발생시켜준다.
DB에서의 null
DB 역시 데이터의 타입에 따라 row에 실제로 들어가는 값이 참조냐 값이냐가 나뉜다. 그러나 db는 값 타입의 필드라도 null 허용 필드라면 null 을 입력할 수 있다. 값 필드가 null일 경우 db도 내부적으로 참조로 변환한 후 처리하는걸까? 그렇지는 않다.
각 row 마다 우리에겐 보이지 않는 헤더 데이터가 존재한다. 이 헤더에는 그 row의 메타데이터들이 들어가는데 여기에 그 row에서 특정 필드가 null 인지 여부를 나타내는 값이 들어간다.