본문으로 이동

C와 C++의 호환성

위키백과, 우리 모두의 백과사전.
(C와 C++의 비교에서 넘어옴)

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

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

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

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

C에서는 유효하지만 C++에서는 유효하지 않은 구성

[편집]

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

  • 일반적으로 접하게 되는 한 가지 차이점은 C가 포인터와 관련하여 더 약한 타입이라는 것이다. 특히 C는 `void*` 포인터를 캐스트 없이 어떤 포인터 타입으로도 할당할 수 있게 허용하지만, C++는 그렇지 않다. 이러한 프로그래밍 관용구는 `malloc` 메모리 할당[9]을 사용하거나, POSIX pthreads API 및 콜백과 관련된 다른 프레임워크에 컨텍스트 포인터를 전달하는 C 코드에서 자주 나타난다. 예를 들어 다음은 C에서는 유효하지만 C++에서는 그렇지 않다.
    void* ptr;
    // void*에서 int*로의 암시적 변환
    int* i = ptr;
    

    또는 비슷하게:

    int* j = malloc(5 * sizeof *j);
    // void*에서 int*로의 암시적 변환
    

    코드를 C와 C++ 모두에서 컴파일되도록 하려면 다음과 같이 명시적 캐스트를 사용해야 한다(두 언어 모두에서 일부 주의사항이 있음).[10]

    void* ptr;
    int* i = (int*)ptr;
    int* j = (int*)malloc(5 * sizeof *j);
    
  • C++는 한정자를 추가하는 포인터 할당에 대해 더 복잡한 규칙을 가지고 있다. C++는 int**const int* const*에 할당하는 것을 허용하지만, 안전하지 않은 const int**로의 할당은 허용하지 않는 반면, C는 둘 다 허용하지 않는다(컴파일러는 일반적으로 경고만 발생시킨다).
  • C++는 일부 C 표준 라이브러리 함수를 변경하여 const 타입 한정자가 있는 추가 오버로드된 함수를 추가한다. 예를 들어 strchr는 C에서 char*를 반환하는 반면, C++는 const char* strchr(const char*)char* strchr(char*)의 두 가지 오버로드된 함수가 있는 것처럼 동작한다. C23에서는 제네릭 선택을 사용하여 C의 동작을 C++의 동작과 더 유사하게 만들었다.[11]
  • C++는 또한 열거형으로의 변환에 더 엄격하다. int는 C에서처럼 열거형으로 암시적으로 변환될 수 없다. 또한 C23 이전에는 열거 상수(enum 열거자)는 C에서 항상 int 타입이었지만, C++(및 C23 이상)에서는 별개의 타입이며 int와 다른 크기를 가질 수 있다.
  • C++에서 const 변수는 초기화되어야 하지만, C에서는 그렇지 않다.
  • C++ 컴파일러는 다음 C99 코드와 같이 goto나 switch가 초기화를 건너뛰는 것을 금지한다.
    void fn(void) {
        goto flack;
        int i = 1;
    flack:
        ;
    }
    
  • 문법적으로 유효하지만, longjmp()는 건너뛴 스택 프레임에 사소하지 않은 소멸자를 가진 객체가 포함된 경우 C++에서 정의되지 않은 동작을 초래한다.[12] C++ 구현은 소멸자가 호출되도록 동작을 정의할 수 있다. 그러나 이는 longjmp()의 일부 사용을 방해할 수 있으며, 예를 들어 longjmp()를 사용하여 별도의 호출 스택 간에 스레드 또는 코루틴을 전환하는 것과 같은 경우, 전역 주소 공간에서 하위 호출 스택에서 상위 호출 스택으로 점프할 때 하위 호출 스택의 모든 객체에 대해 소멸자가 호출될 것이다. C에서는 이러한 문제가 없다.
  • C는 단일 번역 단위 내에서 단일 전역 변수에 대해 여러 개의 임시 정의를 허용하지만, C++에서는 ODR 위반으로 인해 유효하지 않다.
    int n;
    int n = 10;
    
  • C에서는 기존 struct, union 또는 enum과 동일한 이름을 가진 새 유형을 선언하는 것이 유효하지만, C++에서는 유효하지 않다. C에서는 struct, unionenum 유형을 참조할 때마다 해당 유형을 명시해야 하는 반면, C++에서는 이러한 유형의 모든 선언이 암시적으로 Typedef를 포함하기 때문이다.
    enum Bool {
        FALSE = 0,
        TRUE = 1
    };
    
    typedef int Bool;
    
  • 비프로토타입("K&R 스타일") 함수 선언은 C++에서 유효하지 않다. C에서는 C23까지[13][14] 여전히 유효하지만, 1990년 C의 최초 표준화 이후부터는 더 이상 사용되지 않는 것으로 간주되었다. ("더 이상 사용되지 않는"이라는 용어는 ISO C 표준에서 "향후 표준 개정 시 제거될 수 있는" 기능을 의미한다.) 마찬가지로, 암시적 함수 선언(선언되지 않은 함수 사용)은 C++에서 허용되지 않으며, C에서는 1999년부터 유효하지 않다.
  • C23 이전의 C에서는[15] 매개변수 없는 함수 선언, 예: int foo();는 매개변수가 지정되지 않았음을 의미한다. 따라서 foo(42, "hello world")와 같이 하나 이상의 인자를 사용하여 이러한 함수를 호출하는 것이 합법적이다. 반면, C++에서는 인자 없는 함수 프로토타입은 함수가 인자를 받지 않음을 의미하며, 인자를 사용하여 이러한 함수를 호출하는 것은 잘못된 형식이다. C에서 인자를 받지 않는 함수를 선언하는 올바른 방법은 int foo(void);와 같이 'void'를 사용하는 것이며, 이는 C++에서도 유효하다. 빈 함수 프로토타입은 C99(C89와 마찬가지로)에서 더 이상 사용되지 않는 기능이다.
  • C와 C++ 모두에서 중첩된 struct 타입을 정의할 수 있지만, 스코프는 다르게 해석된다. C++에서 중첩된 struct는 바깥쪽 struct의 스코프/네임스페이스 내에서만 정의되는 반면, C에서는 내부 struct도 바깥쪽 struct 외부에서 정의된다.
  • C는 함수 프로토타입에서 struct, union, 그리고 enum 타입을 선언할 수 있지만, C++는 그렇지 않다.

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

  • float complexdouble complex 기본 데이터 타입을 사용하는 복소수 연산_Complex 키워드와 complex 편의 매크로를 통해 C99 표준에 추가되었다. C++에서는 복소수 클래스를 사용하여 복소수 연산을 수행할 수 있지만, 두 방법은 코드 호환성이 없다. (그러나 C++11 이후의 표준에서는 이진 호환성을 요구한다.)[16]
  • 가변 길이 배열. 이 기능은 컴파일 시간 sizeof 연산자가 아닐 수도 있게 한다.[17]
    void foo(size_t x, int a[*]); // VLA 선언
    
    void foo(size_t x, int a[x]) {
        printf("%zu\n", sizeof a); // sizeof(int*)와 동일
        char s[x * 2];
        printf("%zu\n", sizeof s); // x * 2의 값을 출력
    }
    
  • 두 개 이상의 멤버가 있는 C99 구조체 유형의 마지막 멤버는 길이가 지정되지 않은 배열의 구문 형식을 취하는 유연한 배열 멤버일 수 있다. 이는 가변 길이 배열과 유사한 목적을 제공하지만, VLA는 유형 정의에 나타날 수 없으며, VLA와 달리 유연한 배열 멤버는 정의된 크기가 없다. ISO C++에는 이러한 기능이 없다. 예시:
    struct X {
        int m;
        int n;
        char bytes[];
    };
    
  • C99에 정의된 restrict 타입 한정자는 C++03 표준에 포함되지 않았지만, GNU 컴파일러 모음과 같은 대부분의 주류 컴파일러[18], 마이크로소프트 비주얼 C++, 그리고 인텔 C++ 컴파일러는 확장 기능으로 유사한 기능을 제공한다.
  • 함수의 배열 매개변수 한정자는 C에서는 지원되지만 C++에서는 그렇지 않다.
    int foo(int a[const]); // int* const a와 동일
    int bar(char s[static 5]); // s가 최소 5개의 char 길이라는 주석
    
  • C의 복합 리터럴 기능은 C++11의 목록 초기화 구문을 통해 내장 및 사용자 정의 타입 모두로 일반화되지만, 일부 구문 및 의미론적 차이가 있다.
    struct X a = (struct X){4, 6}; // C++의 동등한 표현은 X{4, 6}이다. C99에서 사용된 C 구문 형식은 GCC 및 Clang C++ 컴파일러에서 확장 기능으로 지원된다.
    foo(&(struct X){4, 6}); // 객체는 스택에 할당되며 해당 주소를 함수에 전달할 수 있다. 이는 C++에서 지원되지 않는다.
    
    if (memcmp(d, (int[]){8, 6, 7, 5, 3, 0, 9}, n) == 0) {
    
    }
    // C++의 동등한 표현은 다음과 같다:
    // using digits = int[];
    // if (memcmp(d, digits{8, 6, 7, 5, 3, 0, 9}, n) == 0) {
    //
    // }
    
  • 배열에 대한 지정된 초기화자는 C에서만 유효하다:
    char s[20] = { [0] = 'a', [8] = 'g' };  // C에서는 허용되지만, C++에서는 아님
    
  • 값을 반환하지 않는 함수는 C++에서는 noreturn 특성을 사용하여 주석을 달 수 있지만, C에서는 별도의 키워드를 사용한다. C23에서는 특성 구문도 지원된다.[19]
  • C(C2Y부터)는 이름이 있는 루프와 이름이 있는 break 문(자바와 유사)을 지원한다. 이 기능은 C++에 추가되지 않았다.[20]

C++는 새로운 기능을 지원하기 위해 수많은 추가 키워드를 추가했다. 이로 인해 해당 키워드를 식별자로 사용하는 C 코드는 C++에서 유효하지 않게 된다. 예를 들어, 다음 스니펫은 template이라는 연결 리스트 구조를 정의하며, newclass 필드를 가진다. 이는 유효한 C 코드이지만, template, newclass 키워드가 예약되어 있으므로 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 불리언 타입을 가지며, 상수 truefalse가 있지만 다르게 정의된다.
    • C++에서는 bool내장 타입이자 예약어이다.
    • C99에서는 새로운 키워드인 _Bool이 새로운 내장 불리언 타입으로 도입되었다. 헤더 stdbool.hbool, true, false 매크로를 각각 _Bool, 1, 0으로 정의한다. 따라서 truefalseint 타입을 가진다.
    • 그러나 C23에서는 bool, true, false가 키워드이며, truefalsebool 타입을 가진다.
  • 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++ 컴파일러처럼 심볼을 맹글링하지 않는다.[21]
  • 컴파일러 및 아키텍처에 따라 두 언어 간에 호출 규약이 다를 수도 있다.

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

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

foo.h에서

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

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

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

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

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

예를 들어, 다음 코드는:

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()에 전달되고 있기 때문이다.

같이 보기

[편집]

각주

[편집]
  1. Stroustrup, Bjarne. “An Overview of the C++ Programming Language in The Handbook of Object Technology (Editor: Saba Zamir). CRC Press LLC, Boca Raton. 1999. ISBN 0-8493-3135-8.” (PDF). 4쪽. 16 August 2012에 원본 문서 (PDF)에서 보존된 문서. 12 August 2009에 확인함. 
  2. B.Stroustrup. “C and C++: Siblings. The C/C++ Users Journal. July 2002.” (PDF). 2019년 3월 17일에 확인함. 
  3. “Bjarne Stroustrup's FAQ – Is C a subset of C++?”. 2019년 9월 22일에 확인함. 
  4. B. Stroustrup. “C and C++: A Case for Compatibility. The C/C++ Users Journal. August 2002.” (PDF). 22 July 2012에 원본 문서 (PDF)에서 보존된 문서. 18 August 2013에 확인함. 
  5. Rationale for International Standard—Programming Languages—C 보관됨 6 6월 2016 - 웨이백 머신, revision 5.10 (April 2003).
  6. “C Dialect Options - Using the GNU Compiler Collection (GCC)”. 《gnu.org》. 26 March 2014에 원본 문서에서 보존된 문서. 
  7. “N4659: Working Draft, Standard for Programming Language C++” (PDF). §Annex C.1. 7 December 2017에 원본 문서 (PDF)에서 보존된 문서.  ("명시적 또는 암시적 초기화자를 사용하여 선언을 건너뛰는 것은 유효하지 않다(전체 블록을 건너뛰는 경우는 제외). … 이 간단한 컴파일 타임 규칙을 통해 C++는 초기화된 변수가 범위 내에 있는 경우 확실히 초기화되었음을 보장한다.")
  8. “N4659: Working Draft, Standard for Programming Language C++” (PDF). §Annex C.1. 7 December 2017에 원본 문서 (PDF)에서 보존된 문서. 
  9. “IBM Knowledge Center”. 《ibm.com》. 
  10. “FAQ > Casting malloc - Cprogramming.com”. 《faq.cprogramming.com》. 5 April 2007에 원본 문서에서 보존된 문서. 
  11. “Qualifier-preserving standard library functions, v4” (PDF). 
  12. “longjmp - C++ Reference”. 《www.cplusplus.com》. 19 May 2018에 원본 문서에서 보존된 문서. 
  13. “WG14 N2432 : Remove support for function definitions with identifier lists” (PDF). 
  14. “2011 ISO C draft standard” (PDF). 
  15. “WG14 N 2841: No function declarators without prototypes”. 
  16. “std::complex - cppreference.com”. 《en.cppreference.com》. 15 July 2017에 원본 문서에서 보존된 문서. 
  17. “Incompatibilities Between ISO C and ISO C++”. 9 April 2006에 원본 문서에서 보존된 문서. 
  18. Restricted Pointers 보관됨 6 8월 2016 - 웨이백 머신 from Using the GNU Compiler Collection (GCC)
  19. “WG14-N2764 : The noreturn attribute” (PDF). 《open-std.org》. 2021년 6월 21일. 2022년 12월 25일에 원본 문서 (PDF)에서 보존된 문서. 
  20. “Information technology — Programming languages — C” (PDF). 《open-std.org》. 2025년 5월 4일. 
  21. “IBM Knowledge Center”. 《ibm.com》. 
  22. “IBM Knowledge Center”. 《ibm.com》. 
  23. “Oracle Documentation”. Docs.sun.com. 3 April 2009에 원본 문서에서 보존된 문서. 18 August 2013에 확인함. 

외부 링크

[편집]