C에서 sizeof가 놀라울 정도로 파싱하기 어려운 이유
요약
C 언어의 sizeof 연산자가 구문 분석 시 까다로운 이유와 그 역사적 배경을 다룹니다. 초기 컴파일러의 제약과 하위 호환성 유지 과정에서 발생한 문법적 복잡성, 그리고 전처리기와 타입 식별의 중요성을 설명합니다.
핵심 포인트
- C 문법은 형식 문법보다 초기 컴파일러의 구현 편의성에 따라 발전함
- sizeof 분석 시 typedef와 복합 리터럴을 구분하기 위한 정교한 로직이 필요함
- 정확한 구문 분석을 위해서는 전처리기와 스코프 내 의미 정보 활용이 필수적임
- i[ptr]와 같은 과거의 관습적 문법은 하위 호환성 문제로 인해 점진적으로 제거되는 추세임
C에는 1970년대 저수준 프로그래밍 언어라는 본질과 무관한 우발적 복잡성이 많음. 당시 다른 언어는 비교적 직관적인 문법도 사용했는데, C가 왜 이렇게 발전했는지 궁금함
Algol 계열과 달리 C는 형식 문법에서 출발하지 않았고, 사실상 “C 컴파일러가 받아들이는 것이 문법”이었음. C89 표준화 때는 기존 코드를 모두 수용하는 구문 분석 규칙을 정의해야 했고, 초기 컴파일러의 극심한 메모리 제약 때문에 추적할 상태를 줄이는 편법도 많이 들어감
초기 Fortran은 공백을 모두 무시해 MY X = 10과 MYX=10이 모두 X라는 변수를 선언하는 유효한 문장이었고, 이 때문에 무제한 선행 탐색이 필요했음
당시에는 좋은 문법에 대한 합의가 부족했고, C도 일반적으로 분석하기 쉬운 기능보다 기존 컴파일러에 추가하기 쉬운 기능을 채택했을 가능성이 큼. 오늘날이라면 식별자가 타입인지 변수인지에 따라 구문 트리가 달라지는 문법을 처음부터 설계하지 않겠지만, 당시에는 구문 분석 시점에 의미 정보가 이미 있었기에 이런 기능을 계속 추가하기 쉬웠을 것임
C를 분석해야 하는 도구가 보통 libclang이나 gcc에 의존하는 이유임. 관련 배경은 1에서 볼 수 있음
엄밀히 말하면 C 자체의 구문 분석은 그렇게 어렵지 않음. sizeof, 형 변환 등을 처리하려면 어떤 타입이 존재하는지 알아야 하므로 전처리기를 실행해야 하는데, 이를 제대로 하려면 사실상 clang이나 gcc의 기반 기능을 호출해야 함
따라서 핵심 난점은 전처리 이후의 C 코드보다 전처리기에 있음. Python으로 작성되어 libffi 등에서 쓰이는 pycparser와 제작자가 정리한 구현 접근법도 참고할 만함
이 문제는 심볼이 이름인지 타입인지 추적해서 해결할 수 있으며, C 구현이라면 어차피 필요한 작업임. 남는 모호성은 sizeof(type)과 sizeof((type){...})의 구분인데, 닫는 괄호 뒤를 조금만 선행 탐색하면 해결 가능함
핵심은 sizeof 자체보다 현재 스코프의 의미 정보를 이용해 타입 이름을 식별하는 것임. sizeof 뒤가 괄호가 아니면 단항식을 분석하고, 괄호 안이 타입이면 닫는 괄호 뒤의 {를 확인해 복합 리터럴과 후위 접미사를 처리하며, 아니면 타입 대상 sizeof로 처리하면 됨
단순히 심볼이 typedef인지 아는 것만으로는 부족하고, 복합 리터럴과 후위 접미사까지 처리해야 함. 직접 C 구문 분석기를 만들 때 오랫동안 괄호 안 타입만 검사하고 복합 리터럴을 빠뜨렸음
즉 타입처럼 보여도 실제로는 표현식일 수 있음. sizeof를 단순히 “표현식 또는 타입 분석”으로 생각하기 쉽지만, typedef 해석을 끝낸 뒤에도 추가 로직과 주의가 필요하다는 점이 의외였음
sizeof에서 괄호를 생략할 수 있다는 사실을 몰랐고, 항상 대상 전체를 괄호로 감싼 형태만 봐서 흥미롭지만 무섭게 느껴짐
sizeof가 함수가 아니라 연산자라 괄호가 필요 없다는 사실과, ptr[i]가 i[ptr]와 같다는 사실이 특히 기억에 남음
C2Y에서 마침내 i[ptr]가 폐기 예정 처리되어 이제 하위 호환성 때문에만 지원되며, 머지않아 제거되기를 기대함
배열 첨자 의미도 바뀌어 ptr이 배열이면 암묵적으로 포인터로 변환되지 않으므로 register 배열도 인덱싱할 수 있게 됨. 또한 예전에는 역참조 표현식으로 변환된 결과로만 좌측값이던 일부 배열 첨자식이 더 이상 좌측값이 아니게 됨 i[ptr]가 애초에 표준화된 것이 이상하며, 최소한 K&R식 함수 정의처럼 처음부터 폐기 후보였어야 함. 초기 컴파일러가 그렇게 구현한 이유는 이해하지만 첨자식 피연산자의 타입에 제약 하나를 추가하는 일은 어렵지 않았음
제시된 예제에서는 구문 분석이 특별히 어렵다고 느껴지지 않음. 이것도 어렵다고 한다면 훨씬 까다로운 문법을 접하지 않은 운 좋은 경력일 수 있음
기준에 따라 다르지만, C는 흔히 단순한 언어라고 불리면서도 분석하기 약간 성가시거나 자명하지 않은 요소가 많음. 그래도 충분히 구현 가능함
C 구문 분석은 사실상 불가능해 libclang이나 libgcc만이 답이라는 통념은 틀렸다고 봄. GNU 확장까지 포함한 분석기도 작성할 수 있지만 여러 부분이 난잡하며, 직접 만든 분석기도 바로 그 불가능론을 반박하려고 시작했음
글의 목적은 복합 리터럴에 대한 sizeof를 미처 고려하지 못했고, 닫는 괄호 뒤의 여는 중괄호만 확인하는 첫 해결책도 후위 연산자 때문에 불충분했다는 과정을 기록하는 것이었음. 전체적으로 터무니없이 어렵다는 뜻이 아니라, C치고는 의외로 단순하지 않다는 의미임
AI 자동 생성 콘텐츠
본 콘텐츠는 GeekNews의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기