최신 GCC 소스의 libgcobol/posix를 보면 POSIX 함수용 COBOL 바인딩의 초기 구현을 찾을 수 있음. 이 작업은 내가 먼저 시작한 셈임. errno 값과 선택적으로 오류 문자열까지 반환하는 posix-errno 함수를 만들었음. errno에 값을 쓰는 방법은 아직 논의 중이라 지원하지 않음. 드물게 필요하지만, 필요할 때는 피할 수 없는 기능임.
모든 정수 값과 구조체에는 연결용 코드(shim) 가 필요함. SEEK_SET이나 stat(2)용 구조체는 C 헤더에 정의되며, 표준 바이너리 표현이 정해져 있지 않음. C 컴파일러마다 실제로 서로 다른 정의를 사용할 수 있으므로, POSIX API를 쓰려는 다른 언어는 각 매크로와 구조체를 자기 언어로 정의하고 해당 환경의 C 컴파일러가 정의한 값에 대응시키는 별도 인터페이스를 만들어야 함.
잘 몰라서 묻는데, 왜 C 컴파일러가 POSIX 요구사항에 종속되는 건가요? 구조체, 공용체, ABI 등을 C 언어가 정의하고, POSIX는 C로 작성된 ‘응용 프로그램’이니 전적으로 그 아래에 놓이는 관계라고 생각했는데, 내가 잘못 이해한 건가요?
이런 걸 볼 때마다, 제대로 된 해법보다 ‘효율적’이거나 쉽다는 이유로 누군가 비슷한 편법을 썼다가 이상한 동작이나 빌드 문제를 디버깅해야 했던 일이 떠오름. 실제 코드에서는 이런 편법을 쓰지 말아 주길 바람. 지금 작동한다는 건 흥미롭지만, 내일도 작동하리라는 보장은 없음.
대체로 동의함. 글에서 ‘편법’이나 ‘엉망인 해킹’이라고 부르긴 했지만, 실제 코드에 쓰라고 권하는 게 아니라는 점을 충분히 분명히 하지 못한 것 같음. 그저 재미로 해봤고, C가 막으려는 듯한 일을 어떻게든 해낼 수 있다는 게 멋지다고 느꼈음. 어쩐지 이런 괴상한 것들에 끌리는 편임.
모든 POSIX 준수 시스템에서 작동한다는 의미의 이식성은 없을 것 같음. glibc의 __xpg_strerror_r에서 드러나듯, POSIX는 libc가 외부로 노출하는 심벌의 이름을 특정하지 않는 듯함.
그렇다면 libc와 상호작용하는 진정으로 이식 가능한 방법은 C 프로그램을 컴파일하는 것뿐인가요?
‘이식 가능’을 극도로 엄격하게 정의하면 맞음. POSIX는 함수의 C 인터페이스만 규정하고, 내가 알기로 심벌은 아예 다루지 않는 구현 세부사항에 가까움. 다만 실제로 glibc 말고 심벌 이름을 이상하게 바꾸거나 같은 이름으로 비표준 함수를 따로 제공하는 libc는 알지 못함.
완전히 기상천외한 방식이라는 점을 빼면, 실행 중 오류 문자열이 바뀌는 경우가 걸림. perror()는 현재 로캘의 LC_MESSAGES에서 메시지를 가져오므로, 문자열 표를 만든 뒤 perror()를 호출하기 전에 setlocale()를 호출하면 깨질 것 같음.
호출 직전에 로캘을 C로 되돌리면 될지도 모름. POSIX에 따르면 스레드별 설정 함수인 uselocale()는 실패하면서 errno를 설정할 수 있지만, 전역 설정 함수인 setlocale()는 실패 시 NULL을 반환하고 errno를 설정하지 않음. 그렇다면 성공 후에는 errno 값이 정의되지 않는다는 일반 규칙이 적용되는 걸까요?
glibc 메시지 카탈로그는 모두 설치된 듯한데 오류 문자열은 어느 것도 번역되지 않는 것 같음. 번역 방법을 다시 배워볼 만큼 궁금하지는 않아서 직접 확인하지는 못했음.
setlocale()가 errno를 설정하지 않는다고 가정하면 안 됨. C89부터 C 표준은 함수 설명에 errno 사용이 명시되어 있지 않다면, 오류 발생 여부와 관계없이 라이브러리 함수 호출이 errno를 0이 아닌 값으로 설정할 수 있다고 규정함. setlocale()에는 errno 설정에 관한 명시가 없으니 주의해야 함.
이 코드가 Hare인가요? 확인해 보니 맞음. 운영진에게 묻고 싶은데, Hare 태그는 어떻게 만들 수 있나요?
Hare 유지보수자 중 한 명이지만, 이 글에 Hare 태그가 적절한지는 잘 모르겠음. 예제는 Hare로 작성했지만 Zig 같은 비슷한 언어로 바꿔도 글의 핵심은 거의 같음.
Zig에는 초기화 함수가 없어 사용자가 명시적으로 호출해야 하고, 오류 타입의 기반 표현으로 문자열이나 정수를 쓸 수 없다는 정도의 차이는 있음. 흥미로운 부분은 거의 동일하고, 나는 그냥 Hare로 작성하는 게 즐거울 뿐임.
그냥 errno를 읽는 작은 래퍼 함수를 만들면 안 되나요? errno를 건드리는 라이브러리를 호출할 때만 값이 지워지는 것 아닌가요? get_errno()가 그 값을 훼손하지 않도록 보장할 수 있을 것 같음.
C로 작성한 errno 래퍼를 링크하는 게 올바른 방법이지만, 그건 별로 재미있지 않음. 순전히 재미로 C 코드를 전혀 작성하지 않고도 가능한지 알아보고 싶었음. 글에서 여러 번 ‘엉망인 편법’이라고 부르면 충분할 줄 알았는데, 실제 코드에 쓰길 권하지 않는다는 점을 더 분명히 밝혔어야 했던 것 같음.
C 표준 라이브러리 함수는 errno를 0으로 설정하지 않음. C89부터 표준은 프로그램 시작 시 값은 0이지만, 어떤 라이브러리 함수도 이를 0으로 설정하지 않는다고 규정함.
또 표준은 errno가 매크로인지 외부에서 접근 가능한 변수인지도 정하지 않음. 따라서 포인터, 정확히는 수정 가능한 좌측값을 얻는 함수를 호출하도록 errno를 매크로로 구현할 수도 있음.