News Feed

반세기 넘긴 C 언어, 아직도 현역인 이유

컨텐츠 정보

  • 조회 760

본문

C 프로그래밍 언어는 1972년부터 활발하게 사용되고 있으며, 소프트웨어로 가득 찬 세계에서 지금도 여전히 필수적인 구성 요소 중 하나로 군림하고 있다. 하지만 지난 몇십 년 사이 등장한 다른 많은 새로운 언어는 어떨까? 그중에는 C의 지배력에 도전할 목적으로 설계된 언어도 있고, 자체적으로 인기를 끌면서 C의 인기를 조금씩 침식하는 언어도 있다.

성능, 베어메탈 호환성, 보편성에서 C를 능가하기는 어렵다. 어쨌든 경쟁 관계의 몇몇 주요 언어와 비교해 살펴볼 만한 가치는 있다.

C vs. C++

C는 C++와 자주 비교된다. C++는 이름에서 알 수 있듯이 C의 확장판으로 만들어진 언어다. C++와 C의 차이점은 긍정적으로 표현하면 광범위하고 부정적으로 표현하면 복잡하다.

C++의 구문과 접근 방식은 여전히 C와 비슷하지만 C++에는 네임스페이스, 템플릿, 예외, 자동 메모리 관리 등 C에서는 기본적으로 사용할 수 없는 유용한 기능이 많다. 데이터베이스, ML 시스템과 같이 최고 수준의 성능이 필요한 프로젝트는 C++로 작성되는 경우가 많고 이런 다양한 기능을 사용해 시스템의 성능을 최대한 끌어낸다.

또한 C++는 C보다 훨씬 더 공격적으로 계속 확장되는 중이다. C++ 23은 모듈, 코루틴, 더 빠른 컴파일을 위한 모듈화된 표준 라이브러리를 비롯해 더욱 많은 기능을 제공하는 반면 C 표준의 최신 버전인 C23은 새로운 요소는 거의 추가하지 않은 채 하위 호환성을 유지하는 데 중점을 둔다.

단, C++의 모든 장점은 단점, 그것도 큰 단점으로 작용할 수도 있다. 더 많은 C++ 기능을 사용할수록 복잡성이 커지고 결과를 통제하기가 더 어려워진다. C++의 일부분만 한정적으로 사용하면 C++의 최악의 함정을 많이 피해갈 수 있다. 그러나 C++의 복잡성을 원천적으로 차단하고자 하는 경우도 있다. 예를 들어 리눅스 커널 개발팀은 C++를 기피한다. 이들은 커널을 위해 미래에 추가할 언어로 러스트를 주목하고 있지만 리눅스의 대부분은 여전히 C로 작성될 것이다.

개발자와 코드 유지관리자는 강제적 미니멀리즘을 포용하고 C++의 과잉에 휘말리지 않기 위해 C++ 대신 C를 선택할 수 있다. 물론 C++에 풍부한 고수준 기능이 있는 데는 그럴 만한 이유가 있다. 그러나 현재와 미래의 프로젝트와 프로젝트팀에 미니멀리즘이 더 적합하다면 C가 더 합리적이다.

C vs. 자바

자바는 수십 년 동안 많은 개선을 이루면서 지금까지 엔터프라이즈 소프트웨어 개발은 물론 전반적인 개발에서도 대표적인 언어로 사용되고 있다. 자바 구문의 많은 부분은 C와 C++에서 차용됐다. 다만 자바는 C와 달리 기본적으로 네이티브 코드로 컴파일되지 않는다. 대신 자바의 JIT(Just-in-Time) 컴파일러는 대상 환경에서 실행되도록 자바 코드를 컴파일한다. JIT 엔진은 프로그램 동작에 따라 런타임에 루틴을 최적화하므로 사전에 컴파일되는 C에서는 불가능한 많은 종류의 최적화가 가능하다. JIT 컴파일된 자바 코드는 적절한 환경에서 C에 근접한 성능을 내거나 능가하기도 한다.

또한 자바 런타임이 메모리 관리를 자동화하긴 하지만 자동 관리를 우회하는 것도 가능하다. 예를 들어 아파치 스파크는 자바 런타임의 “안전하지 않은” 부분을 사용해 메모리를 직접 할당 및 관리하고 JVM 가비지 수집 시스템의 오버헤드를 피해 메모리 내 처리 작업을 최적화한다.

“한 번 작성해서 어디서나 실행한다”라는 자바의 철학을 바탕으로 자바 프로그램은 대상 아키텍처에 따라 조정할 필요가 거의 없이 바로 실행할 수 있다. C는 많은 아키텍처로 이식됐지만 C 프로그램은 예컨대 윈도우와 리눅스에서 제대로 실행되기 위해서는 여전히 아키텍처별로 맞춤화 작업이 필요할 수 있다.

자바는 이 같은 이식성과 강력한 성능의 조합, 그리고 방대한 소프트웨어 라이브러리 및 프레임워크 생태계에 힘입어 엔터프라이즈 애플리케이션 빌드의 대표적인 언어이자 런타임으로 군림하고 있다. 자바가 C에 비해 떨어지는 영역은 애초에 자바가 경쟁할 의도가 없었던 부분, 즉 기계에 근접해서 실행되거나 하드웨어를 직접 다루는 부분이다.

C 코드는 프로세스에 의해 직접 실행되는 기계 코드로 컴파일된다. 자바는 중간 코드인 바이트코드로 컴파일되고 JVM 인터프리터가 이를 기계 코드로 변환하는 방식이다. 또한 자바의 자동 메모리 관리는 대부분의 상황에서 유용하지만 한정된 메모리 리소스를 최적으로 사용해야 하는 프로그램에는 초기 점유 면적 자체가 작은 C가 더 적합하다.

C vs. C# 및 닷넷

C#과 닷넷(.NET)은 처음 등장하고 거의 20년이 지난 지금까지 엔터프라이즈 소프트웨어 환경의 중요한 언어로 남아 있다. C#과 닷넷은 마이크로소프트가 관리형 코드 컴파일러 시스템이자 범용 런타임인 자바에 대항하기 위해 만든 것으로 알려졌으며, C와 자바의 많은 비교가 C와 C#/닷넷에도 적용된다.

닷넷은 자바(그리고 일정 부분 파이썬)와 마찬가지로 다양한 플랫폼에 걸친 이식성과 광범위한 통합 소프트웨어 생태계를 제공한다. 닷넷에서 이뤄지는 개발의 상당 부분이 기업 용도임을 감안하면 이는 결코 작지 않은 이점이다. C# 또는 다른 닷넷 언어로 프로그램을 개발하는 경우 닷넷 런타임용으로 만들어진 방대한 툴과 라이브러리를 활용할 수 있다.

자바와 비슷한 닷넷의 또 다른 이점은 JIT 최적화다. C#과 닷넷 프로그램은 C와 마찬가지로 사전 컴파일이 가능하지만 대부분의 경우 닷넷 런타임에 의해 적시(JIT) 컴파일되고 런타임 정보를 사용해 최적화된다. JIT 컴파일은 실행 중인 닷넷 프로그램에 대한 온갖 종류의 실시간 최적화를 가능하게 해준다. C에서는 불가능한 부분이다.

C#과 닷넷은 C(그리고 어느 정도 자바)와 마찬가지로 메모리에 직접 액세스하기 위한 다양한 메커니즘을 제공한다. 힙과 스택, 관리되지 않는 시스템 메모리는 모두 닷넷 API와 객체를 통해 액세스할 수 있다. 또한 개발자는 닷넷의 비안전 모드를 사용해 한층 더 높은 성능을 얻을 수도 있다.

다만 이 모든 장점이 아무런 대가 없이 제공되는 것은 아니다. 관리되는 객체와 안전하지 않은 객체는 임의로 상호 교환할 수 없고, 이들 간의 마샬링에는 성능상의 비용이 따른다. 따라서 닷넷 애플리케이션의 성능을 극대화한다는 것은 곧 관리되는 객체와 관리되지 않는 객체 간의 이동을 최소한으로 유지하는 것을 의미한다.

관리되지 않는 메모리 대비 관리되는 메모리에 따르는 불이익을 감당할 수 없거나 대상 환경(예를 들어 커널 공간)에 비추어 닷넷 런타임이 부적절한 선택이거나 아예 사용할 수 없는 경우 C가 필요하다. 또한 C# 및 닷넷과 달리 C에서는 직접 메모리 액세스가 기본적으로 열려 있다.

C vs. 구글 고

고(Go)는 코드 가독성, 즉 어느 고 프로젝트든 개발자가 짧은 시간 내에 파악하고 코드베이스를 능숙하게 다룰 수 있도록 하는 것을 주 설계 목표 중 하나로 개발된 언어다. C 코드베이스는 특정 프로젝트와 팀에 특화된 매크로와 #ifdef가 난무해 이해하기 어려울 수 있다. 고의 구문, 그리고 내장된 코드 서식과 프로젝트 관리 툴은 이러한 종류의 조직적 문제를 방지하는 데 중점을 둔다.

고 구문은 많은 부분을 C에서 차용했다. 예를 들어 중괄호를 구분 기호로 사용하고 세미콜론으로 문을 끝낸다. 고에 특화된 기능을 고려하더라도 C에 능숙한 개발자라면 일반적으로 큰 어려움 없이 고로 넘어갈 수 있다.

고의 특징적인 기능에는 고루틴과 채널이 포함된다. 고루틴과 채널은 구성요소 간의 동시성과 메시지 전달을 처리하기 위한 언어 수준 툴이다. C에서는 이런 기능을 수제작하거나 외부 라이브러리에서 가져와야 하지만 고는 기본적으로 제공하므로 이와 같은 기능이 필요한 소프트웨어를 훨씬 더 쉽게 구축할 수 있다.

내부적으로 고와 C의 가장 큰 차이점은 메모리 관리다. 고 객체에서는 기본적으로 관리와 가비지 수집이 자동으로 이뤄진다. 대부분의 프로그래밍 작업에서 매우 편리하지만, 대신 결정적인 메모리 취급이 필요한 프로그램을 작성하기는 어렵다.

고에는 일부 타입 처리 안전 장치(예를 들어 Pointer 타입으로 임의의 메모리 읽고 쓰기)를 우회하기 위한 unsafe 패키지가 포함돼 있다. 다만 “unsafe 패키지를 사용해 작성된 프로그램은 이식이 불가능할 수 있으며 고 1 호환성 가이드라인으로 보호되지 않는다”는 경고문을 주의해서 읽을 필요가 있다.

고는 이런 세밀한 조작이 거의 불필요한 명령줄 유틸리티, 네트워크 서비스와 같은 프로그램을 빌드하는 데 적합하다. 고 개발진은 AI/ML을 비롯해 더 까다로운 워크로드에 대한 고의 적합성을 개선할 방법도 고민 중이다. 그러나 저수준 디바이스 드라이버, 커널 공간 운영체제 구성요소, 그리고 메모리 레이아웃 및 관리와 같이 엄밀한 제어가 필요한 작업은 C로 만드는 것이 최선이다.

C vs. 러스트

어떤 면에서 러스트는 C와 C++의 난해한 메모리 관리와 그 외에도 다른 많은 단점의 대응책으로 등장한 언어다. 러스트는 네이티브 기계 코드로 컴파일되므로 성능 면에서 C와 동등한 것으로 평가된다. 그러나 기본적으로 러스트가 내세우는 가장 큰 장점은 메모리 안전성이다.

러스트의 구문과 컴파일 규칙은 개발자가 일반적인 메모리 관리 실수를 피하는 데 도움이 된다. 컴파일 타임에 잡아낼 수 있는 메모리 관리 문제가 존재할 경우 아예 프로그램 컴파일이 되지 않는다. 러스트 초보자, 특히 이러한 버그가 발생할 여지가 큰 C와 같은 언어에서 건너온 초보자는 처음 한동안은 컴파일러의 요구사항을 충족하는 방법을 익히는 데 시간을 소비하게 된다. 그러나 러스트 지지자들은 이 같은 단기적인 고충이 장기적으로는 실행 속도를 희생하지 않는 더 안전한 코드라는 보상으로 이어진다고 주장한다.

러스트의 툴도 C에 비해 개선됐다. 고와 마찬가지로 프로젝트와 구성요소 관리가 툴체인의 일부로 러스트와 함께 기본 제공된다. 패키지, 관리, 프로젝트 폴더 구성을 비롯해 C에서는 프로젝트와 팀마다 각기 다르게 임시방편으로 처리하는 많은 다른 부분을 처리하기 위한 권장 기본 방식이 있다(예를 들어 C에는 표준 패키지 관리자조차 없음).

그러나 러스트가 장점으로 내세우는 것이 C 개발자 입장에서는 그렇게 보이지 않을 수 있다. 러스트의 컴파일 타임 안전 기능은 비활성화할 수 없으므로 아주 사소한 러스트 프로그램이라 해도 러스트의 메모리 안전 구조에 따라야 한다. C는 기본적으로 덜 안전할 수는 있지만 필요할 때 훨씬 더 유연하고 운신의 폭이 넓다.

단점으로 볼 수 있는 또 다른 부분은 러스트 언어의 크기다. C는 표준 라이브러리를 감안하더라도 기능의 수가 비교적 적은 편이다. 반면 러스트 기능 집합은 방대하며 계속 증가하는 중이다. C++도 그렇듯이 기능 집합이 커질수록 더 강력한 대신 복잡성도 더 커진다. C는 상대적으로 크기가 작은 언어지만 개발자가 머릿속으로 이것저것을 구상하기가 훨씬 더 쉽다. 따라서 러스트가 필요에 비해 과잉인 프로젝트에는 C가 더 적합할 수 있다.

C vs. 파이썬

요즘 소프트웨어 개발에 대한 대화에서는 파이썬이 거의 항상 등장한다. 파이썬은 “모든 면에서 두 번째로 좋은 언어”이며, 분명 가장 다재다능한 언어 중 하나이고, 사용 가능한 써드 파티 라이브러리도 수천 개에 이른다. 폭발적으로 인기를 끌면서 세계에서 가장 널리 사용되는 프로그래밍 언어로 부상했다.

파이썬이 강조하는 부분이자 C와의 가장 큰 차이점은 실행 속도보다 개발 속도를 중시한다는 것이다. C와 같은 다른 언어로 작성하는 경우 한 시간 정도가 걸릴 프로그램을 파이썬을 사용하면 몇 분 만에 완성할 수 있다. 반면 C로 만들었다면 몇 초 만에 실행될 프로그램이 파이썬에서는 1분이 걸릴 수 있다. 경험적으로 파이썬 프로그램은 C 프로그램에 비해 대체로 실행 속도가 훨씬 더 느리다. 다만 최신 하드웨어에서 실행할 경우 많은 작업이 파이썬으로도 충분히 빠르며, 바로 이 점이 파이썬이 인기를 끈 중요한 이유다.

또 다른 큰 차이점은 메모리 관리에 있다. 파이썬 프로그램의 경우 파이썬 런타임이 전적으로 메모리를 관리하므로 개발자가 메모리 할당과 해제에 대해 세세히 신경 쓸 필요가 없다. 그러나 여기서도 개발자의 편의성에는 런타임 성능의 대가가 따른다. C 프로그램을 작성하려면 메모리 관리에 세심하게 주의를 기울여야 하지만 그 결과로 얻는 프로그램은 순수한 기계 속도 그대로 실행되는 경우가 많다.

하지만 내부적으로 파이썬과 C에는 깊은 연결 고리가 있다. 레퍼런스 파이썬 런타임은 C로 작성된다. 덕분에 파이썬 프로그램은 C와 C++로 작성된 라이브러리를 래핑할 수 있다. 파이썬 써드 파티 라이브러리 생태계의 상당 부분은(예를 들어 ML용 라이브러리) 그 중심에 C 코드가 있다. 많은 경우 관건은 C냐 파이썬이냐가 아니라, 애플리케이션의 어떤 부분을 C로 작성하고 어떤 부분을 파이썬으로 작성하느냐다.

개발 속도가 실행 속도보다 중요하고 프로그램에서 고성능 요소의 대부분을 독립적인 구성요소로 분리할 수 있다면(코드 전반에 분산시키지 않고) 순수 파이썬, 또는 파이썬과 C 라이브러리를 혼합하는 것이 C만 사용하는 것보다 더 낫다. 그 외의 경우에는 여전히 C가 우위다.

C vs. 카본

C와 C++의 또 다른 경쟁자로 최근에 등장한 카본(Carbon)이 있다. 카본은 현재 개발이 활발히 진행 중인 비교적 신생 언어다.

카본의 목표는 C와 C++의 현대적 대안이 되는 것이다. 이를 위해 간소한 구문, 현대적인 툴과 코드 구성 방법, C 및 C++ 프로그래머들이 오랫동안 직면해 온 문제에 대한 해결책을 제시한다. 또한 C++ 코드베이스와의 상호운용성을 제공하므로 기존 코드를 점진적으로 마이그레이션할 수 있다. C와 C++의 툴과 프로세스는 최근에 개발된 다른 여러 언어에 비해 원시적인 만큼 카본의 이 같은 노력은 환영할 만한 일이다.

그렇다면 단점은 무엇일까? 지금의 카본은 실험적인 프로젝트이며 프로덕션 용도로 사용하려면 아직 갈 길이 멀다. 작동하는 컴파일러조차 없으며 온라인 코드 탐색기만 있을 뿐이다. 카본이 C 또는 C++의 현실적인 대안이 되기까지는 아직 시간이 필요하고, 실제로 대안이 된다는 보장도 없다.
dl-itworldkorea@foundryco.com

관련자료

댓글 0
등록된 댓글이 없습니다.
Member Rank