C와 C++의 호환성

C와 C++ 프로그래밍 언어는 밀접하게 관련되어 있지만 많은 중요한 차이점이 있다. C++는 초기 표준화 이전의 C를 포크하여 시작되었으며, 당시 C 컴파일러와 대부분 소스 및 링크 호환이 되도록 설계되었다. 이 때문에 두 언어의 개발 도구(예: IDE 및 컴파일러)는 종종 단일 제품으로 통합되며, 프로그래머는 C 또는 C++를 소스 언어로 지정할 수 있다.

그러나 C는 C++의 부분집합이 아니며, 사소하지 않은 C 프로그램은 수정 없이 C++ 코드로 컴파일되지 않는다. 마찬가지로 C++는 C에서 사용할 수 없는 많은 기능을 도입하며, 실제로 C++로 작성된 거의 모든 코드는 C 코드에 적합하지 않다. 그러나 이 문서에서는 C 코드에 적합한 것이 C++ 코드에서는 잘못된 형식으로 간주되거나, 두 언어 모두에서 적합/잘못된 형식으로 간주되지만 C와 C++에서 다르게 동작하는 차이점에 초점을 맞춘다.

C++의 창시자인 비야네 스트롭스트룹은 두 언어 간의 상호 운용성을 극대화하기 위해 C와 C++ 간의 비호환성을 가능한 한 줄여야 한다고 제안했다. 다른 이들은 C와 C++가 서로 다른 언어이므로 호환성은 유용하지만 필수적인 것은 아니라고 주장한다. 이들은 비호환성을 줄이려는 노력이 각 언어를 독립적으로 개선하려는 시도를 방해해서는 안 된다고 말한다. 1999년 C 표준 (C99)의 공식 근거는 C와 C++ 간의 "가장 큰 공통 부분집합을 유지하는 원칙을 지지" "동시에 둘 사이의 구분을 유지하고 개별적으로 발전할 수 있도록 허용한다"고 명시하며, 저자들은 "C++가 크고 야심 찬 언어가 되도록 만족한다"고 밝혔다.

C99의 여러 추가 기능은 현재 C++ 표준에서 지원되지 않거나 C++ 기능과 충돌한다. 예를 들어 가변길이배열, 기본 복소수 타입 및 `[[restrict]]` 타입 한정자가 있다. 반면 C99는 `//` 주석 및 혼합 선언과 코드와 같은 C++ 기능을 통합하여 C89에 비해 다른 비호환성을 일부 줄였다.

## C에서는 유효하지만 C++에서는 유효하지 않은 구성
C++는 C보다 엄격한 타이핑 규칙(정적 타입 시스템의 암시적 위반 없음)과 초기화 요구 사항(범위 내 변수가 초기화되지 않도록 컴파일 타임에 강제)을 적용하므로 일부 유효한 C 코드는 C++에서는 유효하지 않다. 이에 대한 근거는 ISO C++ 표준의 Annex C.1에 제공된다.

C99 및 C11은 복소수, 가변 길이 배열(복소수 및 가변 길이 배열은 C11에서 선택적 확장으로 지정됨), 가변 길이 배열 멤버, restrict 키워드, 배열 매개변수 한정자 및 복합 리터럴과 같이 C++20 기준으로 표준 C++에 통합되지 않은 몇 가지 추가 기능을 C에 추가했다.

C++는 새로운 기능을 지원하기 위해 수많은 추가 키워드를 추가했다. 이로 인해 해당 키워드를 식별자로 사용하는 C 코드는 C++에서 유효하지 않게 된다. 예를 들어, 다음 스니펫은 `template`이라는 연결 리스트 구조를 정의하며, `new`와 `class` 필드를 가진다. 이는 유효한 C 코드이지만, `template`, `new` 및 `class` 키워드가 예약되어 있으므로 C++ 컴파일러에 의해 거부된다.


```c
struct template {
    int new;
    struct template* class;
};
```


## C와 C++에서 다르게 동작하는 구성 요소
C와 C++ 모두에서 유효하지만 두 언어에서 다른 결과를 생성하는 몇 가지 구문 구성 요소가 있다.

* `'a'`와 같은 문자 리터럴은 C에서는 `int` 타입이고 C++에서는 `char` 타입이다. 이는 `sizeof 'a'`가 일반적으로 두 언어에서 다른 결과를 반환한다는 것을 의미한다. C++에서는 `1`이 되는 반면, C에서는 `sizeof(int)`가 된다. 이러한 타입 차이의 또 다른 결과로, C에서 `'a'`는 `char`가 부호 있는 타입이든 부호 없는 타입이든 관계없이 항상 부호 있는 표현식이 되는 반면, C++에서는 컴파일러 구현에 따라 달라진다.
* C++는 네임스페이스 범위의 `const` 변수에 대해 명시적으로 `[[extern]]`으로 선언되지 않는 한 내부 연결을 할당한다. 이는 모든 파일 범위 엔티티에 대해 `extern`이 기본값인 C와는 다르다. 실제로 이는 동일한 C 및 C++ 코드 간에 암묵적인 의미론적 변화를 초래하지 않고 컴파일 타임 또는 링크 오류를 초래한다.
* C에서 인라인 함수를 사용하려면 하나의 번역 단위에서 extern 키워드를 사용하여 함수의 프로토타입 선언을 수동으로 추가하여 비인라인 버전이 링크되도록 해야 하지만, C++에서는 이를 자동으로 처리한다. 더 자세히 설명하자면, C는 <code>inline</code> 함수의 두 가지 정의를 구별한다. 일반 외부 정의(`extern`이 명시적으로 사용되는 경우)와 인라인 정의이다. 반면 C++는 인라인 함수에 대해 인라인 정의만 제공한다. C에서 인라인 정의는 내부(즉, 정적) 정의와 유사하여, 다른 번역 단위에 있는 동일한 함수의 하나의 외부 정의 및 여러 내부 및 인라인 정의와 함께 동일한 프로그램에 공존할 수 있으며, 이들 모두는 다를 수 있다. 이는 함수의 연결과는 별개의 고려 사항이지만 독립적인 것은 아니다. C 컴파일러는 동일한 함수의 인라인 및 외부 정의가 모두 보이는 경우 둘 중 하나를 선택할 수 있는 재량권이 부여된다. 그러나 C++는 외부 연결을 가진 함수가 어떤 번역 단위에서 `inline`으로 선언되면, 해당 함수가 사용되는 모든 번역 단위에서 그렇게 선언(따라서 정의)되어야 하며, 해당 함수의 모든 정의가 ODR을 따라 동일해야 한다고 요구한다. 정적 인라인 함수는 C와 C++에서 동일하게 동작한다.
* C(C99부터)와 C++는 모두 `bool` 불리언 타입을 가지며, 상수 `true`와 `false`가 있지만 다르게 정의된다.
** C++에서는 `bool`이 내장 타입이자 예약어이다.
** C99에서는 새로운 키워드인 `_Bool`이 새로운 내장 불리언 타입으로 도입되었다. 헤더 `stdbool.h`는 `bool`, `true`, `false` 매크로를 각각 `_Bool`, `1`, `0`으로 정의한다. 따라서 `true`와 `false`는 `int` 타입을 가진다.
** 그러나 C23에서는 `bool`, `true`, `false`가 키워드이며, `true`와 `false`는 `bool` 타입을 가진다.
* C++에는 단일 UTF 코드 단위를 인코딩하는 `char8_t`, `char16_t`, `char32_t` 타입이 있다. C23에는 이들이 포함되지만, 별개의 내장 타입이 아닌 다른 정수 타입에 대한 typedef로 포함되어 있어 해당 이름이 예약 키워드가 아니다.
* C에서는 `int` 타입의 비트 필드가 부호 있는 것인지 부호 없는 것인지는 구현 정의이지만, C++에서는 기본 타입과 일치하도록 항상 부호 있는 타입이다.

이전 섹션의 다른 몇 가지 차이점도 두 언어 모두에서 컴파일되지만 다르게 동작하는 코드를 생성하는 데 활용될 수 있다. 예를 들어, 다음 함수는 C와 C++에서 다른 값을 반환할 것이다.


```c
extern int T;

int size(void) {
    struct T {
        int i;
        int j;
    };

    return sizeof(T);
    // C:   return sizeof(int)
    // C++: return sizeof(struct T)
}
```


이는 C가 구조체 태그 앞에 `struct`를 요구하기 때문에(따라서 `sizeof(T)`는 변수를 참조한다), C++는 이를 생략할 수 있기 때문이다(따라서 `sizeof(T)`는 암시적 `typedef`를 참조한다). `extern` 선언이 함수 내에 배치되면 결과가 달라질 수 있음에 유의하라. 이 경우 함수 범위에 동일한 이름을 가진 식별자가 있으면 C++의 암시적 `typedef`가 적용되지 않으며, C와 C++의 결과는 동일할 것이다. 또한 위 예시의 모호성은 `sizeof` 연산자와 함께 괄호를 사용했기 때문이다. `sizeof T`를 사용하면 `T`가 타입이 아닌 표현식이어야 하므로 C++에서는 예시가 컴파일되지 않을 것이다.

## C 및 C++ 코드 연결
C와 C++는 높은 수준의 소스 호환성을 유지하지만, 각 컴파일러가 생성하는 오브젝트 파일은 C와 C++ 코드를 혼합할 때 나타나는 중요한 차이점을 가질 수 있다. 특히:
* C 컴파일러는 C++ 컴파일러처럼 심볼을 맹글링하지 않는다.
* 컴파일러 및 아키텍처에 따라 두 언어 간에 호출 규약이 다를 수도 있다.

이러한 이유로 C++ 코드가 C 함수 `foo()`를 호출하려면 C++ 코드는 `extern "C"`로 `foo()`를 프로토타입해야 한다. 마찬가지로 C 코드가 C++ 함수 `bar()`를 호출하려면 `bar()`에 대한 C++ 코드는 `extern "C"`로 선언되어야 한다.

헤더 파일이 C와 C++ 호환성을 모두 유지하는 일반적인 관행은 헤더 범위에 대해 선언을 `extern "C"`로 만드는 것이다.

에서

```c
// C++ 컴파일러인 경우 C 연결 사용
#ifdef __cplusplus
extern "C" {
#endif

// 이 함수들은 C 연결을 얻는다
void foo();

struct Bar {
    // 여기에 구현
};

// C++ 컴파일러인 경우 C 연결 종료
#ifdef __cplusplus
}
#endif
```


C와 C++ 연결 및 호출 규약의 차이점은 함수 포인터를 사용하는 코드에 미묘한 영향을 미칠 수도 있다. 일부 컴파일러는 `extern "C"`로 선언된 함수 포인터가 `extern "C"`로 선언되지 않은 C++ 함수를 가리키면 작동하지 않는 코드를 생성한다.

예를 들어, 다음 코드는:


```c
void myFunction();
extern "C" void foo(void (*fnPtr)(void));

void bar() {
   foo(myFunction);
}
```


썬 마이크로시스템즈의 C++ 컴파일러를 사용하면 다음과 같은 경고가 발생한다:


```c
$ CC -c test.cc
 "test.cc", line 6: Warning (Anachronism): Formal argument fnPtr of type
 extern "C" void(*)() in call to foo(extern "C" void(*)()) is being passed
 void(*)().
```


이는 `myFunction()`이 C 연결 및 호출 규약으로 선언되지 않았지만 C 함수 `foo()`에 전달되고 있기 때문이다.

## 같이 보기
* C 소프트웨어 및 도구 목록
* C++ 소프트웨어 및 도구 목록

## 각주

## 외부 링크

* 자세한 비교, C89 표준 관점에서 문장별로.
* ISO C와 ISO C++ 간의 비호환성, David R. Tribble (2001년 8월).
* Oracle (Sun Microsystems) C++ 마이그레이션 가이드, 섹션 3.11, Oracle/Sun 컴파일러 문서의 링크 범위.
* Oracle: 동일한 프로그램에서 C와 C++ 코드 혼합, Steve Clamage (ANSI C++ 위원회 의장)의 개요.

분류:C++
분류:C 프로그래밍 언어