오프셋
고친 사람 github-actions[bot]
오프셋은 기준점에서 얼마나 떨어졌는지로 위치를 가리킵니다. 기준점은 미리 하나를 정해 둡니다. 데이터에서는 덩이의 맨 앞에서 몇 바이트 떨어졌는지를 뜻합니다. 시각에서는 기준 시각보다 얼마나 앞서거나 뒤처지는지를 뜻합니다.
쉽고 빠른 이해
오프셋은 기준점에서 떨어진 거리로 위치를 가리킵니다. 값 여럿을 이어 붙인 덩이에서 「맨 앞에서 여섯 바이트 뒤가 점수」라고 적어 두는 것이 오프셋입니다.
데이터가 메모리 어디에 실릴지는 프로그램을 돌려 봐야 정해집니다. 절대 위치를 미리 적어 둘 수 없다는 뜻입니다. 대신 덩이 안쪽 순서는 미리 정해집니다. 그래서 기준점에서 떨어진 거리만 적어 두면 됩니다.
도는 모양은 셋입니다.
- 무엇을 기준점으로 삼을지 정합니다
- 찾을 것이 기준점에서 얼마나 떨어졌는지를 적어 둡니다
- 찾을 때 기준점의 실제 위치에 그 거리를 더합니다
대가가 있습니다. 기준점을 모르면 오프셋만으로는 아무 데도 못 갑니다. 앞쪽에 무엇을 끼워 넣으면 뒤에 있던 오프셋이 전부 밀립니다.
상세
약속 장소를 정할 때 「정문에서 오른쪽으로 오십 미터」라고 말합니다. 정문이 기준점입니다. 오십 미터가 떨어진 거리입니다. 듣는 사람은 정문만 찾으면 나머지는 걸어서 셉니다.
오프셋은 기준점에서 떨어진 양입니다. 무엇을 단위로 세는지는 쓰는 곳이 정합니다. 파일에서 「천 번째 바이트부터 읽어라」라고 할 때의 천이 오프셋입니다. 이때 세는 단위는 바이트입니다.
이렇게 적으면 절대 위치를 몰라도 안쪽 구조를 미리 적어 둘 수 있습니다. 데이터가 메모리 어디에 실릴지는 프로그램을 돌려 봐야 정해집니다. 그 안에서 무엇이 몇 바이트째인지는 미리 정해집니다.
그래서 안쪽만 오프셋으로 적어 둡니다. 바깥은 나중에 맞춥니다.
찾을 때는 덧셈 하나면 됩니다. 기준점의 실제 위치에 오프셋을 더하면 찾는 곳이 나옵니다. 이름을 뒤져 찾는 방식과 달리 하나씩 살펴볼 것이 없습니다.
block-beta columns 3 t1["돌려 봐야 정해집니다"]:3 a["실제 위치 1000"] b["실제 위치 1004"] c["실제 위치 1008"] d["오프셋 0"] e["오프셋 4"] f["오프셋 8"] t2["미리 정해 둡니다"]:3
기준점이 1000번지에 실리면 오프셋 4는 1004번지입니다. 기준점이 다른 번지에 실려도 아래 줄은 그대로입니다.
오프셋은 대개 0부터 셉니다. 오프셋 0은 기준점 자신을 가리킵니다. 첫 바이트가 1이 아니라 0입니다. 그래서 몇 번째인지를 세는 순서 번호와 헷갈리기 쉽습니다.
고정 배치에서 값을 골라 읽는 쓰임
레코드는 정해진 값 여럿을 이어 붙여 놓은 한 덩이입니다. 값 하나하나를 필드라고 부릅니다. 덩이의 맨 앞을 기준점으로 삼습니다.
어떤 필드가 몇 개 들어가는지는 미리 고정됩니다. 그래서 각 필드가 기준점에서 몇 바이트 떨어졌는지도 미리 정해집니다.
오프셋의 간격은 일정하지 않습니다. 필드마다 크기가 다르기 때문입니다. 앞 필드들이 차지한 바이트를 모두 더한 값이 다음 필드의 오프셋이 됩니다.
block-beta columns 7 a["아이디 · 4바이트"]:2 b["나이 · 2바이트"]:1 c["점수 · 8바이트"]:4 d["오프셋 0"]:2 e["오프셋 4"]:1 f["오프셋 6"]:4
점수의 오프셋 6은 앞의 두 필드가 차지한 바이트를 더한 값입니다. 간격이 4에서 2로 줄어든 것은 나이가 아이디보다 작기 때문입니다.
헤더도 같은 방식으로 읽습니다. 헤더는 본문 앞에 붙어 그 본문을 어떻게 읽을지 적어 두는 덩이입니다. 몇 번째 비트부터 몇 비트가 무슨 뜻인지를 미리 정해 두면, 읽는 쪽은 오프셋으로 곧장 그 비트를 집습니다.
다음에 읽을 위치를 기억하는 쓰임
앞 소절의 오프셋은 한 번 정해지면 바뀌지 않았습니다. 여기 오프셋은 읽을 때마다 바뀝니다.
파일을 열면 운영체제가 그 파일의 몇 번째 바이트까지 왔는지를 기억합니다. 이 값도 오프셋이라고 부릅니다. 읽기를 한 번 할 때마다 읽은 바이트 수만큼 앞으로 옮겨 갑니다. 그래서 읽는 쪽은 어디까지 읽었는지를 스스로 세지 않아도 됩니다.
앞뒤로 건너뛰고 싶으면 이 오프셋을 직접 옮깁니다. 파일 가운데부터 읽는 일이 그렇습니다.
같은 파일을 따로 두 번 열면 오프셋도 둘로 따로 생깁니다. 한쪽이 읽어 나가도 다른 쪽이 읽던 위치는 남습니다.
연 파일 하나하나에는 번호가 붙습니다. 이 번호를 파일 디스크립터라고 합니다. 어느 쪽 오프셋을 쓸지는 이 번호가 정합니다.
flowchart TD
A["파일 디스크립터 A"] --> B["오프셋 100"]
C["파일 디스크립터 B"] --> D["오프셋 0"]
B --> E["같은 파일 하나"]
D --> E
두 번호가 같은 파일을 가리켜도 오프셋은 따로 움직입니다.
로그처럼 뒤에만 덧붙이는 저장 구조도 같은 말을 씁니다. 쌓인 메시지마다 오프셋을 붙여 두면 읽는 쪽은 어디까지 읽었는지를 숫자 하나로 기억합니다. 끊겼다 다시 붙어도 그 숫자부터 이어 읽습니다.
block-beta columns 5 t["뒤에만 덧붙는 메시지"]:5 m0["오프셋 0"] m1["오프셋 1"] m2["오프셋 2"] m3["오프셋 3"] m4["오프셋 4"] space:3 r3["읽은 데까지"] space
읽는 쪽이 들고 있는 것은 이 표시 하나뿐입니다.
시간축에서 쓰는 오프셋
재는 단위가 바이트에서 시간으로 바뀝니다.
나라마다 쓰는 시각은 세계 공통 기준 시각(UTC)에서 몇 시간씩 떨어져 있습니다. 한국은 아홉 시간 앞섭니다. 이 차이를 오프셋이라고 부릅니다. 시각을 적을 때 이 값을 같이 적어 두면, 읽는 쪽이 어느 기준에서 읽은 시각인지 압니다.
시계를 맞출 때도 같은 말을 씁니다. 내 시계가 기준 시계보다 얼마나 앞서거나 뒤처졌는지가 오프셋입니다. 시각 맞추기는 이 값을 재서 그만큼 시계를 당기거나 늦추는 일입니다.
기준점에 얹혀 있어서 치르는 대가
오프셋은 혼자서는 아무것도 가리키지 못합니다. 기준점을 모르면 떨어진 거리만 남습니다. 그래서 오프셋을 주고받을 때는 무엇을 기준으로 잰 값인지도 같이 넘겨야 합니다.
앞쪽을 건드리면 뒤가 전부 밀립니다. 덩이 가운데에 값을 하나 끼워 넣으면 그 뒤 필드의 오프셋이 모두 달라집니다. 이미 그 오프셋을 적어 둔 쪽은 엉뚱한 바이트를 읽게 됩니다.
block-beta columns 9 t1["끼워 넣기 전"]:9 a1["아이디 · 오프셋 0"]:2 b1["나이 · 오프셋 4"]:1 c1["점수 · 오프셋 6"]:4 space:2 t2["등급을 끼워 넣은 뒤"]:9 a2["아이디 · 오프셋 0"]:2 x2["등급 · 오프셋 4"]:2 b2["나이 · 오프셋 8"]:1 c2["점수 · 오프셋 10"]:4
등급이 들어가자 뒤 두 필드의 오프셋이 함께 밀렸습니다.
필드 목록이 실행 중에 바뀌어야 하면 오프셋을 미리 정할 수 없습니다. 그럴 때는 이름으로 찾는 해시테이블 같은 구조를 씁니다. 찾는 비용을 더 치르는 대신 구성이 바뀌어도 견딥니다.
관련 항목
오프셋으로 값을 찾는 데이터 구조
오프셋 계산에 함께 쓰이는 값
기준 주소 · 패딩 · 바이트 정렬 · 스트라이드 · 워드 · 바이트
오프셋을 들고 다니는 파일 입출력 구성 요소
파일 디스크립터 · 열린 파일 표 · 커서 · lseek · 순차 접근 · 임의 접근
오프셋으로 메시지를 짚는 로그 구조
로그 · 로그 세그먼트 · 오프셋 커밋 · 순서 번호 · 체크포인트
시간축의 오프셋을 다루는 개념
UTC 오프셋 · 타임존 · 시계 보정 · NTP · 지터
오프셋 대신 위치를 가리키는 다른 수단
포인터 · 메모리 주소 · 인덱스 · 해시테이블 · 심볼
주소를 오프셋으로 쪼개는 메모리 구조
다른 이름: offset · 옵셋 · 변위