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++가 크고 야심 찬 언어가 되도록 만족한다"고 밝혔다.Rationale for International Standard—Programming Languages—C , revision 5.10 (April 2003).

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

 C에서는 유효하지만 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++ 컴파일러에 의해 거부된다.

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는 inline 함수의 두 가지 정의를 구별한다. 일반 외부 정의(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++에서 다른 값을 반환할 것이다.

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 연결 사용
#ifdef __cplusplus
extern "C" {
#endif

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

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

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

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

예를 들어, 다음 코드는:

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

void bar() {
   foo(myFunction);
}

썬 마이크로시스템즈의 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 프로그래밍 언어