C와 C++의 호환성
C와 C++ 프로그래밍 언어는 밀접하게 관련되어 있지만 많은 중요한 차이점이 있다. 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,union및enum유형을 참조할 때마다 해당 유형을 명시해야 하는 반면, 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++는 그렇지 않다.
C99 및 C11은 복소수, 가변 길이 배열(복소수 및 가변 길이 배열은 C11에서 선택적 확장으로 지정됨), 가변 길이 배열 멤버, restrict 키워드, 배열 매개변수 한정자 및 복합 리터럴과 같이 C++20 기준으로 표준 C++에 통합되지 않은 몇 가지 추가 기능을 C에 추가했다.
float complex및double 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이라는 연결 리스트 구조를 정의하며, 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++에는 단일 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 함수 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()에 전달되고 있기 때문이다.
같이 보기
[편집]각주
[편집]- ↑ 가 나 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에 확인함.
- ↑ B.Stroustrup. “C and C++: Siblings. The C/C++ Users Journal. July 2002.” (PDF). 2019년 3월 17일에 확인함.
- ↑ “Bjarne Stroustrup's FAQ – Is C a subset of C++?”. 2019년 9월 22일에 확인함.
- ↑ B. Stroustrup. “C and C++: A Case for Compatibility. The C/C++ Users Journal. August 2002.” (PDF). 22 July 2012에 원본 문서 (PDF)에서 보존된 문서. 18 August 2013에 확인함.
- ↑ Rationale for International Standard—Programming Languages—C 보관됨 6 6월 2016 - 웨이백 머신, revision 5.10 (April 2003).
- ↑ “C Dialect Options - Using the GNU Compiler Collection (GCC)”. 《gnu.org》. 26 March 2014에 원본 문서에서 보존된 문서.
- ↑ “N4659: Working Draft, Standard for Programming Language C++” (PDF). §Annex C.1. 7 December 2017에 원본 문서 (PDF)에서 보존된 문서. ("명시적 또는 암시적 초기화자를 사용하여 선언을 건너뛰는 것은 유효하지 않다(전체 블록을 건너뛰는 경우는 제외). … 이 간단한 컴파일 타임 규칙을 통해 C++는 초기화된 변수가 범위 내에 있는 경우 확실히 초기화되었음을 보장한다.")
- ↑ “N4659: Working Draft, Standard for Programming Language C++” (PDF). §Annex C.1. 7 December 2017에 원본 문서 (PDF)에서 보존된 문서.
- ↑ “IBM Knowledge Center”. 《ibm.com》.
- ↑ “FAQ > Casting malloc - Cprogramming.com”. 《faq.cprogramming.com》. 5 April 2007에 원본 문서에서 보존된 문서.
- ↑ “Qualifier-preserving standard library functions, v4” (PDF).
- ↑ “longjmp - C++ Reference”. 《www.cplusplus.com》. 19 May 2018에 원본 문서에서 보존된 문서.
- ↑ “WG14 N2432 : Remove support for function definitions with identifier lists” (PDF).
- ↑ “2011 ISO C draft standard” (PDF).
- ↑ “WG14 N 2841: No function declarators without prototypes”.
- ↑ “std::complex - cppreference.com”. 《en.cppreference.com》. 15 July 2017에 원본 문서에서 보존된 문서.
- ↑ “Incompatibilities Between ISO C and ISO C++”. 9 April 2006에 원본 문서에서 보존된 문서.
- ↑ Restricted Pointers 보관됨 6 8월 2016 - 웨이백 머신 from Using the GNU Compiler Collection (GCC)
- ↑ “WG14-N2764 : The noreturn attribute” (PDF). 《open-std.org》. 2021년 6월 21일. 2022년 12월 25일에 원본 문서 (PDF)에서 보존된 문서.
- ↑ “Information technology — Programming languages — C” (PDF). 《open-std.org》. 2025년 5월 4일.
- ↑ “IBM Knowledge Center”. 《ibm.com》.
- ↑ “IBM Knowledge Center”. 《ibm.com》.
- ↑ “Oracle Documentation”. Docs.sun.com. 3 April 2009에 원본 문서에서 보존된 문서. 18 August 2013에 확인함.
외부 링크
[편집]- 자세한 비교, 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++ 위원회 의장)의 개요.