C--(관습적으로 하이픈 마이너스를 사용하여 표기하며 'C 마이너스 마이너스'라고 읽음)는 인간 프로그래머가 직접 작성하기보다는 주로 고급 언어를 위한 컴파일러가 생성하도록 설계된 C와 유사한 프로그래밍 언어이다. 이 언어는 함수형 프로그래밍 연구자인 사이먼 페이튼 존스와 노먼 램지가 만들었다. 다른 많은 중간 언어와 달리 바이트코드나 다른 이진 형식이 아닌 일반 ASCII 텍스트로 표현된다.
두 가지 주요 분기가 존재한다:
- C--: 원래의 분기로, 2005년 5월에 최종 버전인 2.0이 출시되었다.
- Cmm: 글래스고 하스켈 컴파일러(GHC)에서 중간 표현(IR)으로 활발히 사용되는 포크(fork) 버전이다.
설계
C--는 "이식 가능한 어셈블리어"로, 고품질의 기계어를 생성하는 컴파일러의 구현을 용이하게 하도록 설계되었다. 이는 저수준의 코드 생성과 프로그램 최적화를 C-- 컴파일러에 위임함으로써 이루어진다. 이 언어의 구문은 C에서 많은 부분을 차용했지만, C--의 필수 기능과 코드 생성의 용이성을 저해한다고 간주되는 가변 인자 함수, 포인터 구문, C 자료형 체계의 일부 측면은 생략하거나 변경했다.
언어의 이름은 일종의 농담으로, C++이 C의 개선된 버전을 의미하기 위해 선택된 것과 마찬가지로 C--가 C의 축소된 형태임을 나타내기 위한 것이다. (C에서 --와 ++는 각각 '감소'와 '증가'를 의미한다.)
C--에 대한 작업은 1990년대 후반에 시작되었다. 독자적인 코드 생성기를 작성하는 것 자체가 어려운 일이고, 당시 연구자들이 사용할 수 있었던 컴파일러 백엔드들은 복잡하고 문서화가 제대로 되어 있지 않았기 때문에, 여러 프로젝트가 (예를 들어 원조 모듈라-3 컴파일러와 같이) C 코드를 생성하는 컴파일러를 작성했다. 그러나 C는 함수형 언어에 대해 꼬리 재귀 최적화를 보장하지 않거나, 정확한 쓰레기 수집 또는 효율적인 예외 처리를 지원하지 않는 등 여러 단점이 있다. C--는 이러한 모든 기능을 지원하는, C보다 더 엄격하게 정의된 단순한 대안으로 의도되었다. 가장 혁신적인 기능은 이식 가능한 쓰레기 수집기, 예외 처리 시스템, 그리고 모든 C-- 컴파일러에서 작동하는 기타 런타임 기능을 작성할 수 있게 해주는 런타임 인터페이스이다.
C--의 첫 번째 버전은 1998년 4월 MSRA 논문으로 발표되었으며, 1999년 1월 쓰레기 수집에 관한 논문이 뒤를 이었다. 1999년 5월에는 개정된 매뉴얼이 HTML 형태로 게시되었다. 2000년 노먼 램지의 "제안된 변경 사항"과 크리스티안 린디그의 "새로운 문법"이라는 두 가지 주요 변경 제안이 C-- 버전 2로 이어졌으며, 이는 2004년경에 마무리되어 2005년에 공식 발표되었다.
자료형 체계
C-- 자료형 체계는 고급 언어에서 강제하는 관례가 아닌, 하드웨어에 의해 부과되는 제약을 반영하도록 설계되었다. 레지스터나 메모리에 저장된 값은 오직 하나의 유형인 비트 벡터만을 가질 수 있다. 그러나 비트 벡터는 bits8, bits32, bits64와 같이 여러 너비로 존재하는 다형성 자료형이다. 32비트 또는 64비트의 부동소수점 자료형 계열도 지원된다. 비트 벡터 유형 외에도 C--는 bool이라는 불리언 유형을 제공하는데, 이는 표현식에 의해 계산되어 제어 흐름에 사용될 수 있지만 레지스터나 메모리에 저장할 수는 없다. 어셈블리어와 마찬가지로, 부호 있는 정수, 부호 없는 정수, 부동소수점, 포인터 간의 구별과 같은 상위 수준의 유형 규율은 C-- 연산자나 다른 구문적 구성에 의해 부과된다. C--는 자료형 검사를 수행하지 않으며, 호출 규약(calling convention)을 강제하거나 확인하지도 않는다.
C-- 버전 2는 비트 벡터 유형과 부동소수점 유형 간의 구분을 제거했다. 이러한 유형은 무엇보다도 변수의 정수와 부동소수점 유형, 그리고 저장 방식(전역 또는 지역)을 구별하기 위해 문자열 "kind" 태그로 주석을 달 수 있다. 전자는 정수와 부동소수점 값에 대해 별도의 레지스터를 가진 대상 환경에서 유용하다. 포인터와 네이티브 워드(native word)를 위한 특수 유형이 도입되었으나, 이들은 대상 환경에 따라 길이가 달라지는 비트 벡터에 매핑된다.
코드 예시
다음 C-- 코드는 1부터 n까지의 정수에 대한 합과 곱을 계산한다. (n은 인자로 전달된다.) 이 예시는 다음의 두 가지 언어 기능을 보여준다:
- 프로시저는 여러 개의 결과를 반환할 수 있다.
- "jump" 키워드를 사용하여 꼬리 재귀를 명시적으로 요청한다.
/* 꼬리 재귀 */ export sp; sp(bits32 n) { jump sp_help(n, 1, 1); } sp_help(bits32 n, bits32 s, bits32 p) { if n == 1 { return(s, p); } else { jump sp_help(n - 1, s + n, p * n); } }
또한, 간단한 "Hello World" 예시는 다음과 같다:
target byteorder little; import puts; export main; section "data"{ s:bits8[] "Hello World\0"; } foreign "C" main(){ foreign "C" puts("address"s); foreign "C" return(0); }
구현
C-- 명세 페이지에는 몇 가지 C-- 구현 사례가 나열되어 있다. 가장 활발하게 개발되었던 컴파일러인 Quick C--는 2013년에 개발이 중단되었다.
하스켈
사이먼 페이튼 존스, 주앙 디아스, 노먼 램지를 포함한 일부 C-- 개발자들은 GHC 작업에 참여했거나 참여하고 있으며, 이들의 개발 과정을 통해 C-- 언어가 확장되어 C 전처리기를 사용하는 Cmm 방언이 형성되었다.
GHC 백엔드는 C--를 LLVM IR, 저속 C, 또는 내장된 네이티브 백엔드를 통해 실행 가능한 코드로 변환하는 역할을 담당한다. 당초 의도와 달리 GHC는 일반적인 최적화의 많은 부분을 C--에서 수행한다. 다른 컴파일러 IR과 마찬가지로 C-- 표현식도 디버깅을 위해 덤프될 수 있다. 대상 환경별 최적화는 백엔드에 의해 나중에 수행된다.
처리 시스템
2018년 기준으로 대부분의 처리 시스템은 유지보수되지 않으며 소스 코드도 공개되지 않은 상태이다.
- Quick C--는 Quick C-- 팀이 개발한 컴파일러이다. C-- 코드 버전 2를 인텔 x86 리눅스 기계어로 컴파일한다. 다른 플랫폼을 위한 기계어 컴파일은 실험적 기능으로 제공된다. 이전에는 C-- 언어 명세의 발전과 병행하여 개발되었으나, 2019년 깃허브에서 프로젝트가 보관(archive) 처리되면서 개발이 중단되었다. 단, 소스 코드는 여전히 이용 가능하다.
- cmmc는 페르민 레이그가 ML 프로그래밍 언어로 구현한 C-- 컴파일러이다. Alpha, Sparc, x86 아키텍처를 위한 기계어를 생성한다.
- Trampoline C-- Compiler는 1999년 5월 세르게이 예고로프가 개발한 C-- to C 트랜스파일러이다. C-- 코드를 C 코드로 번역하여 표준 C 컴파일러를 사용하여 컴파일할 수 있게 해준다.
- 오리건 대학원 연구소의 C-- 컴파일러(OGI C-- Compiler)는 1997년 ML 프로그래밍 언어를 사용하여 개발된 최초의 프로토타입 C-- 컴파일러이다. Quick C-- 개발이 시작되면서 유지보수가 중단되었다.
외부 링크
- 과거 공식 웹사이트 아카이브 (cminusminus.org)
- Quick C-- 코드 아카이브 (참조 구현)
