| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | |
| 7 | 8 | 9 | 10 | 11 | 12 | 13 |
| 14 | 15 | 16 | 17 | 18 | 19 | 20 |
| 21 | 22 | 23 | 24 | 25 | 26 | 27 |
| 28 | 29 | 30 |
- language
- P2P
- 시스템 소프트웨어 개발자
- 백엔드 개발
- OCI Registry
- 기술 탐방
- Container Image
- LLVM IR
- redis
- Harbor
- CSS
- 직무 탐방
- JavaScript
- SW개발
- frontend
- statefulset
- IT
- UI UX 개발자
- 소프트웨어 엔지니어링
- CI/CD
- Windows Driver
- kubernetes
- helm
- 프로그래밍 언어
- 프론트엔드 개발
- valkey
- API 개발자
- 직무탐방
- docker
- FAILOVER
- Today
- Total
방구석 IT
[Language] C, C++, C# 본문
C, C++, C#란?
C, C++, C#은 이름과 문법 일부를 공유하지만 같은 언어군으로 뭉뚱그릴 대상은 아니다. C는 운영체제, 펌웨어, 런타임, 데이터베이스 엔진처럼 하드웨어와 가까운 계층을 작게 제어하기 위해 쓰이는 시스템 프로그래밍 언어이다. C++은 C의 저수준 표현력을 바탕으로 클래스, 템플릿, 예외, RAII, 표준 라이브러리 같은 추상화 장치를 더한 다중 패러다임 언어이다. C#은 .NET 런타임 위에서 실행되는 관리형 언어로, 타입 안전성, 생산성, 라이브러리 생태계, 운영 도구와 함께 쓰이도록 설계된 언어이다.
세 언어의 차이는 문법보다 책임의 배치에서 더 크게 드러난다. C는 개발자가 메모리 배치, 포인터, ABI, 컴파일러 옵션, 플랫폼 API를 직접 다루는 방식이다. C++은 같은 수준의 제어를 유지하면서 객체 수명, 타입 추상화, 제네릭 프로그래밍으로 복잡도를 구조화한다. C#은 CLR, GC, 메타데이터, JIT 또는 AOT, .NET 라이브러리와 결합해 많은 실행 세부사항을 런타임과 도구가 맡게 한다.
C는 제어를, C++은 비용을 예측할 수 있는 추상화를, C#은 관리형 런타임 기반 생산성을 중심 가치로 둔다. 이름은 비슷하지만 실무 선택 기준은 분명히 다르다.
관계와 역사
C는 1969년부터 1973년 사이 Unix 초기 개발과 함께 형성되었고, Dennis Ritchie의 회고는 C가 초기 Unix를 위한 시스템 구현 언어로 만들어졌다고 설명한다. 이후 ANSI와 ISO 표준화를 거치며 다양한 시스템으로 확산되었다. ISO/IEC 9899:2024는 C 프로그램의 형태와 해석을 정의하고, 여러 데이터 처리 시스템 사이의 이식성을 촉진하는 표준이다.
C++은 C를 대체하기 위해 단순히 문법을 덧붙인 언어가 아니라, C에 더 강한 타입 검사, 데이터 추상화, 객체지향 프로그래밍, 제네릭 프로그래밍을 결합하려는 시도에서 시작되었다. Bjarne Stroustrup은 1979년에 C++이 된 작업을 시작했고 초기 이름은 C with Classes였다고 설명한다. ISO/IEC 14882:2024도 C++이 C에 기반하지만 추가 데이터 타입, 클래스, 템플릿, 예외, 네임스페이스, 연산자 오버로딩, 표준 라이브러리 시설을 제공한다고 명시한다.
C#은 C나 C++의 표준화 계보 안에 있는 언어가 아니다. C# 언어 사양은 C#이 Microsoft 안에서 개발되었고 Anders Hejlsberg, Scott Wiltamuth, Peter Golde가 주요 발명자였으며, 첫 널리 배포된 구현이 2000년 7월 .NET Framework initiative의 일부로 나왔다고 설명한다. 따라서 C#은 C 계열 표기법을 빌려왔지만 실행 모델과 생태계는 .NET 중심으로 독립되어 있다.
C, C++, C#은 직선형 후계 관계가 아니다. C는 Unix와 시스템 구현에서 출발했고, C++은 C 기반 고성능 추상화를 목표로 성장했으며, C#은 .NET 관리형 플랫폼을 전제로 나온 별도 언어이다.
컴파일과 실행 모델
C와 C++은 일반적으로 전처리, 컴파일, 어셈블, 링크를 거쳐 네이티브 실행 파일이나 라이브러리를 만든다. C 표준은 프로그램이 데이터 처리 시스템에서 어떻게 변환되고 호출되는지까지 지정하지 않으므로, 실제 결과물은 컴파일러, 링커, 운영체제, ABI, 표준 라이브러리 구현에 영향을 받는다. 같은 C 소스라도 임베디드 bare-metal, Linux user space, Windows DLL, WebAssembly target에서는 빌드와 실행 조건이 달라진다.
C++도 네이티브 빌드가 일반적이지만, 이름 장식, 예외 처리, RTTI, 표준 라이브러리, allocator, 템플릿 인스턴스화, shared library 경계가 운영 리스크가 된다. 특히 C++ ABI는 플랫폼과 컴파일러마다 달라질 수 있다. Microsoft의 .NET native interoperability 문서는 C가 .NET interop의 일반적 목표 언어로 권장되는 반면, C++은 주요 컴파일러 구현 사이에 일관된 ABI가 없어 직접 대상으로 삼기 어렵다고 설명한다.
C#은 보통 소스가 IL로 컴파일되고, CLR이 IL을 JIT로 네이티브 코드로 바꿔 실행한다. 이 과정에서 메타데이터, 타입 로딩, GC, exception, reflection, profiling 같은 런타임 서비스가 함께 동작한다. 단, 최신 .NET은 Native AOT 배포도 지원한다. Native AOT는 publish 시점에 IL을 네이티브 코드로 앞서 컴파일해 JIT 없이 실행할 수 있지만, 동적 로딩과 runtime code generation 같은 기능에 제한이 따른다.
C와 C++의 실행 모델은 빌드 산출물과 플랫폼 ABI를 중심으로 이해해야 한다. C#의 실행 모델은 IL, CLR, JIT 또는 AOT, 배포 방식까지 함께 이해해야 한다.
메모리와 타입 모델
C는 정적 타입 언어이지만 메모리 모델은 개발자에게 많은 권한을 준다. 포인터 산술, 배열, 구조체, union, 수동 할당, 함수 포인터를 통해 메모리 표현과 실행 경계를 직접 다룰 수 있다. 이 힘은 시스템 코드에 유리하지만, 객체 수명 종료 후 접근, 범위를 벗어난 배열 접근, 잘못된 포인터 변환, 데이터 레이스 같은 undefined behavior와 보안 취약점으로 이어질 수 있다. SEI CERT C Coding Standard는 C 표준 Annex J.2의 undefined behavior를 보안 코딩 규칙과 연결해 정리한다.
C++은 C보다 더 강한 타입 추상화와 수명 제어를 제공한다. RAII는 파일, 락, 소켓, 힙 메모리 같은 자원을 객체 수명에 묶어 예외나 조기 반환에서도 정리되게 하는 기법이다. C++ Core Guidelines는 resource handle과 RAII를 사용해 자원을 자동 관리하라고 권장한다. 그러나 C++도 raw pointer, dangling reference, iterator invalidation, data race, signed overflow, ODR 위반, ABI 경계 문제를 잘못 다루면 undefined behavior와 장애가 발생한다.
C#은 value type과 reference type을 구분하고, 모든 타입이 직접 또는 간접적으로 object에서 파생되는 통합 타입 시스템을 가진다. 일반 C# 코드는 포인터를 직접 다루지 않으며, unsafe code에서만 pointer type을 사용할 수 있다. GC는 관리형 객체의 메모리 회수를 담당하지만, 파일 핸들, 네이티브 메모리, 소켓 같은 unmanaged resource는 Dispose, safe handle, finalizer 정책으로 명시적 정리가 필요하다. GC는 메모리 누수 일부를 줄이지만 자원 수명 설계를 없애지는 않는다.
C는 메모리를 직접 다루는 자유가 크고, C++은 객체 수명과 타입 추상화로 그 자유를 구조화한다. C#은 관리형 메모리를 기본으로 하지만 unmanaged resource와 interop 경계에서는 여전히 명시적 수명 관리가 필요하다.
추상화와 생태계
C의 추상화는 작고 명시적이다. 구조체와 함수, 헤더, 전처리기, 컴파일 단위, 라이브러리 API를 조합해 시스템을 만든다. 표준 라이브러리는 비교적 작기 때문에 실제 프로젝트에서는 libc, POSIX, Win32, RTOS SDK, vendor HAL, compiler intrinsic, sanitizer, 정적 분석 도구가 함께 생태계를 이룬다. C 프로젝트의 생산성은 언어 기능보다 빌드 시스템, 플랫폼 문서, ABI 안정성, 테스트 장비, 코딩 규칙에서 많이 결정된다.
C++의 핵심은 비용을 숨기는 것이 아니라 비용을 표현 가능한 추상화로 바꾸는 데 있다. 표준 라이브러리의 string, vector, smart pointer, algorithm, thread, chrono, filesystem 같은 구성 요소는 수명 관리와 타입 안전성을 높인다. 템플릿, concept, constexpr, move semantics는 런타임 비용을 줄이면서 재사용 가능한 코드를 만들 수 있게 한다. 반대로 언어 기능이 많고 빌드 시간이 길며, 컴파일러 버전과 표준 라이브러리 차이가 장기 유지보수 비용이 된다.
C#은 언어와 .NET 플랫폼이 거의 함께 소비된다. .NET SDK, NuGet, ASP.NET Core, Entity Framework Core, LINQ, async/await, source generator, OpenTelemetry 연동, Visual Studio와 VS Code 도구가 개발 경험을 구성한다. C# generics는 런타임 타입 정보를 유지하고 type erasure가 없으며, generic collection은 컴파일 시점 타입 검사를 통해 런타임 cast 위험을 줄인다. 이런 생태계는 웹 API, 업무 시스템, 클라우드 서비스, 데스크톱, 게임 스크립팅에서 생산성을 높인다.
C는 작은 언어와 플랫폼 API의 조합, C++은 zero-overhead 지향 추상화와 표준 라이브러리, C#은 .NET 도구 체계와 패키지 생태계가 실무 생산성을 만든다.
상호운용
C는 언어 자체보다 ABI 관점에서 강한 상호운용성을 가진다. 많은 운영체제 API, 플러그인 인터페이스, 런타임 확장, FFI 계층이 C 함수와 구조체를 공통 경계로 사용한다. .NET native interop 문서도 C가 .NET 지원 플랫폼 전반에서 안정적인 ABI를 대표하며, 대부분의 interop 시나리오에서 권장 목표 언어라고 설명한다. 이 때문에 C 라이브러리는 Python, Java, C#, Rust, Go 같은 언어에서 호출되는 공통 기반이 되기 쉽다.
C++은 C와 가까워 보이지만 ABI와 객체 모델 때문에 경계 설계가 더 어렵다. 클래스 layout, name mangling, exception, RTTI, 표준 라이브러리 타입은 컴파일러와 빌드 옵션에 의존한다. 그래서 장기적으로 공개해야 하는 라이브러리 경계는 C API를 노출하거나, 같은 컴파일러와 표준 라이브러리 버전을 고정하거나, COM 같은 안정된 ABI를 사용하는 방식으로 설계한다.
C#은 P/Invoke, System.Runtime.InteropServices, COM interop, C++/CLI 같은 경로로 unmanaged code와 연결할 수 있다. 그러나 interop은 단순 호출 문법이 아니라 marshaling, 문자열 인코딩, 구조체 packing, callback lifetime, exception boundary, thread affinity, ownership 정책을 함께 맞추는 작업이다. 특히 네이티브 코드가 CLR 밖에서 실행되면 CLR의 관리와 보안 경계를 우회할 수 있으므로 검증과 격리가 필요하다.
상호운용의 기본 경계는 대개 C ABI이다. C++은 내부 구현 언어로 강하지만 공개 ABI는 신중히 설계해야 하며, C#은 런타임 경계를 넘을 때 marshaling과 소유권을 명시해야 한다.
선택 기준과 트레이드오프
- C를 고르는 경우: 커널, bootloader, RTOS, device driver, embedded firmware, libc, interpreter runtime, database storage engine처럼 런타임 의존성을 최소화하고 메모리 배치와 ABI를 직접 통제해야 하는 영역에 적합하다.
- C++을 고르는 경우: game engine, browser, real-time graphics, high-frequency trading, robotics, simulation, high-performance server처럼 성능과 복잡한 도메인 모델을 동시에 다뤄야 하는 영역에 적합하다.
- C#을 고르는 경우: ASP.NET Core 웹 API, enterprise backend, Windows와 cross-platform desktop, cloud service, internal tool, Unity game scripting처럼 빠른 개발, 풍부한 라이브러리, 관측성, 배포 도구가 중요한 영역에 적합하다.
- 팀 기준: 언어 자체보다 팀의 디버깅 능력, 빌드 체계, 배포 환경, 장애 대응 경험, 보안 요구, 장기 유지보수 인력 수급을 함께 평가해야 한다.
- 성능 기준: 평균 벤치마크보다 tail latency, startup time, memory footprint, allocation rate, cache locality, warm-up, IO wait, FFI 비용을 기준으로 비교해야 한다.
트레이드오프는 명확하다. C는 작고 이식 가능한 경계를 만들기 쉽지만 안전 장치가 적다. C++은 강한 추상화를 성능 손실 없이 만들 수 있지만 언어와 빌드 복잡도가 높다. C#은 생산성과 운영 도구가 강하지만 런타임, GC, 패키지, 배포 모델의 제약을 이해해야 한다.
언어 선택은 빠른 언어를 고르는 문제가 아니라 책임을 어디에 둘지 정하는 문제이다. 제어가 필요하면 C, 제어 가능한 추상화가 필요하면 C++, 관리형 플랫폼 생산성이 필요하면 C#이 자연스럽다.
보안과 운영 주의점
C와 C++ 프로젝트는 undefined behavior를 단순 버그가 아니라 보안과 운영 리스크로 다뤄야 한다. 범위 밖 접근, use-after-free, double free, integer overflow, null dereference, data race는 테스트 환경에서 바로 드러나지 않더라도 최적화, 입력 데이터, 스레드 스케줄링, 컴파일러 변경으로 장애가 될 수 있다. 실무에서는 warning-as-error, static analyzer, ASan, UBSan, TSan, fuzzing, code review, secure coding rule, hardening flag, dependency CVE 추적을 함께 운영해야 한다.
C++은 RAII와 smart pointer를 사용해 많은 자원 누수를 줄일 수 있지만, 순환 참조, shared ownership 남용, 예외 정책 불일치, allocator 경계, static initialization order, plugin ABI, 컴파일러 업그레이드가 별도 리스크가 된다. 대규모 C++ 팀은 스타일 가이드, formatter, clang-tidy, sanitizer, ABI policy, feature allowlist를 통해 언어 사용 범위를 관리한다.
C#은 메모리 안전성이 상대적으로 높지만 운영 문제가 사라지지는 않는다. allocation 폭증, GC pause, large object heap, thread pool starvation, async deadlock, sync-over-async, connection pool exhaustion, serialization compatibility, NuGet supply chain, runtime patch level, container memory limit이 장애 원인이 될 수 있다. Native AOT나 trimming을 사용하면 startup과 메모리 측면의 이점이 있지만 reflection, dynamic loading, runtime code generation 제약을 사전에 점검해야 한다.
C와 C++은 메모리와 UB를 운영 리스크로 관리해야 한다. C#은 GC와 런타임 덕분에 일부 위험을 줄이지만, allocation, async, interop, 패키지, 배포 모델을 관찰하고 통제해야 한다.
사례
- Linux kernel과 C: Linux Kernel Documentation은 Linux kernel이 C로 작성되며 일반적으로 gcc의
-std=gnu11, 즉 ISO C11의 GNU dialect로 컴파일된다고 설명한다. 커널처럼 하드웨어, 컴파일러 확장, ABI, 플랫폼별 빌드가 얽힌 영역에서 C가 계속 쓰이는 사례이다. 출처: Linux Kernel Documentation, Programming Language이다. - SQLite와 C: SQLite 공식 문서는 SQLite가 작은 고신뢰 SQL database engine을 구현한 C-language library라고 설명하고, canonical source와 amalgamation이 C source file로 구성된다고 정리한다. 배포가 쉽고 다양한 언어에서 호출되는 임베디드 라이브러리 경계로 C가 강한 사례이다. 출처: SQLite Home Page, SQLite, The Amalgamation Versus Canonical Sources이다.
- ISO C++ 표준화와 C++: ISO/IEC 14882:2024는 C++ 구현 요구사항을 정의하며, C++이 C에 기반하지만 클래스, 템플릿, 예외, 네임스페이스, 연산자 오버로딩, 추가 라이브러리 시설을 제공한다고 설명한다. C++이 단순 확장이 아니라 독립 표준 언어로 관리되는 사례이다. 출처: ISO/IEC 14882:2024, Programming languages - C++이다.
- Unreal Engine과 C++: Epic의 Unreal Engine 문서는 C++ programmer가 엔진 프레임워크, reflection system, gameplay class, editor 통합을 활용해 게임 로직을 만들 수 있다고 안내한다. 고성능 엔진 내부와 도메인 친화적 추상화가 결합된 C++ 생태계 사례이다. 출처: Epic Developer Community, Programming with C++ in Unreal Engine이다.
- Chromium과 대규모 C++ 운영: Chromium C++ style guide는 Google C++ Style Guide를 따르되 Chromium codebase의 예외를 두고, 현재 Chromium style이 C++23을 목표로 한다고 설명한다. 큰 C++ 프로젝트에서는 언어 지식뿐 아니라 formatter, style guide, feature policy가 운영 품질을 좌우한다는 사례이다. 출처: Chromium C++ style guide이다.
- ASP.NET Core와 C#: Microsoft Learn은 ASP.NET Core를 .NET 기반의 cross-platform, high-performance, open-source web framework로 설명하고, Kestrel, dependency injection, logging, tracing, runtime metrics, authentication, authorization, data protection 같은 기능을 핵심 요소로 제시한다. C#이 언어와 플랫폼 운영 도구를 함께 제공하는 사례이다. 출처: Microsoft Learn, Overview of ASP.NET Core이다.
- Unity와 C# 스크립팅: Unity Manual은 Unity가 C# programming language로 scripting을 지원하고, script component를 GameObject에 붙여 동작을 구현한다고 설명한다. C#이 엔터프라이즈 백엔드뿐 아니라 게임 제작의 생산성 높은 스크립팅 계층으로도 쓰이는 사례이다. 출처: Unity Manual, Introduction to scripting이다.
실제 사례를 보면 C는 작고 안정적인 시스템 경계, C++은 고성능 대규모 코드베이스, C#은 .NET과 엔진 생태계 위의 생산성에서 강점을 보인다.
'실무 탐방 > 기술 탐방 및 소개' 카테고리의 다른 글
| [Language] 절차지향과 객체지향 (0) | 2026.06.02 |
|---|---|
| [Language] 컴퓨팅 언어 (0) | 2026.06.01 |