세월이 흘러도 세상은 여전히 C 프로그래밍에 의해 구동됩니다
게시 됨: 2022-03-11오늘날 존재하는 많은 C 프로젝트는 수십 년 전에 시작되었습니다.
UNIX 운영 체제의 개발은 1969년에 시작되었으며 그 코드는 1972년에 C로 다시 작성되었습니다. C 언어는 실제로 UNIX 커널 코드를 어셈블리에서 더 적은 코드 줄로 동일한 작업을 수행하는 상위 수준 언어로 옮기기 위해 만들어졌습니다. .
Oracle 데이터베이스 개발은 1977년에 시작되어 1983년에 어셈블리에서 C로 코드를 다시 작성했습니다. 이는 세계에서 가장 인기 있는 데이터베이스 중 하나가 되었습니다.
1985년에는 Windows 1.0이 출시되었습니다. Windows 소스 코드는 공개적으로 사용할 수 없지만 해당 커널은 대부분 C로 작성되었으며 일부는 어셈블리로 작성되었습니다. Linux 커널 개발은 1991년에 시작되었으며 C로도 작성되었습니다. 이듬해 GNU 라이선스로 출시되어 GNU 운영 체제의 일부로 사용되었습니다. GNU 운영 체제 자체는 C 및 Lisp 프로그래밍 언어를 사용하여 시작되었으므로 많은 구성 요소가 C로 작성되었습니다.
그러나 C 프로그래밍은 오늘날만큼 프로그래밍 언어가 많지 않았던 수십 년 전에 시작된 프로젝트에 국한되지 않습니다. 많은 C 프로젝트가 오늘날에도 여전히 시작되고 있습니다. 거기에는 몇 가지 좋은 이유가 있습니다.
C로 세상을 움직이는 방법은 무엇입니까?
고급 언어의 보급에도 불구하고 C는 계속해서 세계에 힘을 실어주고 있습니다. 다음은 수백만 명이 사용하고 C 언어로 프로그래밍된 시스템 중 일부입니다.
마이크로소프트 윈도우
Microsoft의 Windows 커널은 대부분 C로 개발되었으며 일부는 어셈블리 언어로 개발되었습니다. 수십 년 동안 시장 점유율의 약 90%를 차지하는 세계에서 가장 많이 사용되는 운영 체제는 C로 작성된 커널로 구동되었습니다.
리눅스
Linux도 대부분 C로 작성되었으며 일부는 어셈블리로 작성되었습니다. 세계에서 가장 강력한 슈퍼컴퓨터 500대 중 약 97%가 Linux 커널을 실행합니다. 또한 많은 개인용 컴퓨터에서 사용됩니다.
맥
OS X 커널이 대부분 C로 작성되었기 때문에 Mac 컴퓨터도 C로 구동됩니다. Windows 및 Linux 컴퓨터에서와 같이 Mac의 모든 프로그램과 드라이버는 C 기반 커널에서 실행됩니다.
이동하는
iOS, Android 및 Windows Phone 커널도 C로 작성되었습니다. 기존 Mac OS, Linux 및 Windows 커널을 모바일로 개조한 것일 뿐입니다. 따라서 매일 사용하는 스마트폰은 C 커널에서 실행됩니다.
데이터베이스
Oracle Database, MySQL, MS SQL Server 및 PostgreSQL을 포함하여 세계에서 가장 인기 있는 데이터베이스는 C로 코딩되어 있습니다(이 중 처음 세 개는 실제로 C와 C++로 모두 제공됨).
데이터베이스는 금융, 정부, 미디어, 엔터테인먼트, 통신, 건강, 교육, 소매, 소셜 네트워크, 웹 등 모든 종류의 시스템에서 사용됩니다.
3D 영화
3D 영화는 일반적으로 C 및 C++로 작성된 응용 프로그램으로 만들어집니다. 이러한 애플리케이션은 엄청난 양의 데이터를 처리하고 초당 많은 계산을 수행하기 때문에 매우 효율적이고 빨라야 합니다. 효율성이 높을수록 아티스트와 애니메이터가 영화 장면을 생성하는 데 걸리는 시간이 줄어들고 회사에서 더 많은 돈을 절약할 수 있습니다.
임베디드 시스템
어느 날 일어나서 쇼핑을 한다고 상상해 보십시오. 당신을 깨우는 알람 시계는 아마도 C로 프로그래밍되어 있을 것입니다. 그런 다음 전자레인지나 커피 메이커를 사용하여 아침 식사를 만듭니다. 또한 임베디드 시스템이므로 아마도 C로 프로그래밍되어 있을 것입니다. 아침을 먹으면서 TV나 라디오를 켭니다. 그것들도 C로 구동되는 임베디드 시스템입니다. 리모컨으로 차고 문을 열면 C로 프로그래밍된 임베디드 시스템도 사용하고 있는 것입니다.
그런 다음 차에 타십시오. 다음과 같은 기능이 있는 경우 C로도 프로그래밍됩니다.
- 자동 변속기
- 타이어 공기압 감지 시스템
- 센서(산소, 온도, 오일 레벨 등)
- 좌석 및 미러 설정용 메모리.
- 대시보드 디스플레이
- 안티 - 록 브레이크
- 자동 안정성 제어
- 크루즈 컨트롤
- 기후 제어
- 어린이용 자물쇠
- 열쇠가없는 항목
- 난 방석
- 에어백 제어
당신은 상점에 도착하고, 차를 주차하고, 탄산음료를 사러 자동판매기로 갑니다. 그들은 이 자판기를 프로그래밍하기 위해 어떤 언어를 사용했습니까? 아마 C. 그런 다음 상점에서 물건을 삽니다. 금전 등록기도 C로 프로그래밍되어 있습니다. 신용 카드로 지불할 때? 짐작하셨겠지만 신용 카드 리더기는 C로 프로그래밍되어 있을 가능성이 높습니다.
이러한 장치는 모두 임베디드 시스템입니다. 그들은 임베디드 장치에서 펌웨어라고도 하는 프로그램을 실행하는 마이크로컨트롤러/마이크로프로세서가 내부에 있는 소형 컴퓨터와 같습니다. 그 프로그램은 키 누름을 감지하고 그에 따라 작동하고 사용자에게 정보를 표시해야 합니다. 예를 들어, 알람 시계는 사용자와 상호 작용하여 사용자가 어떤 버튼을 누르고 있는지, 때로는 누르고 있는 시간을 감지하고 그에 따라 장치를 프로그래밍해야 하며 이 모든 작업은 사용자에게 관련 정보를 표시하는 것입니다. 예를 들어 자동차의 잠김 방지 브레이크 시스템은 타이어의 갑작스러운 잠김을 감지하고 짧은 시간 동안 브레이크에 가해지는 압력을 해제하고 잠금을 해제하여 제어되지 않은 미끄러짐을 방지할 수 있어야 합니다. 이러한 모든 계산은 프로그래밍된 임베디드 시스템에 의해 수행됩니다.
임베디드 시스템에 사용되는 프로그래밍 언어는 브랜드마다 다를 수 있지만 유연성, 효율성, 성능 및 하드웨어에 대한 근접성과 같은 언어의 기능으로 인해 가장 일반적으로 C 언어로 프로그래밍됩니다.
C 프로그래밍 언어가 여전히 사용되는 이유는 무엇입니까?
오늘날 개발자가 다양한 종류의 프로젝트에서 C보다 생산성을 높일 수 있는 많은 프로그래밍 언어가 있습니다. JSON, XML, UI, 웹 페이지, 클라이언트 요청, 데이터베이스 연결, 미디어 조작 등으로 작업을 단순화하는 훨씬 더 큰 내장 라이브러리를 제공하는 고급 언어가 있습니다.
그러나 그럼에도 불구하고 C 프로그래밍이 오랫동안 활성화될 것이라고 믿을 만한 충분한 이유가 있습니다.
프로그래밍 언어에서는 한 가지 크기가 모든 사람에게 적합하지 않습니다. 다음은 특정 애플리케이션에서 C가 타의 추종을 불허하고 거의 필수인 몇 가지 이유입니다.
휴대성 및 효율성
C는 거의 이식 가능한 어셈블리 언어입니다. 기존 프로세서 아키텍처에서 거의 보편적으로 사용 가능하면서도 가능한 한 기계에 가깝습니다. 거의 모든 기존 아키텍처에 대해 최소한 하나의 C 컴파일러가 있습니다. 그리고 요즘에는 최신 컴파일러에서 생성된 고도로 최적화된 바이너리 때문에 손으로 작성한 어셈블리로 출력을 개선하는 것이 쉬운 일이 아닙니다.
"다른 프로그래밍 언어의 컴파일러, 라이브러리 및 인터프리터는 종종 C로 구현"되는 이식성과 효율성입니다. Python, Ruby 및 PHP와 같은 해석 언어는 기본 구현을 C로 작성했습니다. 다른 언어가 기계와 통신하기 위해 컴파일러에서도 사용합니다. 예를 들어, C는 에펠과 포스의 기초가 되는 중간 언어입니다. 이것은 지원될 모든 아키텍처에 대해 기계어 코드를 생성하는 대신 해당 언어에 대한 컴파일러가 중간 C 코드를 생성하고 C 컴파일러가 기계어 코드 생성을 처리한다는 것을 의미합니다.
C는 또한 개발자 간의 의사 소통을 위한 공용어 가 되었습니다. Dropbox 엔지니어링 관리자이자 Cprogramming.com의 창시자인 Alex Allain은 다음과 같이 말합니다.
C는 대부분의 사람들이 편안하게 사용할 수 있는 방식으로 프로그래밍에서 일반적인 아이디어를 표현하는 훌륭한 언어입니다. 또한 C에서 사용되는 많은 원칙(예: 명령줄 매개변수에 대한
argc
및argv
, 루프 구성 및 변수 유형)은 학습하는 다른 많은 언어로 표시되므로 대화할 수 있습니다. 두 사람 모두에게 공통적인 방식으로 C를 모르더라도 사람들에게 알려야 합니다.
메모리 조작
임의의 메모리 주소 액세스 및 포인터 산술은 C를 시스템 프로그래밍 (운영 체제 및 임베디드 시스템)에 완벽하게 적합하게 만드는 중요한 기능입니다.
하드웨어/소프트웨어 경계에서 컴퓨터 시스템과 마이크로컨트롤러는 주변 장치와 I/O 핀을 메모리 주소에 매핑합니다. 시스템 응용 프로그램은 세계와 통신하기 위해 해당 사용자 지정 메모리 위치를 읽고 써야 합니다. 따라서 임의의 메모리 주소를 조작하는 C의 능력은 시스템 프로그래밍에 필수적입니다.
예를 들어, 메모리 주소 0x40008000의 바이트가 주소 0x40008001의 비트 번호 4가 설정될 때마다 범용 비동기 수신기/송신기 (또는 UART, 주변기기와 통신하기 위한 공통 하드웨어 구성 요소)에 의해 전송되도록 마이크로컨트롤러를 설계할 수 있습니다. 1로 설정하고 해당 비트를 설정하면 주변 장치에서 자동으로 설정 해제됩니다.
이것은 해당 UART를 통해 바이트를 보내는 C 함수에 대한 코드입니다.
#define UART_BYTE *(char *)0x40008000 #define UART_SEND *(volatile char *)0x40008001 |= 0x08 void send_uart(char byte) { UART_BYTE = byte; // write byte to 0x40008000 address UART_SEND; // set bit number 4 of address 0x40008001 }
함수의 첫 번째 줄은 다음과 같이 확장됩니다.
*(char *)0x40008000 = byte;
이 줄은 컴파일러에게 0x40008000
값을 char
에 대한 포인터로 해석한 다음 해당 포인터를 역참조(가 가리키는 값을 제공)하고(맨 왼쪽 *
연산자를 사용하여) 마지막으로 byte
값을 역참조된 포인터에 할당하도록 지시합니다. 즉, 변수 byte
의 값을 메모리 주소 0x40008000
에 씁니다.
다음 줄은 다음으로 확장됩니다.
*(volatile char *)0x40008001 |= 0x08;
이 줄에서 우리는 주소 0x40008001
에 있는 값과 0x08
값에 대해 비트 OR 연산을 수행하고(2진수로 00001000
, 즉, 비트 번호 4에서 1), 그 결과를 주소 0x40008001
에 다시 저장합니다. 즉, 주소 0x40008001에 있는 바이트의 비트 4를 설정합니다. 또한 주소 0x40008001
의 값이 volatile 임을 선언합니다. 이것은 컴파일러에게 이 값이 우리 코드 외부의 프로세스에 의해 수정될 수 있음을 알려줍니다. 따라서 컴파일러는 해당 주소에 쓴 후 해당 주소의 값에 대해 어떠한 가정도 하지 않습니다. (이 경우 이 비트는 소프트웨어로 설정한 직후 UART 하드웨어에 의해 설정 해제됩니다.) 이 정보는 컴파일러의 최적화 프로그램에 중요합니다. 예를 들어 값이 휘발성임을 지정하지 않고 for
루프 내에서 이 작업을 수행한 경우 컴파일러는 이 값이 설정된 후 변경되지 않는다고 가정하고 첫 번째 루프 이후에 명령 실행을 건너뛸 수 있습니다.

자원의 결정적 사용
시스템 프로그래밍이 의존할 수 없는 공통 언어 기능은 가비지 수집 또는 일부 임베디드 시스템에 대한 동적 할당입니다. 임베디드 애플리케이션은 시간과 메모리 리소스가 매우 제한적입니다. 가비지 수집기에 대한 비결정적 호출을 감당할 수 없는 실시간 시스템에 자주 사용됩니다. 그리고 메모리 부족으로 인해 동적 할당을 사용할 수 없는 경우 C 포인터가 허용하는 대로 사용자 지정 주소에 데이터를 배치하는 것과 같은 다른 메모리 관리 메커니즘을 갖는 것이 매우 중요합니다. 동적 할당 및 가비지 수집에 크게 의존하는 언어는 리소스가 제한된 시스템에 적합하지 않습니다.
코드 크기
C는 런타임이 매우 짧습니다. 그리고 코드의 메모리 공간은 대부분의 다른 언어보다 작습니다.
예를 들어 C++와 비교할 때 임베디드 장치로 가는 C 생성 바이너리는 유사한 C++ 코드로 생성된 바이너리 크기의 약 절반입니다. 그 주요 원인 중 하나는 예외 지원입니다.
예외는 C를 통해 C++에 의해 추가된 훌륭한 도구이며, 트리거되지 않고 현명하게 구현되지 않으면 실행 시간 오버헤드가 거의 없습니다(그러나 코드 크기를 늘리는 대가로).
C++의 예를 살펴보겠습니다.
// Class A declaration. Methods defined somewhere else; class A { public: A(); // Constructor ~A(); // Destructor (called when the object goes out of scope or is deleted) void myMethod(); // Just a method }; // Class B declaration. Methods defined somewhere else; class B { public: B(); // Constructor ~B(); // Destructor void myMethod(); // Just a method }; // Class C declaration. Methods defined somewhere else; class C { public: C(); // Constructor ~C(); // Destructor void myMethod(); // Just a method }; void myFunction() { A a; // Constructor aA() called. (Checkpoint 1) { B b; // Constructor bB() called. (Checkpoint 2) b.myMethod(); // (Checkpoint 3) } // b.~B() destructor called. (Checkpoint 4) { C c; // Constructor cC() called. (Checkpoint 5) c.myMethod(); // (Checkpoint 6) } // c.~C() destructor called. (Checkpoint 7) a.myMethod(); // (Checkpoint 8) } // a.~A() destructor called. (Checkpoint 9)
A
, B
및 C
클래스의 메소드는 다른 곳(예: 다른 파일)에서 정의됩니다. 따라서 컴파일러는 이를 분석할 수 없으며 예외가 발생하는지 알 수 없습니다. 따라서 생성자, 소멸자 또는 기타 메서드 호출에서 발생하는 예외를 처리할 준비를 해야 합니다. 소멸자는 throw해서는 안되지만(매우 나쁜 습관) 사용자는 어쨌든 throw할 수 있습니다. 또는 예외를 throw하는 일부 함수 또는 메서드(명시적 또는 암시적)를 호출하여 간접적으로 throw할 수 있습니다.
myFunction
의 호출 중 예외가 발생하는 경우 스택 해제 메커니즘은 이미 구성된 개체에 대한 모든 소멸자를 호출할 수 있어야 합니다. 스택 해제 메커니즘에 대한 한 가지 구현은 이 함수에서 마지막 호출의 반환 주소를 사용하여 예외를 트리거한 호출의 "체크포인트 번호"를 확인합니다(간단한 설명입니다). 다음과 유사한 해당 함수의 본문에서 예외가 throw되는 경우 스택 해제에 사용할 보조 자동 생성 함수(일종의 조회 테이블)를 사용하여 이를 수행합니다.
// Possible autogenerated function void autogeneratedStackUnwindingFor_myFunction(int checkpoint) { switch (checkpoint) { // case 1 and 9: do nothing; case 3: b.~B(); goto destroyA; // jumps to location of destroyA label case 6: c.~C(); // also goes to destroyA as that is the next line destroyA: // label case 2: case 4: case 5: case 7: case 8: a.~A(); } }
체크포인트 1과 9에서 예외가 발생하면 객체를 삭제할 필요가 없습니다. 체크포인트 3의 경우 b
와 a
는 파괴되어야 합니다. 체크포인트 6의 경우 c
와 a
는 반드시 파괴되어야 합니다. 모든 경우에 폐기 순서를 준수해야 합니다. 체크포인트 2, 4, 5, 7, 8의 경우 객체 a
만 파괴하면 됩니다.
이 보조 기능은 코드에 크기를 추가합니다. 이것은 C++가 C에 추가하는 공간 오버헤드의 일부입니다. 많은 임베디드 응용 프로그램이 이 추가 공간을 감당할 수 없습니다. 따라서 임베디드 시스템용 C++ 컴파일러에는 종종 예외를 비활성화하는 플래그가 있습니다. 표준 템플릿 라이브러리는 오류를 알리기 위해 예외에 크게 의존하기 때문에 C++에서 예외를 비활성화하는 것은 무료가 아닙니다. 이 수정된 체계를 예외 없이 사용하려면 C++ 개발자가 가능한 문제를 감지하거나 버그를 찾기 위해 더 많은 교육이 필요합니다.
그리고 우리는 "사용하지 않는 것에 대해서는 비용을 지불하지 않는다"는 원칙을 가진 언어인 C++에 대해 이야기하고 있습니다. 이진 크기의 이러한 증가는 매우 유용하지만 임베디드 시스템에서는 제공할 수 없는 다른 기능으로 추가 오버헤드를 추가하는 다른 언어의 경우 더 악화됩니다. C는 이러한 추가 기능의 사용을 제공하지 않지만 다른 언어보다 훨씬 더 컴팩트한 코드 공간을 허용합니다.
C를 배워야 하는 이유
C는 배우기 어려운 언어가 아니므로 배우면 얻을 수 있는 모든 이점이 매우 저렴합니다. 그 혜택 중 일부를 살펴보겠습니다.
링구아 프랑카
이미 언급했듯이 C는 개발자를 위한 공용어 입니다. 책이나 인터넷에 있는 새로운 알고리즘의 많은 구현은 저자에 의해 처음(또는 유일한) C로 제공됩니다. 이것은 구현을 위한 최대한의 이식성을 제공합니다. 나는 프로그래머가 C의 아주 기본적인 개념을 모르기 때문에 다른 프로그래밍 언어로 C 알고리즘을 다시 작성하기 위해 인터넷에서 고군분투하는 것을 보았습니다.
C는 오래되고 널리 퍼진 언어이므로 웹에서 C로 작성된 모든 종류의 알고리즘을 찾을 수 있습니다. 따라서 이 언어를 알면 매우 유익할 것입니다.
기계 이해(Think in C)
코드의 특정 부분의 동작이나 다른 언어의 특정 기능에 대해 동료와 논의할 때 결국 "C로 대화"하게 됩니다. 이 부분이 개체에 대한 "포인터"를 전달하거나 전체 개체를 복사합니까? 여기서 "캐스트"가 일어날 수 있습니까? 등등.
우리는 고급 언어의 코드 부분의 동작을 분석할 때 코드 부분이 실행하는 어셈블리 명령에 대해 거의 논의(또는 생각)하지 않습니다. 그 대신, 기계가 무엇을 하는지 논의할 때 우리는 C로 꽤 명확하게 말하고(또는 생각합니다).
게다가, 멈추고 자신이 하고 있는 일에 대해 그렇게 생각할 수 없다면 (마술처럼) 일이 어떻게 수행되는지에 대한 일종의 미신으로 프로그래밍을 끝낼 수 있습니다.
많은 흥미로운 C 프로젝트 작업
큰 데이터베이스 서버 또는 운영 체제 커널에서 개인 만족과 재미를 위해 집에서 할 수 있는 작은 임베디드 응용 프로그램에 이르기까지 많은 흥미로운 프로젝트가 C로 수행됩니다. 한 가지 이유로 좋아할 수 있는 일을 중단할 이유가 없습니다. C와 같이 오래되고 작지만 강력하고 입증된 프로그래밍 언어를 모른다는 것입니다.
결론
C 프로그래밍 언어에는 만료일이 없는 것 같습니다. 하드웨어에 가깝고, 뛰어난 이식성과 리소스의 결정적인 사용으로 인해 운영 체제 커널 및 임베디드 소프트웨어와 같은 저수준 개발에 이상적입니다. 다용성, 효율성 및 우수한 성능으로 인해 데이터베이스 또는 3D 애니메이션과 같은 복잡한 데이터 조작 소프트웨어에 탁월한 선택이 됩니다. 오늘날 많은 프로그래밍 언어가 의도한 용도에 대해 C보다 낫다는 사실이 모든 영역에서 C를 능가한다는 의미는 아닙니다. 성능이 우선인 경우 C는 여전히 타의 추종을 불허합니다.
세상은 C 전원 장치에서 실행되고 있습니다. 우리는 인식 여부에 관계없이 이러한 장치를 매일 사용합니다. C는 과거, 현재, 그리고 우리가 볼 수 있는 한 소프트웨어의 많은 영역에서 여전히 미래입니다.