your c is not standard c
여러분의 C는 표준 C가 아닙니다
GCC, Clang, glibc, musl이 C 및 POSIX 표준과 다르게 동작하는 사례를 정리합니다. 컴파일러 확장과 미지원 pragma, 라이브러리의 오류 처리 및 헤더 정의 차이가 표준에 맞춘 코드의 이식성을 떨어뜨릴 수 있다고 설명합니다.
- 주제
에디터 노트
표준 pragma의 미지원과 함수별 제어의 차이는 이 글에서 가장 구체적인 이식성 문제입니다. GCC에서는 pragma가 무시되고 명령행 옵션은 번역 단위 전체에 적용되므로, 같은 소스라도 컴파일러별 설정에 따라 부동소수점 연산 조건이 달라집니다. `initstate`와 `va_start` 사례도 표준 문서만 보고 구현 동작을 예측하기 어려운 지점을 보여줍니다. 댓글의 임베디드 경험은 `CX_LIMITED_RANGE`와 최적화가 얽힐 때 간헐적인 증상이 생길 수 있다는 사례입니다. 다만 특정 프로젝트 경험이므로 해당 pragma가 원인이었다는 일반적 증거로 확대할 수는 없습니다. 글은 GCC와 Clang, 두 C 라이브러리의 차이를 모은 목록이며 각 항목의 적용 버전이나 표준 모드별 조건은 제시하지 않습니다.
AI 요약
GCC와 Clang, glibc와 musl을 사용하는 환경에서 표준 C와 실제 구현 사이에 생기는 차이를 정리합니다. 글쓴이는 목록이 불완전하다고 밝히며, 컴파일러 옵션뿐 아니라 부동소수점 pragma, POSIX 함수 동작, 선언 해석과 표준 헤더까지 여러 층위의 사례를 듭니다.
컴파일러와 부동소수점 환경
GCC와 Clang은 기본 설정에서 asm을 확장 키워드로 인식합니다. 표준 C에서 asm은 예약되지 않은 식별자이므로, 이 설정에서는 표준에 맞는 코드가 컴파일되지 않을 수 있습니다. -std 옵션으로 표준 모드를 선택할 수 있지만, 글쓴이는 두 컴파일러가 기본값으로 표준을 따르지 않는 점과 C에서 asm을 예약어로 만들지 않은 이유를 짚습니다.
float.h의 FLT_ROUNDS는 실행 중 부동소수점 반올림 환경에 따라 값이 달라져야 합니다. 그러나 GCC는 fesetround 호출과 관계없이 항상 1, 즉 가장 가까운 값으로 반올림한다고 정의합니다. GCC 소스 주석도 이 값이 fenv.h의 호출에 따라 바뀌어야 한다고 적습니다. Clang은 __builtin_flt_rounds()를 제공하지만 GCC에는 이에 해당하는 내장 기능이 없다고 설명합니다.
C 표준은 부동소수점 연산 동작을 제어하는 pragma 다섯 가지를 규정합니다. C99에는 FP_CONTRACT, FENV_ACCESS, CX_LIMITED_RANGE가 있고, C23에는 FENV_ROUND, FENV_DEC_ROUND가 추가됐습니다. 글쓴이에 따르면 GCC는 이 pragma들을 지원하지 않고 무시합니다. 일부 동작은 명령행 옵션으로 바꿀 수 있지만, 옵션은 번역 단위 전체에 적용됩니다. pragma는 함수나 블록 단위로 적용할 수 있습니다. Clang은 C99 pragma 대부분을 지원하지만 C23 pragma는 지원하지 않는다고 적습니다.
라이브러리와 선언의 차이
initstate는 POSIX 확장 함수입니다. POSIX는 오류가 나면 NULL을 반환하도록 요구하지만, glibc는 오류를 나타내는 errno를 설정하면서도 NULL을 반환하지 않습니다. 예로 상태 배열에 8바이트보다 작은 공간을 주면 EINVAL은 설정되지만 반환값은 NULL이 아닙니다. glibc의 매뉴얼은 반환값 설명에서 POSIX 동작을 먼저 적고, 아래쪽 BUGS 항목에서 실제 구현의 차이를 밝힙니다.
함수 반환형에 붙은 한정자도 컴파일러마다 다르게 처리합니다. 표준은 함수 반환형의 const 같은 한정자를 버리도록 규정합니다. 따라서 int f();와 const int f(); 선언은 같은 함수형이어야 합니다. 그러나 Clang은 두 선언을 충돌로 판단해 오류를 냅니다.
musl의 stdarg.h는 컴파일러 내장 헤더보다 오래된 va_start 정의를 제공한다고 글쓴이는 지적합니다. musl은 va_start(v, l)을 __builtin_va_start(v, l)로 정의합니다. C23은 두 번째 인수를 생략하는 형태를 허용하지만 이 매크로는 이를 받지 못합니다. 글쓴이는 C23 모드에서 가변 인수 매크로와 __builtin_c23_va_start를 쓰는 정의가 필요하다고 제안합니다. 따라서 컴파일러가 단일 인수 형식을 지원하더라도 musl 환경에서는 사용할 수 없다고 설명합니다.
Lobsters 반응
- @interlandi —
CX_LIMITED_RANGE는 재현하기 어려운 버그의 악몽 같은 원인입니다. 임베디드 프로젝트에서 복소수 루프 변형을 다룰 때, 화요일 밤이면 모듈러스 값이 무한대로 튀어 무한 루프가 생겼습니다. 에뮬레이션을 켠-O2에서만 나타났습니다. 시스템은 계속 작동했고 인터럽트도 발생했으며 워치독도 동작했습니다. 관측할 수 있는 증상은 배터리가 가끔 급격히 닳는 것뿐이었습니다. MCU가 너무 뜨거워져 로그를 확인한 뒤에야 알아챘습니다.- @riking —
CX_LIMITED_RANGE에서 나눗셈의 "명백한 기호적 정의"가 대체 뭔가요. 으악.
- @riking —
원문: sebsite.pw / 번역·요약: Trawling