C 프로그래밍 언어

작성, 마지막 큰 수정

에 풉;에 첫 작성.

에 허튼소리로 옮기고 윤문 및 내용 추가. 에 마크업 변경 과정에서 한영 병행 표기를 살짝 수정.

C는 1970년대에 데니스 리치(Dennis Ritchie)가 유닉스 운영체제에 쓰려고 만든 프로그래밍 언어이다.

main()
{
    printf("hello, world\n");
}

《C 프로그래밍 언어(The C Programming Language)》 1판에 나오는 최초의 “Hello, world!” 프로그램.

시초에서 알 수 있듯 C는 본디 시스템 프로그래밍 용으로 만들어졌지만, 사실상 모든 플랫폼에 컴파일러가 있다는 특성 때문에 이식성 높은 소프트웨어를 개발하는 데 더 많이 쓰인다. 그럼에도 불구하고 이식성 높은 어셈블리라는 농담 아닌 농담이 있을 정도로 현대에 와서는 저수준으로 취급된다.

유닉스의 대성공과 함께 C는 역사상 가장 중요한 프로그래밍 언어 중 하나로 부상하게 되었는데, 이 때문에 C에서 영향을 받은 언어가 수도 없이 많다. 가장 대표적인 예로 C++, C#, 자바, 자바스크립트가 여기에 속한다. C 하면 바로 생각나는 중괄호를 사용한 문법은 보편화되어 중괄호 프로그래밍 언어라는 분류가 생기까지 했다.

버전 #

브라이언 커니핸(Brian Kernighan)과 데니스 리치(Dennis Ritchie)가 쓴 책 《C 프로그래밍 언어(The C Programming Language)》는 오랫동안 K&R C라 불리며 사실상의 C 표준으로 취급받았다. 이후 ANSI X3.159(통칭 ANSI C) 및 ISO/IEC 9899(통칭 ISO C)로 표준화되어 현재에 이른다. K&R C를 제외하면 표준이 나온 연도에 따라 C 뒤에 두 자리 연도를 붙이는 것이 관례이며, 미래 표준은 보통 C2x와 같이 마지막 자리를 생략한다.

문제점 #

C는 2021년 시점에서 나온지 반세기가 된 프로그래밍 언어이다. 그 사이에 프로그래밍 언어 이론이나 컴퓨터는 질적·양적으로 엄청난 성장을 이루었으며, 당시에 당연하다고 생각했던 제약이 현재는 쓸데 없는 것을 넘어서 프로그래머를 적극적으로 방해하는 수준에 도달했다.

메모리 안전성의 부재 #

C는 포인터에 아무 제약도 없어서 메모리 안전성이 부재한 대표적인 언어로 꼽힌다. C 호환성을 유지하는 C++ 등의 언어에서도 이 문제가 상존하지만, C++의 일부 기능만 사용할 경우 문제를 피하는 게 비교적 할 만 하다는 점과도 대조된다.

1970년대 당시에는 생각도 할 수 없던 개념이지만, 1988년의 모리스 웜을 시작으로 메모리 안전성은 컴퓨터 보안에서 빼 놓을 수 없는 요소가 되었다. 현대에는 이를 쓰레기 수거(GC)나 (드물게) 정적 분석으로 해결하는데, C에도 이를 지원하는 도구들이 많이 있지만 어느 쪽도 일반적으로 쓰이지 않는다.

지금 와서 C 코드를 짜야 한다면 무조건 메모리 안전성을 확인해 주는 도구를 사용해야 한다. 이를테면 Zed Shaw의 《깐깐하게 배우는 C(Learn C the Hard Way)》에서는 초장에 바로 valgrind를 쓰도록 하고 있다.

미정의 동작 #

C의 미정의 동작(Undefined Behavior, UB)은 본디 호환성을 위해 일부 언어 동작을 의도적으로 정의하지 않은 것이었다. 이를테면 플랫폼마다 부호 있는 정수가 오버플로되었을 때의 동작이 다르기 때문에, 이식성 높은 프로그램은 애초에 그런 동작을 하지 못하도록 한 것이다. 하지만 어느 플랫폼에서 프로그램이 실행될지 알고 있다면 정의된 동작을 한다고 가정하고 코드를 짤 수 있었던 것이다. 꽤 오랫동안 대부분의 컴파일러들이 이런 코드를 용인해 왔다.

C가 고수준 프로그래밍에서 점점 덜 쓰이게 되며 상대적으로 저수준 성능을 더 요구받게 되면서 이 상황이 꼬여 버렸다. 예를 들어서 포트란은 임의의 포인터를 지원하지 않는다는 점에서 C에 비해 최적화 여지가 더 많다고 평가되었는데, 이를 만회하기 위해 C99에서 한 주소를 서로 다른 타입을 가지는 포인터로 접근하는 것을 UB로 막아 버렸다(strict aliasing). 이런 추세에 따라 컴파일러들이 UB를 최적화 기회로 여기게 되자, 지금껏 문제 없이 동작하던 코드들이 갑자기 최적화되어 사라지며 오동작하기 시작한 것이다. 더 심각한 것은 그렇게 사라진 코드 중 일부는 바로 앞선 메모리 안전성을 확인하기 위한 코드였다는 것이다.

[주장:] C의 이러한 변화는 두 가지의 서로 상충되는 목표, 즉 충분히 저수준이어서 극한의 성능을 뽑아낼 수 있는 프로그래밍 언어라는 목표와, 이식성 높은 프로그래밍 언어라는 목표 사이의 충돌을 보여 준다. 물론 현재의 C는 두 마리 토끼 중 하나도 제대로 잡지 못한 상태로, 어느 한 쪽을 포기하지 않는다면 이러한 문제는 계속 반복될 것이다.

대규모 프로그래밍에 부적합 #

소프트웨어가 대규모화되면서 프로그래밍 언어도 이에 맞춰서 변화할 필요가 생겼다. 공개되는 이름의 목록을 제어하고 이름간의 충돌을 조율하는 모듈 시스템이나, 라이브러리를 지원하기 위한 패키지 관리자 등이 그러한 요구의 결과이다.

C에는 그런 기능이 하나도 없다. C에서 여러 파일을 연결하기 위한 수단은 여전히 선형적으로 실행되는 헤더 파일이며, 하다 못해 #pragma once와 같이 널리 쓰이는데다 최적화에도 도움이 되는 기능조차 표준화되지 않은 상태이다. 이 때문에 컴파일러들은 미리 컴파일된 헤더 파일(precompiled header file) 같은 것을 만들어 가며 고생하고 있다. 패키지 관리자 같은 건 꿈도 꾸지 못할 노릇인지라, 파일 하나짜리 라이브러리 같은 것이 호응을 얻어 널리 쓰이는 상황이다.

반동: 엘리트 주의 #

이런 상황인데도 C를 굳이 꼭 써야 한다고 주장하는 사람들이 꽤 남아 있다. 이들은 크게 둘로 나눌 수 있으며, 정도의 차이는 있지만 프로그래머 안에서의 엘리트 주의를 반영하기에 대체로 무익하다.