EOVERFLOW
고친 사람 github-actions[bot]
EOVERFLOW 는 운영체제가 값을 건네주지 못했을 때 알려 주는 오류 값입니다. 값은 멀쩡합니다. 그 값을 받을 프로그램 쪽 변수가 너무 작습니다. 운영체제는 숫자를 잘라 틀린 값을 주는 대신 호출을 실패로 끝냅니다. 아주 큰 파일의 크기나 2038년 이후의 시각을 읽을 때 주로 만납니다.
쉽고 빠른 이해
EOVERFLOW 는 「값이 너무 커서 받으실 변수에 안 들어갑니다」라는 답입니다. 오래된 32비트 프로그램이 3기가바이트짜리 파일의 크기를 물으면 이 답이 옵니다. 32비트 변수로 받는 파일 크기는 약 2기가바이트가 끝이기 때문입니다.
이 오류 값이 없으면 운영체제는 넘치는 숫자를 잘라서 건네야 합니다. 그러면 프로그램은 파일 크기를 엉뚱한 값으로 받고도 모릅니다. 실패로 알려 주면 프로그램이 틀린 값을 쓰기 전에 멈춥니다.
어떻게 도는가:
- 프로그램이 파일 크기나 현재 시각을 묻습니다
- 운영체제가 그 값이 프로그램 쪽 변수에 들어가는지 봅니다
- 안 들어가면 값을 건네지 않고 실패(-1)를 돌려줍니다
- 실패한 까닭으로 이 오류 값을 남깁니다
대가가 있습니다. 다시 시도해도 풀리지 않습니다. 값이 저절로 줄어들 일이 없어서입니다. 받는 변수의 타입을 넓혀 다시 빌드해야 풀립니다.
상세
이 절은 EOVERFLOW 가 어떤 값이고 왜 생기는지를 봅니다. 큰 파일의 크기를 읽는 장면과 2038년 이후의 시각을 읽는 장면, 두 가지로 보입니다.
errno 와 오류 값
커널은 운영체제의 핵심입니다. 파일과 메모리 같은 자원을 쥐고 관리합니다. 프로그램은 이 자원이 필요하면 커널에 부탁해야 합니다.
시스템 콜은 그 부탁을 보내는 호출입니다. 파일 정보를 묻는 stat() 과 현재 시각을 묻는 time() 이 그런 호출입니다.
이런 호출은 실패하면 대개 -1 을 돌려줍니다. -1 만으로는 무엇이 잘못됐는지 알 수 없습니다. 그래서 errno 라는 정수 변수에 실패한 까닭을 따로 담아 둡니다.
errno 에 담기는 이 값이 오류 값입니다. 오류 값마다 이름이 붙어 있습니다. EOVERFLOW 는 그 이름 가운데 하나입니다. 뜻은 「Value too large to be stored in data type」, 곧 값이 너무 커서 받는 변수에 담을 수 없다는 것입니다.
오류 이름은 POSIX(Portable Operating System Interface)가 정합니다. POSIX 는 유닉스 계열 운영체제가 함께 따르는 표준입니다. 그래서 EOVERFLOW 는 유닉스 계열 어디서나 같은 뜻입니다.
다만 이름에 붙는 숫자 값은 운영체제마다 다릅니다. 리눅스 안에서도 아키텍처마다 다를 수 있습니다. 아키텍처는 중앙 처리 장치(CPU, Central Processing Unit)의 종류입니다. 그래서 코드는 숫자 대신 EOVERFLOW 라는 이름으로 비교합니다.
정수 오버플로
프로그램은 값을 변수로 받습니다. 그 변수가 얼마나 큰 값을 담을 수 있는지는 변수의 데이터 타입이 정합니다. 정수 타입은 정해진 비트 수만큼만 담습니다. 부호 있는 32비트 정수가 담을 수 있는 가장 큰 수는 2,147,483,647 입니다.
그보다 큰 수를 억지로 담으면 넘친 부분이 잘려 나가 엉뚱한 수가 됩니다. 이것을 정수 오버플로라고 부릅니다. EOVERFLOW 라는 이름의 OVERFLOW 가 이 넘침입니다.
받는 변수의 타입
EOVERFLOW 가 알리는 넘침은 계산하다 생긴 넘침이 아닙니다. 운영체제는 값을 넉넉한 크기로 들고 있습니다. 모자란 쪽은 그 값을 받는 변수의 타입입니다.
프로그램은 결과를 담을 변수를 미리 마련해 호출에 넘깁니다. 그 변수의 타입이 작으면 운영체제는 값을 옮겨 담을 수 없습니다. 이때 값을 잘라 넣지 않습니다. 호출을 -1 로 끝내고 errno 에 EOVERFLOW 를 담습니다.
잘라 넣지 않는 데는 까닭이 있습니다. 3기가바이트 파일의 크기를 32비트 변수에 잘라 넣으면 엉뚱한 값이 됩니다. 프로그램은 그 값을 믿고 파일을 덜 읽거나 계산을 틀립니다. 실패로 알리면 틀린 값이 퍼지기 전에 멈춥니다.
넘치기 쉬운 세 값
이 소절은 EOVERFLOW 를 낳기 쉬운 값 셋을 봅니다. 백엔드 개발자가 만나기 쉬운 것은 파일 정보와 시각입니다.
값마다 담는 타입이 따로 있습니다. 타입 이름 끝의 _t 는 type 의 머리글자입니다. 운영체제가 용도별로 이름을 붙여 둔 정수 타입이라는 표시입니다.
표에 나올 inode 번호는 파일 시스템이 파일마다 붙이는 번호입니다. 파일 이름이 바뀌어도 이 번호로 같은 파일을 가립니다.
| 값 | 담는 타입 | 이 값을 묻는 호출 |
|---|---|---|
| 파일 크기 | off_t | stat() |
| inode 번호 | ino_t |
stat() |
| 1970년 1월 1일 이후 흐른 초 | time_t | time() |
파일 크기와 시각은 아래 두 장면에서 봅니다. inode 번호도 같은 식으로 넘칩니다. 파일 시스템이 32비트로 못 담을 만큼 큰 번호를 붙인 파일이 있다고 합시다. ino_t 가 32비트인 프로그램이 그 파일에 stat() 을 부르면 EOVERFLOW 로 실패합니다.
32비트로 빌드한 오래된 프로그램에서는 이 타입들이 32비트일 수 있습니다. 64비트로 빌드한 프로그램에서는 대개 64비트라서 넘칠 일이 거의 없습니다.
다만 프로그램의 비트 수와 타입의 폭은 따로 정해집니다. 32비트로 빌드한 프로그램이어도 이 타입들만 64비트로 넓힐 수 있습니다. 아래 두 장면의 처방이 이것입니다.
32비트 프로그램과 큰 파일
첫 장면은 큰 파일입니다. 32비트로 빌드한 프로그램은 파일 크기를 32비트 off_t 로 받을 수 있습니다. 부호가 있으니 담을 수 있는 가장 큰 크기는 2,147,483,647 바이트, 약 2기가바이트입니다.
이 프로그램이 그보다 큰 파일에 stat() 을 부르면 EOVERFLOW 로 실패합니다. 아래는 3기가바이트 파일 big.tar 의 정보를 물은 경우입니다.
struct stat st;
int r = stat("big.tar", &st); // -1
int e = errno; // EOVERFLOW
r 은 실패를 뜻하는 -1 입니다. 그 까닭이 e 에 담긴 EOVERFLOW 입니다. 파일이 없어서 실패한 것이 아닙니다.
고치는 길은 off_t 를 64비트로 넓혀 다시 빌드하는 것입니다. 리눅스에서 32비트로 빌드할 때는 컴파일러에 -D_FILE_OFFSET_BITS=64 를 넘겨 이렇게 합니다. -D 는 컴파일러에 설정값 하나를 넘기는 옵션입니다. 프로그램은 여전히 32비트입니다. off_t 만 64비트로 넓어집니다.
2038년 이후의 시각
둘째 장면은 시각입니다. 시각은 협정 세계시를 기준으로 셉니다. 협정 세계시는 세계가 함께 쓰는 기준 시각입니다. 나라마다 시간대가 달라도 이 기준 하나로 맞춥니다.
유닉스 계열 운영체제는 1970년 1월 1일 0시 협정 세계시부터 흐른 초의 수로 시각을 셉니다. 이 수를 유닉스 시간이라고 부릅니다. 시각을 숫자 하나로 들고 있으면 비교와 계산이 쉽습니다.
time_t 가 부호 있는 32비트면 2,147,483,647 초까지만 셉니다. 그 끝이 2038년 1월 19일 03:14:07 협정 세계시입니다. 이 끝에 부딪히는 일을 2038 문제라고 부릅니다.
32비트 time_t 로 빌드한 프로그램을 64비트 커널에서 돌린다고 해 봅시다. 64비트 커널은 2038년을 넘긴 시각도 바르게 셉니다. 그래서 그 시각을 32비트 time_t 에 건넬 수 없다는 것을 알아챕니다. 시각이 2038년 1월 19일 03:14:08 협정 세계시를 지나면 time() 이 EOVERFLOW 로 실패할 수 있습니다.
이 조합이 아니면 이야기가 다릅니다. 시각이 time_t 범위를 벗어날 때 어떻게 될지 정해져 있지 않습니다. 언제나 EOVERFLOW 로 알려 준다고 기대할 수는 없습니다.
시각도 처방이 같습니다. time_t 를 64비트로 넓혀 다시 빌드합니다.
다시 해도 안 풀리는 실패
EOVERFLOW 는 기다린다고 풀리지 않습니다. 잠시 뒤 다시 부르라는 EAGAIN(Resource temporarily unavailable, 자원을 잠시 쓸 수 없음)과 다릅니다. 파일 크기도 시각도 저절로 줄지 않기 때문입니다.
그래서 이 값을 보면 재시도 대신 프로그램의 빌드를 봅니다. 32비트로 빌드됐는지, 크기와 시각을 담는 타입이 몇 비트인지를 확인합니다.
「너무 크다」를 말하는 이웃 오류 값
errno 에는 「너무 크다」를 말하는 값이 몇 더 있습니다. 무엇이 너무 큰지로 가릅니다.
| 값 | 뜻 | 무엇이 너무 큰가 |
|---|---|---|
| EOVERFLOW | Value too large to be stored in data type | 운영체제가 건넬 값이 받는 변수의 타입보다 큼 |
| EFBIG | File too large | 파일이 허용 크기를 넘어 자라려 함 |
| ERANGE | Result too large | 함수가 계산하거나 변환한 결과가 돌려줄 타입의 범위를 넘음 |
| E2BIG | Argument list too long | 프로그램을 띄울 때 넘긴 인자 목록이 김 |
가장 헷갈리는 짝은 EFBIG 입니다. EFBIG 는 파일을 더 키우려다 막힌 것입니다. EOVERFLOW 에서는 파일이 이미 그 크기로 있습니다. 그 크기를 읽어 가는 쪽이 문제입니다.
ERANGE 도 EOVERFLOW 와 비슷해 보입니다. 갈리는 곳은 넘친 값이 어디서 왔느냐입니다. ERANGE 는 수학 함수나 문자열을 숫자로 바꾸는 strtol() 이 결과를 만들다 넘친 것입니다. EOVERFLOW 는 운영체제가 이미 들고 있는 값을 건네다 넘친 것입니다.
관련 항목
EOVERFLOW 가 속하는 오류 체계
errno · errno.h · POSIX · 시스템 콜 · 커널
EOVERFLOW 를 낳는 넘침과 타입
정수 오버플로 · 데이터 타입 · off_t · ino_t · time_t · blkcnt_t · time64_t
EOVERFLOW 를 돌려주는 호출
stat · fstat · lseek · open · time(2) · clock_gettime
EOVERFLOW 를 피하려고 타입을 넓히는 수단
_FILE_OFFSET_BITS · _TIME_BITS · 대용량 파일 지원 · 64비트 시간 · ABI
EOVERFLOW 가 드러나는 시각과 파일의 상한
2038 문제 · 유닉스 시간 · 에포크 · inode · 파일 시스템
EOVERFLOW 와 헷갈리는 이웃 오류 값
EFBIG · ERANGE · E2BIG · EAGAIN · EINVAL · ENOSPC
다른 이름: Value too large to be stored in data type