- 1989년 FM TOWNS용 High C Compiler는 DOS 환경 대응을 넘어, 당시 C 컴파일러로는 드문 사용자 지향 언어 기능을 여럿 담고 있었음
- Phar Lap의 DOS extender와 결합되며 16비트 MS-DOS 환경에서 32비트 80386을 활용하는 개발 흐름 속에 FM TOWNS의 1st-party C 컴파일러가 됨
- 숫자 리터럴 밑줄, 라벨 인자, case 범위, 중첩 함수, 제너레이터 등은 C/C++ 표준에 훨씬 늦게 들어갔거나 아직도 표준에 없는 기능임
- 중첩 함수는 함수 포인터와 컨텍스트 포인터를 함께 전달하는 비탈출 클로저 형태의 “full function value”를 제공해 일반 C 함수 포인터보다 표현력이 컸음
- 제너레이터는 중첩 함수 위의 문법 설탕으로 구현되어, 호출자의
for루프 본문을 중첩 함수로 바꿔yield인자로 넘기는 단순한 구조로 동작함
FM TOWNS와 High C의 자리
- FM TOWNS 관련 책 더미에서 나온 1980년대 C 컴파일러 매뉴얼이 예상보다 풍부한 언어 확장을 담고 있었음
- C와 그 계열 언어를 실제 환경에서 쓰려면 오랫동안 벤더 확장이 필요했음
- 오늘날 GCC, Clang, MSVC 중심 환경에서는 플랫폼별 처리나 저수준 세부 제어에 확장이 집중되는 경향이 있음
- 1980년대에는 더 작고 많은 회사들이 채택 경쟁을 벌이면서 확장 기능도 더 다양했음
- Phar Lap은 16비트 MS-DOS 환경에서 32비트 80386 프로세서를 활용하게 해주는 초기 DOS extender 중 하나를 만들었음
- MetaWare는 Phar Lap의 의뢰로 High C Compiler를 Phar Lap의 DOS extender SDK에 포팅함
- Fujitsu는 803386 기반 FM TOWNS 플랫폼 OS에 Phar Lap의 DOS extender를 통합했고, High C는 이 플랫폼의 1st-party C 컴파일러가 됨
- FM TOWNS는 첫 ANSI C 표준인 C89가 비준되기 직전인 1989년에 출시됨
표준보다 앞섰던 작은 편의 기능들
-
숫자 리터럴의 밑줄 구분자
- 긴 숫자 리터럴을 읽기 쉽게 만들기 위해 숫자 안에 밑줄 구분자를 넣을 수 있었음
- C++은 C++14에서
1'000'000같은 작은따옴표 구분자를 도입함 - C는 C23에서야 유사한 기능을 도입함
-
라벨 인자
- 매개변수가 많거나
bool처럼 호출 지점에서 의미가 드러나기 어려운 타입을 많이 쓰는 함수에서 인자 이름을 붙일 수 있음 - High C의 라벨 인자는 Python의 인기 기능과 비슷하게 동작함
- 인자 라벨은 선택 사항임
- 라벨이 있으면
argumentName => value문법으로 인자를 임의 순서로 지정할 수 있음 - 라벨 없는 인자와 라벨 있는 인자를 섞을 수 있지만, 함수의 모든 매개변수에는 대응하는 인자가 있어야 함
- 표준 C와 C++에는 아직 이 기능이 없음
- 매개변수가 많거나
-
case 범위
- Pascal의
case low..high처럼 값의 범위를 한 번에 매칭하는 기능을 제공함 - 표준 C와 C++은 이 기능을 채택하지 않았음
- Pascal의
중첩 함수와 full function value
- High C는 Pascal처럼 함수 안에 중첩 함수를 선언할 수 있음
- 구현 방식은 표준 Pascal이나 GCC의 중첩 함수 확장보다 더 완전한 형태에 가까움
- High C는 중첩 함수 선언뿐 아니라 full function value 타입도 선언할 수 있음
- 전통적인 C 함수 포인터와 달리 함수 포인터에 컨텍스트 포인터를 함께 담음
- 중첩 함수가 캡처한 컨텍스트를 다시 찾을 수 있게 해줌
- 바깥 함수가 반환된 뒤까지 수명이 이어지지는 않는 비탈출 클로저임
- GCC의 중첩 함수 확장은 중첩 함수를 일반 함수 포인터로 참조하게 하려고 호출 스택에 실행 가능한 코드를 써서 컨텍스트 포인터를 thunk했음
- 이 방식은 보안상 큰 위험으로 이어졌고, 많은 플랫폼이 해당 기능을 완전히 비활성화하게 됨
- High C의 로컬 함수 참조는 1급 값처럼 사용할 수 있지만, 바깥 함수 반환 이후까지 수명이 연장되지는 않음
- 중첩 함수는 부모 함수로
goto할 수도 있음- Smalltalk 블록처럼 중첩 함수 밖으로 빠져나가는 비지역 탈출이 가능함
- 이를 이용해 제어 흐름처럼 동작하는 함수를 만들 수 있음
- Objective-C는 2009년에 escaping closure로 사용할 수 있는 blocks를 얻었고, C++은 2011년에 lambdas를 도입함
- 두 기능 모두 비지역 탈출 능력은 갖지 않았음
- 표준 C에는 아직 공식 중첩 함수 기능이 없음
제너레이터 코루틴
- MetaWare는 한 장 전체를 할애할 정도로 제너레이터 기능을 강조함
- High C는 1989년에 plain C에서 Python 스타일 제너레이터 코루틴을 지원했음
- 제너레이터 함수는
void foo(Arg arguments) -> (Yield yields)문법으로 선언함- 함수 내부에서 마법 함수
yield(values...)를 여러 번 호출해 값의 시퀀스를 생성할 수 있음 - 호출자는
for variable... <- foo(arguments...) do { ... }형태의 새for루프 문법으로 생성된 값을 차례대로 순회함
- 함수 내부에서 마법 함수
- 이 구현은 중첩 함수와 복잡하게 조합될 수 있음
- 제너레이터 안의 중첩 함수는 바깥 제너레이터의
yield동작을 캡처할 수 있음 - 중첩 함수가 자신을 재귀 호출해 트리나 재귀적 데이터 구조를 순회하면서 각 단계에서
yield할 수 있음
- 제너레이터 안의 중첩 함수는 바깥 제너레이터의
- 이런 형태는 Python이나 많은 주류 제너레이터 코루틴 언어에서는 구현하기 어려운 방식으로 보임
제너레이터 구현 방식과 표준 언어와의 차이
- High C의 제너레이터는 고급 런타임 없이 중첩 함수 위의 문법 설탕으로 동작함
void foo(Arg arguments) -> (Yield yields)형태의 제너레이터 선언은 일반 함수void foo(void yield(Yield yields)!, Arg arguments)선언과 동등함yield는 “full function value” 타입의 암시적 매개변수임- 제너레이터 본문 안의
yield(values)호출은 이 암시적 함수 매개변수를 호출하는 일반 함수 호출임
- 호출자 쪽의
for루프 본문은 중첩 함수로 변환됨- 이 중첩 함수가 제너레이터의
yield인자로 전달됨 - 구조는 단순하지만 효과적임
- 이 중첩 함수가 제너레이터의
- 중첩 함수가 비지역 탈출을 지원하므로
for루프 본문 밖으로 나가는break,continue,goto도 적절한 루프 바깥 위치로goto하는 방식으로 동작함 - 표준 C가 이런 기능을 통합하려 할 가능성은 낮음
- C++20은 컴파일 타임 코루틴 변환에 기반한 매우 유연하고 복잡한 코루틴 기능을 제공함
- 이를 사용해 제너레이터를 구현할 수는 있을 것으로 보임
- 다만 그 결과물은 로컬 함수와 이렇게 직관적으로 조합되지는 않을 것으로 보임