C (프로그래밍 언어)

C는 1970년대에 데니스 리치가 만든 범용 프로그래밍 언어이다. 설계상 C는 프로그래머에게 일반적인 CPU 아키텍처의 기능에 상대적으로 직접 접근할 수 있게 하며, 대상 명령어 집합에 맞게 조정된다. C는 운영체제(특히 커널), 장치 드라이버, 프로토콜 스택을 구현하는 데 사용되어 왔고 현재도 사용되고 있으나, 응용 소프트웨어에서의 사용은 점차 감소하고 있다. C는 거대한 슈퍼컴퓨터부터 가장 작은 마이크로컨트롤러와 임베디드 시스템에 이르기까지 다양한 컴퓨터에서 사용된다.

프로그래밍 언어 B의 후계자인 C는 원래 1972년에서 1973년 사이에 리치가 유닉스에서 실행되는 유틸리티를 제작하기 위해 벨 연구소에서 개발했다. 이는 유닉스 운영체제의 커널을 다시 구현하는 데 적용되었다. 1980년대에 걸쳐 C는 점차 인기를 얻었다. C는 가장 널리 사용되는 프로그래밍 언어 중 하나가 되었으며, 사실상 모든 현대적 컴퓨터 구조와 운영체제를 위한 C 컴파일러가 존재한다. 원래 언어 설계자가 공동 저술한 책인 C 프로그래밍 언어 (책)은 오랫동안 이 언어의 사실상의 표준 역할을 했다. C는 1989년부터 미국 국가표준 협회(ANSI)에 의해 표준화되었으며, 이후 국제 표준화 기구(ISO)와 국제전기기술위원회(IEC)에 의해 공동으로 관리되고 있다.

C는 명령형 절차적 언어로, 구조적 프로그래밍, 어휘적 변수 범위, 재귀를 지원하며 정적 자료형 체계를 갖추고 있다. C는 최소한의 런타임 지원만으로 메모리에 대한 저수준 접근을 제공하고 기계어 명령에 효율적으로 매핑되는 언어 구문을 제공하도록 컴파일되도록 설계되었다. 저수준 기능에도 불구하고, 이 언어는 크로스 플랫폼 프로그래밍을 장려하도록 설계되었다. 이식성을 염두에 두고 작성되어 표준을 준수하는 C 프로그램은 소스 코드를 거의 수정하지 않고도 매우 다양한 컴퓨터 플랫폼과 운영체제에서 컴파일될 수 있다.

C나 그 표준 라이브러리는 다른 언어에서 볼 수 있는 몇 가지 인기 있는 기능을 제공하지 않지만, 이를 지원할 수 있을 만큼 유연하다. 예를 들어, 객체 지향 프로그래밍과 쓰레기 수집은 각각 외부 라이브러리인 GLib Object System과 보엠 가비지 컬렉터(Boehm garbage collector)를 통해 제공된다.

2000년 이후 C는 일반적으로 TIOBE 인덱스에서 가장 인기 있거나 두 번째로 인기 있는 언어로 순위가 매겨져 왔다.

 특징 
섬네일|C 프로그래밍 언어의 창시자인 데니스 리치(오른쪽)와 켄 톰프슨

C 언어는 다음과 같은 특징을 나타낸다:

 자유 형식 소스 코드
 쌍반점으로 문을 종결함
 중괄호로 문들을 그룹화함
 실행 코드는 함수 내에 포함됨 (스크립트 같은 구문이 없음)
 매개변수는 값에 의해 전달됨(pass-by-value). 참조에 의한 전달은 값에 대한 포인터를 전달함으로써 달성됨
 상대적으로 적은 수의 키워드
 if, for, do, while, switch를 포함한 제어 흐름 구문
 를 포함한 산술, 비트, 논리 연산자
 단일 문에서 다중 할당을 수행할 수 있음
 사용자 정의 식별자는 키워드와 구별되지 않음 (즉, 시길을 사용하지 않음)
 블록 내에 선언된 변수는 해당 블록 내에서만, 그리고 선언 아래에서만 접근 가능함
 함수의 반환 값을 무시할 수 있음
 함수 내에 함수를 중첩할 수 없으나, 일부 변환기는 이를 지원함
 함수 포인터를 사용하여 런타임 다형성을 구현할 수 있음
 재귀를 지원함
 데이터 타이핑은 정적이지만 약하게 강제됨. 모든 변수는 자료형을 갖지만, 기본 자료형 간의 암시적 변환이 서로 다른 자료형 간의 분리를 약화시킴
 사용자 정의 자료형을 통해 자료형 지정자에 별칭을 부여할 수 있음
 배열 정의 및 접근 구문은 대괄호 표기법(예: month[11])을 사용함. 인덱싱은 포인터 산술의 관점에서 정의됨. 전체 배열은 사용자 정의 코드나 라이브러리 코드 없이는 복사하거나 비교할 수 없음
 사용자 정의 구조체 유형을 사용하면 관련 데이터 요소를 하나의 단위로 전달하고 복사할 수 있지만, 각 필드를 비교하는 사용자 정의 코드 없이는 두 구조체를 비교할 수 없음
 사용자 정의 공용체 유형은 겹치는 멤버를 지원하여 여러 자료형이 동일한 메모리 위치를 공유할 수 있게 함
 사용자 정의 열거 유형은 정수 값에 별칭을 부여하는 것을 지원함
 문자열 자료형이 부족하지만, 표준 라이브러리에 관련 처리 기능이 있는 널 종단 문자열에 대한 구문을 갖추고 있음
 포인터를 통해 컴퓨터 메모리에 대한 저수준 접근을 지원함
 void를 반환하는 함수로서 프로시저와 유사한 구문을 지원함
 표준 라이브러리 함수를 통해 동적 메모리를 지원함
 매크로 정의, 소스 코드 파일 포함 및 조건부 컴파일을 수행하는 C 전처리기를 포함함
 파일이 개별적으로 처리된다는 점에서 모듈성을 지원하며, static 및 extern 속성을 통해 가시성을 제어함
 핵심 언어의 기능은 최소화하면서 입출력, 문자열 조작, 수학 함수와 같은 상대적으로 복잡한 기능은 표준 라이브러리 함수를 통해 지원함
 결과물인 컴파일된 코드는 하부 플랫폼에 대해 상대적으로 간단한 요구사항을 가지므로, 운영체제 및 임베디드 시스템에 적합함

 "Hello, world" 예제 
섬네일|브라이언 커니핸이 작성한 "Hello, World!" 프로그램 (1978)
K&R 초판에 등장한 "Hello, World!" 프로그램 예제는 대부분의 프로그래밍 교과서에서 입문 프로그램의 모델이 되었다. 이 프로그램은 표준 출력에 "hello, world"를 출력한다.

원래 버전은 다음과 같았다:

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

보다 현대적인 버전은 다음과 같다:

#include <stdio.h>

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

첫 번째 줄은 #include로 표시되는 C 전처리기 지시문으로, 전처리기가 해당 코드 줄을 stdio.h 헤더 파일의 텍스트로 대체하게 한다. 이 헤더 파일에는 printf를 포함한 입출력 함수들의 선언이 들어 있다. stdio.h 주위의 꺾쇠괄호는 프로젝트 전용 디렉터리에 있는 동일한 이름의 파일보다 컴파일러와 함께 제공된 헤더 파일을 우선적으로 선택하는 검색 전략을 사용하여 헤더 파일을 찾을 수 있음을 나타낸다.

다음 코드 줄은 엔트리 포인트 함수인 main을 선언한다. 런타임 환경은 프로그램 실행을 시작하기 위해 이 함수를 호출한다. 자료형 지정자 int는 함수가 정수 값을 반환함을 나타낸다. void 매개변수 목록은 함수가 인수를 받지 않음을 나타낸다. 런타임 환경은 실제로 두 개의 인수(int 및 char *[] 유형)를 전달하지만, 이 구현에서는 이를 무시한다. ISO C 표준(섹션 5.1.2.2.1)은 void이거나 이 두 개의 인수를 갖는 구문을 요구하며이는 다른 함수에는 제공되지 않는 특별한 처리이다.

여는 중괄호는 함수의 내용을 정의하는 코드의 시작을 나타낸다.

다음 코드 줄은 문자열 리터럴로 지정된 널 종단 문자열의 첫 번째 문자의 메모리 주소와 함께 C 표준 라이브러리 함수인 printf를 호출(실행을 전환)한다. 텍스트 \n은 터미널에서 출력될 때 커서를 다음 줄의 시작으로 이동시키는 새줄 문자를 나타내는 이스케이프 시퀀스이다. printf는 int 값을 반환하지만, 여기서는 암묵적으로 버려진다. 쌍반점 ;은 호출 문을 종결한다.

닫는 중괄호는 main 함수의 끝을 나타낸다. C99 이전에는 main 함수의 끝에 명시적인 return 0; 문이 필요했으나, C99 이후부터는 main 함수(초기 함수 호출로서)가 마지막 닫는 중괄호에 도달하면 암묵적으로 0을 반환한다.

 역사 

 초기 개발 
+C 언어 연대기 연도 비공식명칭 공식표준 1972 첫 출시  1978 K&R C  1989,1990 ANSI C, C89,ISO C, C90 ANSI X3.159-1989ISO/IEC 9899:1990 1999 C99, C9X ISO/IEC 9899:1999 2011 C11, C1X ISO/IEC 9899:2011 2018 C17, C18 ISO/IEC 9899:2018 2024 C23, C2X ISO/IEC 9899:2024  C2Y

C의 기원은 유닉스 운영체제의 개발과 밀접하게 연관되어 있다. 유닉스는 원래 데니스 리치와 켄 톰프슨이 동료들의 여러 아이디어를 통합하여 PDP-7에서 어셈블리어로 구현했다. 결국 그들은 운영체제를 PDP-11로 이식하기로 결정했다. 유닉스의 원래 PDP-11 버전 역시 어셈블리어로 개발되었다.

 B 

톰프슨은 새로운 플랫폼을 위한 유틸리티를 개발할 프로그래밍 언어를 원했다. 그는 처음에 포트란 컴파일러 작성을 시도했으나 곧 그 생각을 포기하고, 대신 최근에 개발된 BCPL이라는 시스템 프로그래밍 언어의 축소 버전을 만들었다. 당시 BCPL의 공식 설명서는 구할 수 없었고, 톰프슨은 구문을 덜 '장황하게' 만들고 SMALGOL이라고 알려진 단순화된 알골과 비슷하게 수정했다. 그는 그 결과를 B라고 불렀으며, 이를 "많은 SMALGOL 구문을 가진 BCPL 의미론"이라고 설명했다. BCPL과 마찬가지로 B에는 새로운 기계로의 이식을 용이하게 하는 부트스트래핑 컴파일러가 있었다. 궁극적으로 B는 너무 느렸고 바이트 주소 지정 기능과 같은 PDP-11의 기능을 활용할 수 없었기 때문에 B로 작성된 유틸리티는 거의 없었다.

줄 끝까지 주석을 표시하는 BCPL의 // 주석 스타일과 달리, B는 PL/1에 더 가까운 /* 주석 */을 주석 구분자로 채택하여 줄 중간에 주석이 나타날 수 있게 했다. (BCPL의 주석 스타일은 나중에 C++에서 다시 도입되었다.)

 New B와 첫 C 출시 
1971년 리치는 더 강력한 PDP-11의 기능을 사용하기 위해 B를 개선하기 시작했다. 중요한 추가 사항은 문자 자료형이었다. 그는 이를 New B(NB)라고 불렀다. 톰프슨은 유닉스 커널을 작성하기 위해 NB를 사용하기 시작했고, 그의 요구 사항이 언어 개발의 방향을 결정했다.

1972년에 걸쳐 NB 언어에 더 풍부한 유형들이 추가되었다. NB에는 int와 char 배열이 있었고, 여기에 포인터, 다른 유형에 대한 포인터를 생성하는 능력, 모든 유형의 배열, 그리고 함수에서 반환될 유형들이 추가되었다. 표현식 내의 배열은 사실상 포인터로 취급되었다. 새로운 컴파일러가 작성되었고 언어의 이름은 C로 변경되었다.

C 컴파일러와 그것으로 만든 일부 유틸리티는 리서치 유닉스로도 알려진 버전 2 유닉스에 포함되었다.

 구조체와 유닉스 커널 재작성 
1973년 11월에 출시된 버전 4 유닉스에서 유닉스 커널은 C로 광범위하게 다시 구현되었다. 이 무렵 C 언어는 struct 유형과 같은 강력한 기능들을 획득했다.

C 전처리기는 앨런 스나이더의 촉구와 BCPL 및 PL/I에서 사용 가능한 파일 포함 메커니즘의 유용성을 인식하여 1973년경에 도입되었다. 초기 버전은 포함 파일과 매개변수 없는 매크로의 간단한 문자열 교체(#include 및 #define)만 제공했다. 그 직후 마이크 레스크와 존 라이저에 의해 매개변수가 있는 매크로와 조건부 컴파일을 통합하도록 확장되었다.

유닉스는 어셈블리어 이외의 언어로 구현된 최초의 운영체제 커널 중 하나였다. 이전 사례로는 1961년 멀틱스 시스템(PL/I로 작성됨)과 Burroughs B5000용 마스터 컨트롤 프로그램(MCP)(알골로 작성됨)이 있다. 1977년경 리치와 스티븐 C. 존슨은 유닉스 운영체제의 이식성을 용이하게 하기 위해 언어를 더욱 변경했다. 존슨의 포터블 C 컴파일러는 새로운 플랫폼에서 C의 여러 구현을 위한 기반 역할을 했다. (참고: PDF는 원본의 OCR 스캔본이며, "IBM 370"을 "IBM 310"으로 렌더링한 오류를 포함하고 있음.)

 K&R C 
섬네일|240x240px|브라이언 커니핸과 데니스 리치가 저술한 C 프로그래밍 언어 초판 표지
1978년 브라이언 커니핸과 데니스 리치는 C 프로그래밍 언어 (책) 초판을 출간했다. 저자들의 머리글자를 따서 K&R로 알려진 이 책은 오랫동안 언어의 비공식 시방서 역할을 했다. 이 책에서 설명하는 C의 버전은 흔히 "K&R C"라고 불린다. 이 버전은 1978년에 출시되었기 때문에 현재는 C78이라고도 불린다.   이 책의 개정판은 아래에서 설명하는 이후의 ANSI C 표준을 다룬다.

K&R은 몇 가지 언어 기능을 도입했다:
 표준 입출력 라이브러리
 long int 자료형
 unsigned int 자료형
 =op 형태(예: =-)의 복합 할당 연산자는 op= 형태(즉, -=)로 변경되었다. 이는 i=-10과 같은 구문이 원래 의도했을 수도 있는 i = -10(i에 -10을 대입) 대신 i = i - 10(i를 10만큼 감소)으로 해석되는 등의 의미적 모호성을 제거하기 위함이었다.

1989년 ANSI 표준이 발표된 후에도 오랫동안 K&R C는 많은 구형 컴파일러가 여전히 사용 중이었고, 주의 깊게 작성된 K&R C 코드가 표준 C에서도 적법할 수 있었기 때문에 최대의 이식성을 원할 때 C 프로그래머들이 스스로를 제한하는 "최소 공통분모"로 간주되었다.

이후 버전의 C에서는 함수가 명시적인 유형 선언을 가져야 하지만, K&R C는 int 이외의 유형을 반환하는 함수만 사용 전에 선언할 것을 요구한다. 사전 선언 없이 사용된 함수는 int를 반환하는 것으로 간주되었다.

예시:

long long_function();

calling_function()
{
    long longvar;
    register intvar;
    longvar = long_function();
    if (longvar > 1)
          intvar = 0;
    else
          intvar = int_function();
    return intvar;
}

의 선언(1행)은 이 함수가 가 아닌 을 반환하기 때문에 필요하다.  함수는 를 반환하므로 선언되지 않았더라도 호출(11행)할 수 있다. 또한 변수 는  키워드의 기본 자료형이 이므로 자료형을 선언할 필요가 없다.

함수 선언에 인수에 대한 정보가 포함되지 않았기 때문에 자료형 검사가 수행되지 않았지만, 일부 컴파일러는 함수에 대한 서로 다른 호출이 다른 수나 유형의 인수를 사용하는 경우 경고를 보냈다. 여러 소스 파일에 걸친 함수 사용의 일관성을 검사하는 유닉스의 린트 유틸리티와 같은 도구들이 개발되었다.

K&R C 출간 이후 몇 년 동안 AT&T(특히 PCC) 및 다른 벤더들의 컴파일러에 의해 지원되는 여러 기능이 언어에 추가되었다. 여기에는 다음이 포함된다:
 void 함수; 값을 반환하지 않는 함수
 Struct 또는 union 유형을 반환하는 함수
 struct 변수에 대한 할당
 열거형 유형

이 언어의 인기, 표준 라이브러리 인터페이스에 대한 합의 부족, K&R 사양 준수 부족 등으로 인해 표준화 노력이 이어졌다.

 ANSI C 및 ISO C 

1970년대 후반과 1980년대에 걸쳐 C의 인기가 크게 증가함에 따라 IBM PC를 포함한 매우 다양한 메인프레임, 미니컴퓨터, 마이크로컴퓨터용으로 C 버전들이 구현되었다.

1983년 미국 국가표준 협회(ANSI)는 C의 표준 사양을 확립하기 위해 위원회 X3J11을 구성했다. X3J11은 유닉스 구현을 바탕으로 C 표준을 수립했으나, 유닉스 C 라이브러리의 비이식성 부분은 IEEE 실무단 1003에 넘겨져 1988년 POSIX 표준의 기초가 되었다. 1989년 C 표준은 ANSI X3.159-1989 "Programming Language C"로 비준되었다. 이 언어의 버전은 종종 ANSI C, 표준 C, 또는 때때로 C89라고 불린다.

1990년 ANSI C 표준은(형식 변경을 거쳐) 국제 표준화 기구(ISO)에 의해 ISO/IEC 9899:1990으로 채택되었으며, 이는 때때로 C90이라고 불린다. 따라서 "C89"와 "C90"이라는 용어는 동일한 프로그래밍 언어를 가리킨다.

다른 국가 표준 기관들과 마찬가지로 ANSI는 더 이상 C 표준을 독립적으로 개발하지 않고, 실무단 ISO/IEC JTC 1/SC 22/WG14에서 유지 관리하는 국제 C 표준을 따른다. 국제 표준 업데이트의 국가적 채택은 일반적으로 ISO 출판 후 1년 이내에 이루어진다.

C 표준화 프로세스의 목표 중 하나는 이후 도입된 많은 비공식 기능들을 통합하여 K&R C의 상위 집합을 만드는 것이었다. 표준 위원회는 또한 C++에서 빌려온 함수 원형, void 포인터, 국제 문자 집합 및 로케일 지원, 전처리기 강화 등 여러 추가 기능을 포함했다. 매개변수 선언을 위한 구문이 C++에서 사용되는 스타일을 포함하도록 확장되었지만, 기존 소스 코드와의 호환성을 위해 K&R 인터페이스도 계속 허용되었다.

C89는 현재의 C 컴파일러들에 의해 지원되며, 대부분의 현대적 C 코드는 이를 기반으로 한다. 표준 C로만 작성되고 하드웨어 의존적인 가정이 없는 프로그램은 자원 제한 내에서 준수하는 모든 C 구현이 있는 플랫폼에서 올바르게 실행된다. 이러한 예방 조치가 없으면, 예를 들어 GUI 라이브러리와 같은 비표준 라이브러리 사용이나 자료형의 정확한 크기 및 바이트 엔디언과 같은 컴파일러 또는 플랫폼 특정 속성에 대한 의존으로 인해 프로그램이 특정 플랫폼이나 특정 컴파일러에서만 컴파일될 수 있다.

코드가 표준 준수 컴파일러 또는 K&R C 기반 컴파일러 모두에서 컴파일 가능해야 하는 경우, __STDC__ 매크로를 사용하여 코드를 표준 및 K&R 섹션으로 분할함으로써 표준 C에서만 사용 가능한 기능이 K&R C 기반 컴파일러에서 사용되는 것을 방지할 수 있다.

ANSI/ISO 표준화 프로세스 이후 C 언어 사양은 몇 년 동안 상대적으로 정체되어 있었다. 1995년에는 일부 세부 사항을 수정하고 국제 문자 집합에 대한 광범위한 지원을 추가하기 위해 1990년 C 표준의 규범적 수정안 1(ISO/IEC 9899/AMD1:1995, 비공식적으로 C95로 알려짐)이 발표되었다.

 C99 

C 표준은 1990년대 후반에 더욱 개정되어 1999년에 ISO/IEC 9899:1999가 발표되었으며, 이는 일반적으로 "C99"라고 불린다. 이후 기술 정오표에 의해 세 차례 수정되었다.

C99는 인라인 함수, 여러 새로운 자료형(long long int 및 복소수를 나타내는 complex 유형 포함), 가변길이배열 및 유연한 배열 멤버, 개선된 IEEE 754 부동 소수점 지원, 가변 항수 매크로(가변 항수의 매크로) 지원, BCPL이나 C++에서와 같이 //로 시작하는 단일 행 주석 지원 등 여러 새로운 기능을 도입했다. 이들 중 많은 기능은 이미 여러 C 컴파일러에서 확장 기능으로 구현되어 있었다.

C99는 대부분 C90과 하위 호환되지만 몇 가지 측면에서 더 엄격하다. 특히 유형 지정자가 없는 선언은 더 이상 암묵적으로 int로 간주되지 않는다. 표준 매크로 __STDC_VERSION__은 C99 지원이 가능함을 나타내기 위해 199901L 값으로 정의된다. GCC, Solaris Studio 및 기타 C 컴파일러들은 현재 C99의 새로운 기능 중 상당수 또는 전부를 지원한다. 그러나 마이크로소프트 비주얼 C++의 C 컴파일러는 C89 표준과 C++11과의 호환을 위해 필요한 C99의 일부만을 구현한다.

또한 C99 표준은 이스케이프된 문자 형태(예:  또는 )를 사용한 유니코드 식별자 지원을 요구하며, 원시 유니코드 이름 지원을 권장한다.

 C11 

2011년 12월 8일 ISO/IEC 9899:2011로 공식 발표되기 전까지 비공식적으로 "C1X"라 불렸던 C 표준의 또 다른 개정 작업이 2007년에 시작되었다. C 표준 위원회는 기존 구현에 의해 테스트되지 않은 새로운 기능의 채택을 제한하는 지침을 채택했다.

C11 표준은 제네릭 매크로, 익명 구조체, 개선된 유니코드 지원, 원자적 연산, 멀티스레딩, 경계 검사 함수 등 수많은 새로운 기능을 C와 라이브러리에 추가했다. 또한 기존 C99 라이브러리의 일부를 선택 사항으로 만들고 C++와의 호환성을 개선했다. 표준 매크로 __STDC_VERSION__은 C11 지원이 가능함을 나타내기 위해 201112L로 정의된다.

 C17 

C17은 2018년 6월에 발표된 C 프로그래밍 언어 표준인 ISO/IEC 9899:2018의 비공식 명칭이다. 새로운 언어 기능은 도입하지 않았으며, C11의 결함에 대한 기술적 수정 및 명확화만 이루어졌다. 표준 매크로 __STDC_VERSION__은 C17 지원이 가능함을 나타내기 위해 201710L로 정의된다.

 C23 

C23은 현재 주요 C 언어 표준 개정판의 비공식 명칭이다. 개발 기간 내내 "C2X"로 알려졌다. 과거 릴리스를 기반으로 하여 새로운 키워드, 변수 선언 시 자료형 추론을 제공하는 auto의 추가 의미, nullptr_t 및 _BitInt(N)을 포함한 새로운 유형, 표준 라이브러리의 확장 등의 기능을 도입했다.

C23은 2024년 10월 ISO/IEC 9899:2024로 발표되었다. 표준 매크로 __STDC_VERSION__은 C23 지원이 가능함을 나타내기 위해 202311L로 정의된다.

 C2Y 

C2Y는 C23(C2X) 이후 2020년대 후반에 출시될 것으로 예상되는 다음 주요 C 언어 표준 개정판의 비공식 명칭이며, "C2Y"의 '2'는 이를 의미한다. C2Y의 초기 작업 초안은 2024년 2월 실무단 ISO/IEC JTC 1/SC 22/WG14에 의해 N3220으로 공개되었다.

 임베디드 C 

역사적으로 임베디드 C 프로그래밍은 고정 소수점, 다중 독립 메모리 뱅크, 기본 입출력 연산과 같은 특이한 기능을 지원하기 위해 C 언어에 대한 비표준 확장이 필요했다.

2008년 C 표준 위원회는 모든 구현이 준수할 공통 표준을 제공함으로써 이러한 문제를 해결하기 위해 C 언어를 확장하는 기술 보고서를 발표했다. 여기에는 고정 소수점 산술, 명명된 주소 공간, 기본 입출력 하드웨어 주소 지정 등 일반 C에서는 사용할 수 없는 여러 기능이 포함되어 있다.

 정의 

C는 C 표준에 의해 지정된 형식 문법을 갖는다. C를 위한 BNF 문법을 포함하고 있음. 행 끝은 일반적으로 C에서 의미가 없으나, 전처리 단계에서는 행 경계가 의미를 갖는다. 주석은 구분 기호 /*와 */ 사이에 나타나거나, (C99 이후부터) // 다음에 행 끝까지 나타날 수 있다. /*와 */로 구분된 주석은 중첩될 수 없으며, 이러한 문자 시퀀스가 문자열 리터럴이나 캐릭터 리터럴 내부에 나타나면 주석 구분 기호로 해석되지 않는다.

C 소스 파일은 선언과 함수 정의를 포함한다. 함수 정의는 다시 선언과 문을 포함한다. 선언은 struct, union, enum과 같은 키워드를 사용하여 새로운 유형을 정의하거나, 유형을 작성하고 그 뒤에 변수 이름을 써서 새로운 변수에 유형을 할당하고 아마도 저장 공간을 예약한다. char 및 int와 같은 키워드는 내장 유형을 지정한다. 코드 섹션은 중괄호({ 및 })로 묶어 선언의 범위를 제한하고 제어 구조를 위한 단일 문처럼 작동하게 한다.

명령형 언어로서 C는 작업을 지정하기 위해 문을 사용한다. 가장 일반적인 문은 평가할 식 뒤에 쌍반점이 붙는 식 문이다. 평가의 부작용으로 함수가 호출되거나 변수에 새로운 값이 할당될 수 있다. 일반적인 순차적 실행을 수정하기 위해 C는 예약된 키워드로 식별되는 여러 제어 흐름 문을 제공한다. 구조적 프로그래밍은 if ... [else] 조건부 실행과 do ... while, while, for 반복 실행(루핑)에 의해 지원된다. for 문은 별도의 초기화, 테스트, 재초기화 식을 가지며, 이 중 어느 것이나 생략할 수 있다. break와 continue는 루프 내에서 사용될 수 있다. Break는 가장 안쪽의 루프 문을 벗어나는 데 사용되고 continue는 재초기화로 건너뛰는 데 사용된다. 또한 함수 내의 지정된 레이블로 직접 분기하는 비구조적 goto 문도 있다. switch는 정수 식의 값에 따라 실행할 case를 선택한다. 다른 많은 언어들과 달리, 제어 흐름은 break에 의해 종료되지 않는 한 다음 case로 계속 진행된다.

식은 다양한 내장 연산자를 사용할 수 있으며 함수 호출을 포함할 수 있다. 함수의 인수와 대부분의 연산자의 피연산자가 평가되는 순서는 지정되지 않는다. 평가는 심지어 섞일 수도 있다. 그러나 모든 부작용(변수 저장 포함)은 다음 "시퀀스 포인트" 이전에 발생한다. 시퀀스 포인트에는 각 식 문의 끝, 그리고 각 함수 호출의 진입 및 반환이 포함된다. 시퀀스 포인트는 특정 연산자(&&, ||, ?: 및 콤마 연산자)를 포함하는 식의 평가 중에도 발생한다. 이를 통해 컴파일러는 높은 수준의 목적 코드 최적화를 수행할 수 있으나, C 프로그래머가 다른 프로그래밍 언어에서 필요한 것보다 더 신중하게 신뢰할 수 있는 결과를 얻을 것을 요구한다.

커니핸과 리치는 C 프로그래밍 언어의 서문에서 다음과 같이 말한다: "C는 다른 언어와 마찬가지로 결함이 있다. 일부 연산자는 잘못된 우선순위를 가지며, 구문의 일부는 더 나을 수도 있었다." C 표준은 이러한 결함 중 많은 부분을 수정하려 하지 않았는데, 이는 이미 존재하는 소프트웨어에 미칠 영향 때문이다.

 문자 집합 
기본 C 소스 문자 집합에는 다음 문자들이 포함된다:"Committee Draft ISO/IEC 9899:TC3: 5.2.1 Character sets".
2007.
 ISO 기본 라틴 알파벳의 소문자와 대문자: a–z, A–Z
 십진 숫자: 0–9
 그래픽 문자: ! " # % & ' ( ) * + , - . / : ; < = > ? [ \ ] ^ _ { | } ~
 공백 문자: 공백, 수평 탭, 수직 탭, 폼 피드, 새줄 문자

새줄 문자는 텍스트 행의 끝을 나타내며, 편의상 C에서는 단일 문자로 취급하지만 실제 단일 문자에 대응할 필요는 없다.

POSIX 표준은 기본 C 소스 문자 집합에 몇 가지 문자(특히 "@")를 추가한 이동성 문자 집합을 규정한다. 두 표준 모두 특정 값 인코딩을 규정하지 않는다. ASCII와 EBCDIC는 이 기본 문자들을 최소한 포함하고 있으므로 비록 다른 인코딩 값을 사용하더라도 이 표준들을 준수한다.

추가적인 멀티바이트 인코딩 문자가 문자열 리터럴에서 사용될 수 있으나, 이는 완전히 이식 가능하지는 않다. C99 이후부터 다국어 유니코드 문자는 \uXXXX 또는 \UXXXXXXXX 인코딩(여기서 X는 16진수 문자를 나타냄)을 사용하여 C 소스 텍스트 내에 이식 가능하게 포함될 수 있다.

기본 C 실행 문자 집합은 동일한 문자들과 함께 널 문자, 경고음, 백스페이스, 캐리지 리턴을 위한 표현을 포함한다.

확장 문자 집합에 대한 런타임 지원은 C 표준의 각 개정판과 함께 증가해 왔다.

 예약어 
C의 모든 버전에는 대소문자를 구분하는 예약어가 있다. 예약어이므로 변수 이름으로 사용할 수 없다.

C89에는 32개의 예약어가 있다:

 auto
 break
 case
 char
 const
 continue
 default
 do
 double
 else
 enum
 extern
 float
 for
 goto
 if
 int
 long
 register
 return
 short
 signed
 sizeof
 static
 Struct
 switch
 Typedef
 union
 unsigned
 void
 volatile
 while

C99는 5개의 예약어를 추가했다: (‡는 C23 키워드에 대한 대체 철자 별칭을 나타냄)

 inline
 restrict
 _Bool ‡
 _Complex
 _Imaginary

C11은 7개의 예약어를 추가했다: (‡는 C23 키워드에 대한 대체 철자 별칭을 나타냄)

 _Alignas ‡
 _Alignof ‡
 _Atomic
 _Generic
 _Noreturn
 _Static_assert ‡
 _Thread_local ‡

C23은 15개의 단어를 예약했다:

 alignas
 alignof
 bool
 constexpr
 false
 nullptr
 static_assert
 thread_local
 true
 typeof
 typeof_unqual
 _BitInt
 _Decimal32
 _Decimal64
 _Decimal128

최근에 예약된 단어들 대부분은 밑줄 뒤에 대문자가 오는 형식으로 시작한다. 이는 해당 형식의 식별자가 이전에 구현체에서만 사용하도록 C 표준에 의해 예약되었기 때문이다. 기존 프로그램 소스 코드는 이러한 식별자를 사용하지 않았어야 하므로, C 구현체가 이러한 프로그래밍 언어 확장을 지원하기 시작하더라도 영향을 받지 않을 것이다. 일부 표준 헤더는 밑줄로 시작하는 식별자에 대해 더 편리한 동의어를 정의한다. 이 중 일부 단어는 C23에서 관습적인 철자로 키워드로 추가되었고 해당 매크로는 제거되었다.

C89 이전에는 entry가 키워드로 예약되어 있었다. 커니핸과 리치는 C89가 된 내용을 설명하는 그들의 책 C 프로그래밍 언어 (책) 개정판에서 "이전에 예약되었으나 결코 사용되지 않았던 ... [키워드] entry는 더 이상 예약되지 않는다" 및 "사산된 entry 키워드는 철회된다"라고 썼다.

 연산자 

C는 식 내에서 평가를 수행하는 동안 수행될 조작을 지정하는 기호인 연산자 집합을 풍부하게 지원한다. C는 다음과 같은 연산자를 갖는다:
 산술: +, -, *, /, %
 할당: =
 복합 할당: 
 비트 논리: ~, &, |, ^
 비트 이동: <<, >>
 불리언 논리: !, &&, ||
 조건부 평가: ? :
 동등성 테스트: ==, !=
 함수 호출: ( )
 증감 연산자: ++, --
 멤버 선택: ., ->
 객체 크기: sizeof
 자료형: Typeof, C23 이후 typeof_unqual
 순서 관계: <, <=, >, >=
 참조 및 역참조: &, *, [ ]
 순차화: ,
 부분식 그룹화: ( )
 형 변환: (typename)

C는 포트란과 PL/I의 선례를 따라 할당을 나타내기 위해 연산자 =(수학에서 등식 표현에 사용됨)를 사용하지만, 알골 및 그 유도 언어들과는 다르다. C는 동등성을 테스트하기 위해 연산자 ==를 사용한다. 할당 연산자와 동등 연산자 사이의 유사성은 한쪽을 다른 쪽 대신 실수로 사용하는 결과를 초래할 수 있으며, 많은 경우 이 실수는 오류 메시지를 생성하지 않는다(일부 컴파일러는 경고를 생성함). 예를 들어, 조건식 if (a == b + 1)을 실수로 if (a = b + 1)로 작성할 수 있는데, 이는 할당 후 a의 값이 0이 아닌 한 true로 평가된다.

C의 연산자 우선순위가 항상 직관적인 것은 아니다. 예를 들어, 연산자 ==는 x & 1 == 0과 같은 식에서 연산자 &(비트 AND) 및 |(비트 OR)보다 더 단단히 결합되어(먼저 실행되어) 코더의 의도가 그러하다면 (x & 1) == 0으로 작성되어야 한다.

 자료형 

섬네일
C의 자료형 체계는 정적이며 약하게 유형화되어 있으며, 이는 파스칼과 같은 알골 계열의 유형 시스템과 유사하다. 부호가 있거나 없는 다양한 크기의 정수, 부동 소수점 수, 열거형(enum)을 위한 내장 유형들이 있다. 정수 유형 char는 종종 단일 바이트 문자에 사용된다. C99는 불리언 자료형을 추가했다. 또한 배열, 포인터, 레코드(Struct), 공용체(union)를 포함한 파생 유형들도 있다.

C는 자료형 체계로부터의 탈출이 필요할 수 있는 저수준 시스템 프로그래밍에 종종 사용된다. 컴파일러는 대부분의 식의 유형 정확성을 보장하려 시도하지만, 프로그래머는 값을 한 유형에서 다른 유형으로 명시적으로 변환하기 위해 형 변환을 사용하거나, 포인터나 공용체를 사용하여 데이터 객체의 하부 비트를 다른 방식으로 재해석함으로써 검사를 무시할 수 있다.

일부 사람들은 특히 함수 포인터에 대한 C의 선언 구문이 비직관적이라고 생각한다. (리치의 아이디어는 식별자가 사용되는 문맥과 유사하게 선언하는 것이었다: "선언은 사용을 반영한다".)

C의 일반적인 산술 변환은 효율적인 코드가 생성되도록 하지만, 때때로 예상치 못한 결과를 초래할 수 있다. 예를 들어, 동일한 너비의 부호 있는 정수와 부호 없는 정수를 비교할 때 부호 있는 값을 부호 없는 값으로 변환해야 한다. 부호 있는 값이 음수일 경우 예상치 못한 결과가 발생할 수 있다.

 포인터 
C는 메모리 내의 객체나 함수의 주소 또는 위치를 기록하는 참조의 일종인 포인터 사용을 지원한다. 포인터는 포인터가 가리키는 주소에 저장된 데이터에 접근하거나 가리키는 함수를 호출하기 위해 역참조될 수 있다. 포인터는 할당이나 포인터 산술을 사용하여 조작될 수 있다. 포인터 값의 런타임 표현은 일반적으로 원시 메모리 주소(단어 내 오프셋 필드로 보강될 수 있음)이지만, 포인터의 유형에는 가리키는 대상의 유형이 포함되므로 포인터를 포함하는 식은 컴파일 타임에 유형 검사를 받을 수 있다. 포인터 산술은 가리키는 자료형의 크기에 의해 자동으로 조정된다.

포인터는 C에서 많은 목적으로 사용된다. 문자열은 일반적으로 문자 배열에 대한 포인터를 사용하여 조작된다. 동적 메모리 할당은 포인터를 사용하여 수행되며, malloc의 결과는 대개 저장될 데이터의 자료형으로 캐스팅된다. 연결 리스트나 트리와 같은 많은 자료형은 대개 포인터를 사용하여 서로 연결된 동적 할당 struct 객체로 구현된다. 다른 포인터에 대한 포인터는 다차원 배열 및 struct 객체의 배열에서 자주 사용된다. 함수에 대한 포인터(함수 포인터)는 함수를 고차 함수(예: qsort 또는 bsearch)의 인수로 전달하거나, 디스패치 테이블 또는 이벤트 핸들러에 대한 콜백으로 유용하다.

널 포인터 값은 명시적으로 유효하지 않은 위치를 가리킨다. 널 포인터 값을 역참조하는 것은 정의되지 않았으며, 종종 세그멘테이션 오류를 초래한다. 널 포인터 값은 연결 리스트의 마지막 노드에서 "다음" 포인터가 없음을 나타내거나, 포인터를 반환하는 함수의 오류 표시와 같은 특수한 경우를 나타내는 데 유용하다. 소스 코드의 적절한 문맥에서(예: 포인터 변수에 할당할 때), 널 포인터 상수는 포인터 유형으로 명시적 캐스팅을 하거나 하지 않고 0으로 작성할 수 있으며, 여러 표준 헤더에 정의된 NULL 매크로 또는 C23 이후로는 nullptr 상수로 작성할 수 있다. 조건부 문맥에서 널 포인터 값은 false로 평가되는 반면, 다른 모든 포인터 값은 true로 평가된다.

보이드 포인터(void *)는 지정되지 않은 유형의 객체를 가리키며, 따라서 "범용" 데이터 포인터로 사용될 수 있다. 가리키는 객체의 크기와 유형을 알 수 없으므로 보이드 포인터는 역참조할 수 없으며 포인터 산술도 허용되지 않지만, 다른 객체 포인터 유형으로(또는 그로부터) 쉽게(그리고 많은 문맥에서 암시적으로) 변환될 수 있다.

부주의한 포인터 사용은 잠재적으로 위험하다. 포인터는 일반적으로 검사되지 않기 때문에 포인터 변수가 임의의 위치를 가리키도록 만들 수 있으며, 이는 바람직하지 않은 영향을 초래할 수 있다. 적절하게 사용된 포인터는 안전한 장소를 가리키지만, 유효하지 않은 포인터 산술을 사용함으로써 안전하지 않은 장소를 가리키게 할 수 있다. 포인터가 가리키는 객체는 할당 해제된 후에도 계속 사용될 수 있고(허상 포인터), 초기화되지 않은 상태로 사용될 수도 있으며(와일드 포인터), 캐스트, 공용체 또는 다른 손상된 포인터를 통해 직접 안전하지 않은 값이 할당될 수도 있다. 일반적으로 C는 포인터 유형 간의 조작과 변환을 허용하는 데 관대하지만, 컴파일러는 대개 다양한 수준의 검사를 위한 옵션을 제공한다. 다른 프로그래밍 언어들은 더 제한적인 참조 유형을 사용하여 이러한 문제들을 해결한다.

 배열 

C에서 배열 유형은 전통적으로 컴파일 타임에 지정된 고정된 정적 크기를 갖는다. 보다 최근의 C99 표준은 가변길이배열 형식도 허용한다. 그러나 표준 라이브러리의 malloc 함수를 사용하여 런타임에 (임의의 크기의) 메모리 블록을 할당하고 이를 배열로 취급하는 것도 가능하다.

배열은 항상 (사실상) 포인터를 통해 접근되므로, 비록 일부 컴파일러가 옵션으로 경계 검사를 제공할 수는 있지만, 배열 접근은 대개 하부 배열 크기에 대해 검사되지 않는다.예를 들어, gcc는 _FORTIFY_SOURCE를 제공한다.  따라서 배열 경계 위반이 가능하며, 이는 잘못된 메모리 접근, 데이터 손상, 버퍼 오버플로, 런타임 예외를 초래할 수 있다.

C는 다차원 배열을 선언하기 위한 특별한 규정이 없으며, 대신 유형 시스템 내에서 배열의 배열을 선언하는 재귀에 의존하며, 이는 사실상 동일한 목적을 달성한다. 결과물인 "다차원 배열"의 인덱스 값은 행 우선 순서로 증가하는 것으로 생각할 수 있다. 다차원 배열은 행렬을 저장하기 위해 수치 알고리즘(주로 응용 선형대수학에서)에서 흔히 사용된다. C 배열의 구조는 이 특정 작업에 잘 맞는다. 그러나 초기 버전의 C에서 배열의 경계는 고정된 값이어야 했으며, 그렇지 않으면 이를 필요로 하는 서브루틴에 명시적으로 전달되어야 했고, 동적 크기의 배열의 배열은 이중 인덱싱을 사용하여 접근할 수 없었다. (이에 대한 해결책은 열에 대한 포인터의 추가적인 "행 벡터"와 함께 배열을 할당하는 것이었다.) C99는 이 문제를 해결하는 "가변 길이 배열"을 도입했다.

현대 C(C99 이상)를 사용하여 힙에 2차원 배열을 할당하고 접근을 위해 다차원 배열 인덱싱을 사용하는 예시는 다음과 같다(많은 C 컴파일러에서 경계 검사를 사용할 수 있음):
int func(int n, int m) {
    float (*p)[n][m] = malloc(sizeof *p);
    if (p == NULL) {
         return -1;
    }
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            (*p)[i][j] = i + j;
        }
    }
    print_array(n, m, p);
    free(p);
    return 1;
}

그리고 C99의 자동 VLA 기능을 사용한 유사한 구현은 다음과 같다:
int func(int n, int m) {
    // 주의: n * m * sizeof(float)가 자동 VLA의 제한을 초과하지 않고 스택의 가용 크기 내에 있는지 확인하기 위한 검사가 이루어져야 함.
    float p[n][m]; // 자동 VLA는 스택에 유지되며, 함수가 호출될 때 크기가 결정됨
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            p[i][j] = i + j;
    }
    print_array(n, m, p);
    // 함수가 종료될 때 나머지 스택 프레임과 함께 사라지므로 free(p)를 할 필요가 없음
    return 1;
}

 배열-포인터 상호 교환성 
첨자 표기법 x[i](여기서 x는 포인터)는 *(x+i)에 대한 신택틱 슈거이다. 포인터 유형에 대한 컴파일러의 지식을 활용하여, x + i가 가리키는 주소는 기본 주소(x가 가리키는 곳)에 i 바이트를 더한 것이 아니라, 기본 주소에 i와 x가 가리키는 요소의 크기를 곱한 값을 더한 것으로 정의된다. 따라서 x[i]는 배열의 i+1번째 요소를 지정한다.

더 나아가, 대부분의 식 문맥에서(주목할 만한 예외는 sizeof의 피연산자일 때임), 배열 유형의 식은 자동으로 배열의 첫 번째 요소에 대한 포인터로 변환된다. 이는 배열이 함수의 인수로 이름이 불릴 때 전체가 복사되는 것이 아니라 첫 번째 요소의 주소만 전달됨을 의미한다. 따라서 C의 함수 호출은 값에 의한 전달 의미론을 사용하지만, 배열은 사실상 참조에 의해 전달된다.

배열 x의 전체 크기는 배열 유형의 식에 sizeof를 적용하여 결정할 수 있다. 요소의 크기는 n = sizeof A[0]과 같이 배열 A의 역참조된 요소에 연산자 sizeof를 적용하여 결정할 수 있다. 따라서 선언된 배열 A의 요소 개수는 sizeof A / sizeof A[0]으로 결정될 수 있다. 위에서 설명한 자동 변환 때문에 C 코드에서 흔히 그렇듯이 첫 번째 요소에 대한 포인터만 사용할 수 있는 경우, 배열의 전체 유형과 길이에 대한 정보는 손실된다는 점에 유의해야 한다.

 메모리 관리 
프로그래밍 언어의 가장 중요한 기능 중 하나는 메모리와 메모리에 저장된 객체를 관리하기 위한 시설을 제공하는 것이다. C는 객체를 위해 메모리를 할당하는 세 가지 주요 방법을 제공한다:
 정적 메모리 할당: 객체를 위한 공간이 컴파일 타임에 바이너리에 제공된다. 이 객체들은 이를 포함하는 바이너리가 메모리에 로드되어 있는 동안 지속되는 범위(또는 수명)를 갖는다.
 자동 메모리 할당: 임시 객체들은 스택에 저장될 수 있으며, 이 공간은 그것들이 선언된 블록을 벗어난 후에 자동으로 해제되고 재사용 가능하다.
 동적 메모리 할당: 임의의 크기의 메모리 블록을 힙이라고 불리는 메모리 영역으로부터 malloc과 같은 라이브러리 함수를 사용하여 런타임에 요청할 수 있다. 이 블록들은 나중에 라이브러리 함수 realloc 또는 free를 호출하여 재사용을 위해 명시적으로 해제될 때까지 유지된다.

이 세 가지 접근 방식은 서로 다른 상황에 적합하며 다양한 장단점이 있다. 예를 들어, 정적 메모리 할당은 할당 오버헤드가 거의 없고, 자동 할당은 약간 더 많은 오버헤드를 수반할 수 있으며, 동적 메모리 할당은 할당과 해제 모두에서 잠재적으로 많은 오버헤드를 가질 수 있다. 정적 객체의 지속적인 성격은 함수 호출 간에 상태 정보를 유지하는 데 유용하며, 자동 할당은 사용하기 쉽지만 스택 공간은 일반적으로 정적 메모리나 힙 공간보다 훨씬 더 제한적이고 일시적이다. 그리고 동적 메모리 할당은 런타임에만 크기를 알 수 있는 객체를 편리하게 할당할 수 있게 한다. 대부분의 C 프로그램은 이 세 가지를 광범위하게 사용한다.

가능한 경우, 자동 또는 정적 할당이 일반적으로 가장 단순한데, 이는 저장 공간이 컴파일러에 의해 관리되어 프로그래머가 저장 공간을 수동으로 할당하고 해제해야 하는 잠재적으로 오류가 발생하기 쉬운 잡무에서 벗어나게 해주기 때문이다. 그러나 많은 데이터 구조는 런타임에 크기가 변할 수 있으며, 정적 할당(및 C99 이전의 자동 할당)은 컴파일 타임에 고정된 크기를 가져야 하므로 동적 할당이 필요한 상황이 많다. C99 표준 이전에는 가변 크기 배열이 이에 대한 흔한 예였다. (동적으로 할당된 배열의 예시는 C 동적 메모리 할당 문서를 참조.) 통제되지 않은 결과를 초래하며 런타임에 실패할 수 있는 자동 할당과 달리, 동적 할당 함수는 요청된 저장 공간을 할당할 수 없을 때 (널 포인터 값의 형태로) 표시를 반환한다. (너무 큰 정적 할당은 대개 프로그램이 실행을 시작하기도 전에 링커나 로더에 의해 감지된다.)

별도로 지정하지 않는 한, 정적 객체는 프로그램 시작 시 0 또는 널 포인터 값을 포함한다. 자동 및 동적으로 할당된 객체는 초기 값이 명시적으로 지정된 경우에만 초기화된다. 그렇지 않으면 처음에는 결정되지 않은 값(대개 저장 공간에 우연히 존재하는 어떤 비트 패턴, 해당 자료형에 유효한 값을 나타내지 않을 수도 있음)을 갖는다. 프로그램이 초기화되지 않은 값에 접근하려고 시도하면 결과는 정의되지 않는다. 많은 현대적 컴파일러는 이 문제를 감지하고 경고하려 시도하지만, 거짓 양성과 거짓 음성 모두 발생할 수 있다.

힙 메모리 할당은 가능한 한 많이 재사용될 수 있도록 모든 프로그램에서의 실제 사용과 동기화되어야 한다. 예를 들어, 힙 메모리 할당에 대한 유일한 포인터가 범위를 벗어나거나 명시적으로 할당 해제되기 전에 그 값이 덮어씌워지면, 해당 메모리는 나중에 재사용하기 위해 회수될 수 없으며 본질적으로 프로그램에서 손실되는데, 이를 메모리 누수라고 한다. 반대로, 메모리가 해제된 후 나중에 참조될 수 있으며, 이는 예측할 수 없는 결과를 초래한다. 대개 실패 증상은 오류를 일으킨 코드와 무관한 프로그램 부분에서 나타나 진단을 어렵게 만든다. 이러한 문제들은 자동 쓰레기 수집 기능이 있는 언어들에서 완화된다.

 라이브러리 
C 프로그래밍 언어는 확장의 주요 방법으로 라이브러리를 사용한다. C에서 라이브러리는 단일 "아카이브" 파일 내에 포함된 함수들의 집합이다. 각 라이브러리는 일반적으로 헤더 파일을 가지며, 여기에는 프로그램에서 사용할 수 있는 라이브러리 내 함수들의 원형과 이러한 함수들과 함께 사용되는 특수 자료형 및 매크로 기호의 선언이 들어 있다. 프로그램이 라이브러리를 사용하려면 라이브러리의 헤더 파일을 포함해야 하며, 라이브러리는 프로그램과 연결되어야 한다. 많은 경우 이는 컴파일러 플래그(예: 수학 라이브러리를 연결하기 위한 -lm)를 요구한다.

가장 일반적인 C 라이브러리는 C 표준 라이브러리로, ISO 및 ANSI C 표준에 의해 지정되어 있으며 모든 C 구현체와 함께 제공된다(임베디드 시스템과 같이 제한된 환경을 대상으로 하는 구현체는 표준 라이브러리의 하위 집합만 제공할 수 있음). 이 라이브러리는 스트림 입출력, 메모리 할당, 산술, 문자열 및 시간 값을 지원한다. 여러 개의 분리된 표준 헤더(예: stdio.h)가 이들과 다른 표준 라이브러리 시설을 위한 인터페이스를 지정한다.

또 다른 일반적인 C 라이브러리 함수 집합은 유닉스 및 유닉스 계열 시스템을 대상으로 하는 애플리케이션에서 사용되는 것들로, 특히 커널에 대한 인터페이스를 제공하는 함수들이다. 이러한 함수들은 POSIX 및 단일 유닉스 규격과 같은 다양한 표준에 상세히 설명되어 있다.

많은 프로그램이 C로 작성되었기 때문에 매우 다양한 다른 라이브러리들을 사용할 수 있다. C 컴파일러가 효율적인 목적 코드를 생성하기 때문에 라이브러리는 종종 C로 작성된다. 프로그래머들은 루틴들이 자바, 펄, 파이썬과 같은 고수준 언어에서 사용될 수 있도록 라이브러리에 대한 인터페이스를 생성한다.

 파일 처리 및 스트림 
파일 입출력(I/O)은 C 언어 자체의 일부가 아니며, 대신 라이브러리(C 표준 라이브러리 등)와 관련 헤더 파일(예: stdio.h)에 의해 처리된다. 파일 처리는 일반적으로 스트림을 통해 작동하는 고수준 I/O를 통해 구현된다. 이러한 관점에서 스트림은 장치와 독립적인 데이터 흐름인 반면, 파일은 구체적인 장치이다. 고수준 I/O는 파일을 스트림에 연결함으로써 이루어진다. C 표준 라이브러리에서 버퍼(메모리 영역 또는 큐)는 데이터가 최종 목적지로 전송되기 전에 일시적으로 저장하기 위해 사용된다. 이는 하드 드라이브나 솔리드 스테이트 드라이브와 같이 느린 장치를 기다리는 시간을 줄여준다. 저수준 I/O 함수는 표준 C 라이브러리의 일부가 아니며, 대개 대부분의 임베디드 프로그래밍과 같이 어떠한 운영체제와도 무관한 프로그래밍인 "베어 메탈" 프로그래밍의 일부이다. 몇몇 예외를 제외하고, 구현체들은 저수준 I/O를 포함한다.

 언어 도구 

컴파일러가 제공하는 것보다 더 엄격하게 프로그래머가 정의되지 않은 동작이나 오류 가능성이 있는 식을 찾고 수정하는 것을 돕기 위해 여러 도구가 개발되었다.

린트와 같은 자동화된 소스 코드 검사 및 감사 도구가 존재한다. 프로그램을 처음 작성할 때 의심스러운 코드를 감지하기 위해 린트를 사용하는 것이 일반적인 관례이다. 프로그램이 린트를 통과하면 C 컴파일러를 사용하여 컴파일된다. 또한 많은 컴파일러는 구문적으로는 유효하지만 실제로는 오류일 가능성이 높은 구문에 대해 선택적으로 경고를 보낼 수 있다. MISRA C는 임베디드 시스템을 위해 개발된, 이러한 의심스러운 코드를 피하기 위한 사유 지침 집합이다.

또한 배열에 대한 경계 검사, 버퍼 오버플로 감지, 직렬화, 동적 메모리 추적 및 자동 쓰레기 수집과 같이 C의 표준 부분이 아닌 작업을 수행하기 위한 컴파일러, 라이브러리 및 운영체제 수준의 메커니즘도 있다.

Purify나 Valgrind와 같은 메모리 관리 검사 도구를 사용하거나, 특수한 버전의 메모리 할당 함수가 포함된 라이브러리와 연결하는 것은 메모리 사용에서의 런타임 오류를 발견하는 데 도움이 될 수 있다.

 용도 
C는 엔드 유저 및 시스템 수준 애플리케이션을 구현하는 데 널리 사용되어 왔다.

 시스템 프로그래밍에서의 사용 근거 
섬네일|C로 작성된 일부 소프트웨어
C는 운영체제와 임베디드 시스템 애플리케이션을 구현하는 시스템 프로그래밍에서 널리 사용된다. 이는 다음과 같은 몇 가지 이유 때문이다:
 C 언어는 포인터와 유형 변경을 통해 플랫폼 하드웨어와 메모리에 접근하는 것을 허용하므로, 시스템 특정 기능(예: 제어/상태 레지스터, 입출력 레지스터)을 C로 작성된 코드로 구성하고 사용할 수 있다. 즉, 실행 중인 플랫폼에 대한 최대한의 제어를 허용한다.
 컴파일에 의해 생성된 코드는 많은 시스템 기능을 요구하지 않으며, 일부 부팅 코드에서 직접적인 방식으로 호출될 수 있다. 즉, 실행이 간단하다.
 C 언어 문과 식은 대개 대상 프로세서의 명령어 시퀀스에 잘 매핑되므로, 결과적으로 시스템 자원에 대한 런타임 수요가 낮다. 즉, 실행이 빠르다.
 풍부한 연산자 집합을 통해 C 언어는 대상 CPU의 많은 기능을 사용할 수 있다. 특정 CPU에 더 특이한 명령어가 있는 경우, 해당 명령어를 활용하기 위해 아마도 내장 함수를 가진 언어 변종을 구성할 수 있다. 즉, 사실상 대상 CPU의 모든 기능을 사용할 수 있다.
 이 언어는 이진 데이터 블록 위에 구조체를 덧씌우는 것을 쉽게 하여 데이터를 이해하고 탐색하고 수정할 수 있게 한다. 즉, 데이터 구조와 파일 시스템까지 작성할 수 있다.
 이 언어는 정수 산술 및 논리를 위한 비트 조작을 포함한 풍부한 연산자 집합과 다양한 크기의 부동 소수점 수를 지원한다. 즉, 적절하게 구조화된 데이터를 효과적으로 처리할 수 있다.
 C는 적은 수의 문장만을 가진 상당히 작은 언어이며, 광범위한 대상 코드를 생성하는 기능이 너무 많지 않다. 즉, 이해하기 쉽다.
 C는 메모리 할당 및 해제에 대한 직접적인 제어권을 가져 메모리 처리 작업에 합리적인 효율성과 예측 가능한 타이밍을 제공하며, 산발적인 stop-the-world 쓰레기 수집 이벤트에 대한 걱정이 없다. 즉, 성능이 예측 가능하다.
 C는 일반적인  및 , 영역 기반 메모리 관리를 사용하는 더 정교한 메커니즘, 또는 DMA에 적합하거나 인터럽트 핸들러 내에서 사용하거나 가상 메모리 시스템과 통합된 OS 커널용 버전 등 다양한 메모리 할당 체계의 사용 및 구현을 허용한다.
 링커와 환경에 따라 C 코드는 어셈블리어로 작성된 라이브러리를 호출할 수도 있고 어셈블리어에서 호출될 수도 있다. 즉, 다른 저수준 코드와 잘 상호 운용된다.
 C와 그 호출 규약 및 링커 구조는 다른 고수준 언어와 함께 흔히 사용되며, C로의 호출과 C로부터의 호출이 모두 지원된다. 즉, 다른 고수준 코드와 잘 상호 운용된다.
 C는 라이브러리, 프레임워크, 오픈 소스 컴파일러, 디버거 및 유틸리티를 포함한 성숙하고 광범위한 생태계를 가지고 있으며 사실상의 표준이다. 드라이버가 이미 C로 존재하거나 유사한 CPU 아키텍처가 C 컴파일러의 백엔드로 존재할 가능성이 높으므로 다른 언어를 선택할 유인이 적다.

 게임 
컴퓨터 게임은 종종 여러 언어의 조합으로 제작된다. C는 특히 컴퓨터 플랫폼에서 최고의 성능을 얻으려는 게임들에서 중요한 역할을 해왔다. 1993년의 둠(Doom)이 그 예이다.

 월드 와이드 웹 
역사적으로 C는 웹 애플리케이션, 서버, 브라우저 사이의 정보를 위한 "게이트웨이"로서 공용 게이트웨이 인터페이스(CGI)를 사용하여 웹 개발에 때때로 사용되었다. C는 속도, 안정성, 그리고 거의 보편적인 가용성 때문에 인터프리트 언어보다 선택되었을 수 있다. 웹 개발이 C로 이루어지는 것은 더 이상 일반적이지 않으며, 다른 많은 웹 개발 언어들이 인기를 얻고 있다. C 기반 웹 개발이 계속되는 애플리케이션에는 라우터, IoT 기기 등의 HTTP 구성 페이지가 포함되지만, 여기서도 일부 프로젝트는 OpenWRT 내에서의 루아 사용과 같이 고수준 언어로 된 부분을 가지고 있다.

인기 있는 두 웹 서버인 아파치 HTTP 서버와 Nginx는 C로 작성되었다. C의 하드웨어에 밀착된 접근 방식은 이러한 고성능 소프트웨어 시스템의 구축을 가능하게 한다.

 중간 언어로서의 C 
C는 때때로 다른 언어의 구현체들에 의해 중간 언어로 사용되기도 한다. 이 접근 방식은 이식성이나 편의성을 위해 사용될 수 있다. C를 중간 언어로 사용함으로써 추가적인 기계 특정 코드 생성기가 필요하지 않게 된다. C는 생성된 코드의 컴파일을 지원하는 행 번호 전처리기 지시문 및 초기화 목록 끝의 선택적 잉여 콤마와 같은 몇 가지 기능을 가지고 있다. 그러나 C의 몇 가지 단점으로 인해 C--와 같이 중간 언어로 사용하기 위해 특별히 설계된 다른 C 계열 언어들이 개발되었다. 또한 현대의 주요 컴파일러인 GCC와 LLVM은 모두 C가 아닌 중간 표현을 특징으로 하며, 이러한 컴파일러들은 C를 포함한 많은 언어를 위한 프런트엔드를 지원한다.

 연산 집약적 라이브러리 
C는 하드웨어로부터의 추상화 계층이 얇고 오버헤드가 낮기 때문에 알고리즘과 데이터 구조의 효율적인 구현을 가능하게 하며, 이는 연산 집약적인 프로그램에 중요한 기준이다. 예를 들어, GNU 다중 정밀도 산술 라이브러리, GNU 사이언티픽 라이브러리, Mathematica, 매트랩은 전체적으로 또는 부분적으로 C로 작성되었다. 많은 언어들이 C로 된 라이브러리 함수 호출을 지원한다. 예를 들어, 파이썬 기반 프레임워크인 NumPy는 고성능 및 하드웨어 상호작용 측면을 위해 C를 사용한다.

 C로 작성된 다른 언어들 
C의 광범위한 가용성과 효율성의 결과로 다른 프로그래밍 언어의 컴파일러, 라이브러리 및 인터프리터가 종종 C로 구현된다. 예를 들어, 파이썬, 펄, 루비, PHP의 참조 구현은 C로 작성되었다.

 한계 
리치 자신도 그가 만든 언어의 한계에 대해 농담을 한 적이 있다:

C는 인기 있고 영향력 있으며 큰 성공을 거두었으나, 다음과 같은 단점이 있다:
 malloc 및 free를 사용한 표준 동적 메모리 처리는 실수하기 쉽다. 부적절한 사용은 메모리 누수와 허상 포인터를 초래할 수 있다.
 포인터 사용과 메모리의 직접적인 조작은 메모리 오염이 발생할 수 있음을 의미한다.
 자료형 검사가 존재하지만, 가변 인자 함수와 같은 일부 영역에는 적용되지 않으며 자료형 검사를 사소하게 혹은 무심코 우회할 수 있다. 정적으로 유형화되었음에도 불구하고 약하게 유형화되어 있다.
 컴파일러에 의해 생성된 코드에 런타임 검사가 거의 없기 때문에, 버퍼 오버런, 배열 경계 검사, 스택 오버플로, 메모리 소진으로부터 보호하고 경쟁 상태, 스레드 격리 등을 고려해야 하는 부담이 프로그래머에게 있다.
 포인터의 사용과 이에 대한 런타임 조작은 동일한 데이터에 접근하는 두 가지 방법(앨리어싱)을 가능하게 하며, 이는 컴파일 타임에 항상 결정 가능한 것은 아니다. 이는 포트란과 같은 다른 언어에서 사용할 수 있는 일부 최적화가 C에서는 불가능함을 의미한다. 이러한 이유로 때때로 포트란이 더 빠르다고 간주된다.
 일부 표준 라이브러리 함수(예: scanf 또는 )는 버퍼 오버플로를 초래할 수 있다.
 서로 다른 함수 호출 규약 및 ABI, 서로 다른 구조체 패킹 관례, 더 큰 정수 내의 서로 다른 바이트 순서(엔디언 포함)와 같이 생성된 코드의 저수준 변종 지원에 대한 표준화가 제한적이다. 많은 언어 구현에서 이러한 옵션 중 일부는 전처리기 지시문인 #pragma로 처리될 수 있으며, 일부는 __cdecl 호출 규약 사용과 같은 추가 키워드로 처리된다. 지시문과 옵션은 일관되게 지원되지 않는다.
 표준 라이브러리를 사용한 문자열 처리는 명시적인 메모리 관리가 필요하여 코드 집약적이다.
 이 언어는 객체 지향, 내성, 런타임 식 평가(자바스크립트의 eval 등), 쓰레기 수집 등을 직접 지원하지 않는다.
 언어 기능의 오용에 대한 방어 장치가 거의 없어 유지보수가 불가능한 코드가 만들어질 수 있다. 특히 C 전처리기는 이중 평가 이상의 곤란한 효과를 숨길 수 있다. 이러한 난해한 코드 작성 능력은 국제 난해한 C 코드 대회와 Underhanded C Contest와 같은 대회를 통해 기념되어 왔다.
 C는 예외 처리에 대한 표준 지원이 부족하며 오류 검사를 위해 반환 코드만 제공한다. setjmp 및 longjmp 표준 라이브러리 함수는 매크로를 통해 try-catch 메커니즘을 구현하는 데 사용되어 왔다. 또한 goto 문은 오류 처리를 위해 흔히 사용된다.

 C의 문제에 대한 완화책 

일부 목적을 위해 원치 않는 동작의 기회를 줄이기 위해 MISRA C나 CERT C와 같이 제한된 C 스타일이 채택되어 왔다. CWE와 같은 데이터베이스는 일반적인 시스템, 특히 C로 코딩된 시스템이 가질 수 있는 잠재적 취약점의 수와 완화 권장 사항을 집계하려 시도한다.

단점 중 일부를 완화할 수 있는 도구들이 있다. 현대의 C 컴파일러는 많은 잠재적 버그를 식별하는 데 도움이 되는 경고를 생성할 수 있는 검사 기능을 포함한다.

C의 포인터 사용은 ISA 확장인 CHERI나 Permission Overlay Extensions를 사용하여 덜 위험하게 만들 수 있다. 이러한 기술은 하드웨어 수준에서 포인터의 근본적인 성격을 경계 검사와 목적을 포함하도록 변경하여 버퍼 오버런 및 부적절한 힙 접근을 방지하는 데 도움을 줄 수 있다.

2020년대 초부터 리눅스 커널은 안전성을 개선하기 위한 구체적인 조치를 가진 언어인 러스트로 작성된 섹션들을 포함하고 있다.

 관련 언어 

섬네일|TIOBE index

C 이후에 개발된 많은 언어들이 C의 영향을 받았거나 C의 측면을 빌려왔다. 여기에는 C++, C#, C 셸, D, Go, Java, 자바스크립트, Julia, Limbo, LPC, 오브젝티브-C, 펄, PHP, 파이썬, Ruby, Rust, Swift, 베릴로그 및 시스템베릴로그가 포함된다. 어떤 이들은 가장 널리 퍼진 영향이 구문적인 것이라고 주장한다. 즉, 이러한 언어들이 C의 문장 및 식 구문을 사용하면서 C와는 다른(때로는 근본적으로 다른) 자료형 체계, 데이터 모델 및 대규모 프로그램 구조를 결합했다는 것이다.

스크립팅에도 사용될 수 있는 Ch 및 CINT를 포함하여 여러 C 또는 C에 가까운 인터프리터가 존재한다.

객체 지향 프로그래밍 언어가 인기를 얻었을 때, C++와 오브젝티브-C는 객체 지향 기능을 제공하는 C의 두 가지 서로 다른 확장이었다. 두 언어 모두 원래 소스 대 소스 컴파일러로 구현되었다. 소스 코드가 C로 번역된 다음 C 컴파일러로 컴파일되었다.

비야네 스트롭스트룹이 고안한 C++ 프로그래밍 언어(원래 이름은 "C with Classes")는 C와 유사한 구문으로 객체 지향 기능을 제공하기 위한 접근 방식이었다. C++는 더 강력한 유형화, 스코핑 및 객체 지향 프로그래밍에 유용한 기타 도구들을 추가하며 템플릿을 통해 제네릭 프로그래밍을 허용한다. C의 거의 초월집합인 C++는 현재 몇 가지 예외를 제외하고 C의 대부분을 지원한다.

오브젝티브-C는 원래 C 위에 덧씌워진 얇은 계층이었으며, 하이브리드 동적/정적 타이핑 패러다임을 사용하여 객체 지향 프로그래밍을 허용하는 C의 엄격한 초월집합으로 남아 있다. 오브젝티브-C는 C와 스몰토크 모두에서 구문을 유도했다. 전처리, 식, 함수 선언 및 함수 호출을 포함하는 구문은 C에서 상속되었으며, 객체 지향 기능에 대한 구문은 원래 스몰토크에서 가져왔다.

C++와 오브젝티브-C 외에도 Ch, Cilk 및 Unified Parallel C는 거의 C의 초월집합이다.

 같이 보기 

 B (프로그래밍 언어)
 C99
 C11
 C 언어의 문법
 C 언어 변수
 C 언어 포인터
 C++
 오브젝티브-C

 각주 

 내용주

 외부 링크 
  Ritchie, Dennis M. The Development of the C Language. History of Programming Languages-II. Second History of Programming Languages conference, Cambridge, Massachusetts, April, 1993. .
 Richard M. Stallman: Using and Porting the GNU Compiler Collection, Free Software Foundation, 
 Richard M. Stallman: Using Gcc: The Gnu Compiler Collection Reference, Free Software Foundation, 
 Brian J. Gough: An Introduction to GCC, Network Theory Ltd., 


분류:절차적 프로그래밍 언어
분류:ISO 표준
분류:미국의 발명품
분류:1972년 개발된 프로그래밍 언어
분류:C 프로그래밍 언어 계열
분류:시스템 프로그래밍 언어
분류:정적 타입 프로그래밍 언어
분류:ISO 표준 프로그래밍 언어
분류:고급 프로그래밍 언어