C ++ 17, C ++ 14 및 C ++ 11 개체를 연결하는 것이 안전합니까? 구조 만 사용하여

모두 동일한 컴파일러 / 버전 에서 생성 된 세 개의 컴파일 된 객체가 있다고 가정합니다 .

  1. A는 C ++ 11 표준으로 컴파일되었습니다.
  2. B는 C ++ 14 표준으로 컴파일되었습니다.
  3. C는 C ++ 17 표준으로 컴파일되었습니다.

단순화를 위해 모든 헤더가 세 가지 표준 버전간에 의미가 변경되지 않은 구조 만 사용하여 C ++ 11로 작성되었다고 가정 해 보겠습니다. 따라서 상호 종속성이 헤더 포함으로 올바르게 표현되고 컴파일러가 객체를 처리하지 않았습니다.

이러한 객체의 어떤 조합이 단일 바이너리에 링크하는 것이 안전하지 않습니까? 왜?


편집 : 주요 컴파일러 (예 : gcc, clang, vs ++)를 다루는 답변을 환영합니다.



답변

이러한 객체의 어떤 조합이 단일 바이너리에 링크하는 것이 안전하지 않습니까? 왜?

GCC의 경우 개체 A, B 및 C의 모든 조합을 함께 연결하는 것이 안전합니다. 모두 동일한 버전으로 빌드 된 경우 ABI와 호환되며 표준 버전 (예 : -std옵션)은 아무런 차이가 없습니다.

왜? 그것은 우리가 보장하기 위해 열심히 노력하는 우리 구현의 중요한 속성이기 때문입니다.

당신은 문제가있는 경우는 GCC의 다른 버전으로 컴파일 된 개체 함께 연결하는 경우입니다 그 표준 GCC의 지원이 완료되기 전에 새로운 C ++ 표준에서 불안정 기능을 사용하고 있습니다. 예를 들어 GCC 4.9를 사용하여 개체를 컴파일하고 -std=c++11GCC 5를 사용하여 다른 개체 를 컴파일하면 -std=c++11문제가 발생합니다. C ++ 11 지원은 GCC 4.x에서 실험적 이었으므로 GCC 4.9와 5 버전의 C ++ 11 기능간에 호환되지 않는 변경 사항이있었습니다. 마찬가지로 GCC 7로 하나의 객체를 컴파일 -std=c++17하고 GCC 8로 다른 객체 를 컴파일 -std=c++17하면 GCC 7 및 8에서 C ++ 17 지원이 아직 실험적이고 발전하고 있기 때문에 문제가 발생합니다.

반면에 다음 개체의 모든 조합이 작동합니다 ( libstdc++.so버전 에 대한 아래 참고 참조 ).

  • GCC 4.9로 컴파일 된 객체 D 및 -std=c++03
  • GCC 5로 컴파일 된 객체 E 및 -std=c++11
  • GCC 7 및 -std=c++17

이는 사용 된 세 가지 컴파일러 버전 모두에서 C ++ 03 지원이 안정적이므로 모든 개체간에 C ++ 03 구성 요소가 호환되기 때문입니다. C ++ 11 지원은 GCC 5부터 안정적이지만 객체 D는 C ++ 11 기능을 사용하지 않으며 객체 E와 F는 모두 C ++ 11 지원이 안정적인 버전을 사용합니다. C ++ 17 지원은 사용 된 컴파일러 버전에서 안정적이지 않지만 개체 F 만 C ++ 17 기능을 사용하므로 다른 두 개체와의 호환성 문제가 없습니다 (공유하는 유일한 기능은 C ++ 03에서 제공됨). 또는 C ++ 11 및 사용 된 버전이 해당 부분을 확인합니다.) 나중에 GCC 8을 사용하여 네 번째 객체 G -std=c++17를 컴파일하려는 경우 F와 G의 C ++ 17 기호가 호환되지 않기 때문에 동일한 버전 (또는 F에 연결하지 않음)으로 F를 다시 컴파일해야합니다.

위에서 설명한 D, E 및 F 간의 호환성에 대한 유일한주의 사항은 프로그램이 libstdc++.soGCC 7 (또는 이후 버전) 의 공유 라이브러리를 사용해야한다는 것 입니다. 객체 F는 GCC 7로 컴파일되었으므로 해당 릴리스의 공유 라이브러리를 사용해야합니다. GCC 7로 프로그램의 일부를 컴파일하면 libstdc++.soGCC 4.9 또는 GCC 5에 없는 심볼에 대한 종속성이 발생할 수 있기 때문입니다 . 마찬가지로, GCC 8로 빌드 된 객체 G에 링크 한 경우 GCC 8에서를 사용하여 libstdc++.soG에 필요한 모든 기호를 찾을 수 있도록해야합니다. 간단한 규칙은 프로그램이 런타임에 사용하는 공유 라이브러리가 최소한 객체를 컴파일하는 데 사용되는 버전만큼 새로운 것인지 확인하는 것입니다.

질문에 대한 의견에서 이미 언급했듯이 GCC를 사용할 때 또 다른주의 사항은 GCC 5 이후 libstdc ++에서 사용할 수있는 두 가지 구현이std::string 있다는 것입니다 . 두 구현은 그렇게 충돌하는 경우 하나의 객체가 사용하지 않는, (그래서 서로 연결 할 수없는, 서로 다른 난도질 이름이)하지만 같은 이진 수 공존이 (서로 다른 난도질 이름이 링크 호환되지 않습니다 std::string와 다른 용도 std::__cxx11::string). 객체가 사용하는 std::string경우 일반적으로 모두 동일한 문자열 구현으로 컴파일되어야합니다. 로 컴파일 -D_GLIBCXX_USE_CXX11_ABI=0하여 원래 gcc4-compatible구현 -D_GLIBCXX_USE_CXX11_ABI=1을 선택하거나 새 cxx11구현 을 선택합니다 (이름에 속지 마십시오. C ++ 03에서도 사용할 수 있습니다.cxx11C ++ 11 요구 사항을 준수하기 때문에). 어떤 구현이 기본값인지는 GCC가 구성된 방법에 따라 다르지만 기본값은 항상 매크로를 사용하여 컴파일 타임에 재정의 할 수 있습니다.


답변

답에는 두 부분이 있습니다. 컴파일러 수준에서의 호환성 및 링커 수준에서의 호환성. 전자부터 시작합시다.

모든 헤더가 C ++ 11로 작성되었다고 가정 해 보겠습니다.

동일한 컴파일러를 사용한다는 것은 타겟 C ++ 표준에 관계없이 동일한 표준 라이브러리 헤더 및 소스 파일 (컴파일러와 관련된 파일)이 사용된다는 것을 의미합니다. 따라서 표준 라이브러리의 헤더 파일은 컴파일러에서 지원하는 모든 C ++ 버전과 호환되도록 작성됩니다.

즉, 번역 단위를 컴파일하는 데 사용되는 컴파일러 옵션이 특정 C ++ 표준을 지정하는 경우 최신 표준에서만 사용할 수있는 모든 기능에 액세스 할 수 없습니다. 이것은 __cplusplus지시문을 사용하여 수행됩니다 . 사용 방법에 대한 흥미로운 예 는 벡터 소스 파일을 참조하십시오 . 마찬가지로 컴파일러는 최신 버전의 표준에서 제공하는 모든 구문 기능을 거부합니다.

그 모든 것은 당신의 가정이 당신이 작성한 헤더 파일에만 적용될 수 있음을 의미합니다. 이러한 헤더 파일은 다른 C ++ 표준을 대상으로하는 다른 번역 단위에 포함될 때 비 호환성을 유발할 수 있습니다. 이것은 C ++ 표준의 부록 C에서 논의됩니다. 4 개의 절이 있습니다. 첫 번째 절만 논의하고 나머지는 간단히 언급하겠습니다.

C.3.1 조항 2 : 어휘 규약

작은 따옴표는 C ++ 11에서 문자 리터럴을 구분하는 반면 C ++ 14 및 C ++ 17에서는 숫자 구분 기호입니다. 순수 C ++ 11 헤더 파일 중 하나에 다음 매크로 정의가 있다고 가정합니다.

#define M(x, ...) __VA_ARGS__

// Maybe defined as a field in a template or a type.
int x[2] = { M(1'2,3'4) };

헤더 파일을 포함하지만 각각 C ++ 11 및 C ++ 14를 대상으로하는 두 개의 변환 단위를 고려하십시오. C ++ 11을 대상으로 할 때 따옴표 안의 쉼표는 매개 변수 구분 기호로 간주되지 않습니다. 매개 변수는 한 번만 있습니다. 따라서 코드는 다음과 같습니다.

int x[2] = { 0 }; // C++11

반면에 C ++ 14를 대상으로 할 때 작은 따옴표는 숫자 구분 기호로 해석됩니다. 따라서 코드는 다음과 같습니다.

int x[2] = { 34, 0 }; // C++14 and C++17

여기서 중요한 점은 순수한 C ++ 11 헤더 파일 중 하나에 작은 따옴표를 사용하면 C ++ 14 / 17을 대상으로하는 번역 단위에서 놀라운 버그가 발생할 수 있다는 것입니다. 따라서 헤더 파일이 C ++ 11로 작성된 경우에도 이후 버전의 표준과 호환되도록 신중하게 작성해야합니다. 여기서 __cplusplus지시문이 유용 할 수 있습니다.

표준의 다른 세 가지 조항은 다음과 같습니다.

C.3.2 Clause 3 : 기본 개념

변경 : 새로운 보통 (비 배치) 할당 해제

근거 : 크기가 지정된 할당 해제에 필요합니다.

원래 기능에 대한 영향 : 유효한 C ++ 2011 코드는 다음과 같이 전역 배치 할당 함수 및 할당 해제 함수를 선언 할 수 있습니다.

void operator new(std::size_t, std::size_t);
void operator delete(void*, std::size_t) noexcept;

그러나이 국제 표준에서 연산자 삭제 선언은 사전 정의 된 일반 (비 배치) 연산자 삭제 (3.7.4)와 일치 할 수 있습니다. 만약 그렇다면, 프로그램은 클래스 멤버 할당 함수와 할당 해제 함수 (5.3.4)와 같이 잘못된 형식입니다.

C.3.3 Clause 7 : 선언

변경 사항 : constexpr 비 정적 멤버 함수는 암시 적으로 const 멤버 함수가 아닙니다.

근거 : constexpr 멤버 함수가 개체를 변경하도록 허용하는 데 필요합니다.

원래 기능에 대한 영향 : 유효한 C ++ 2011 코드가이 국제 표준에서 컴파일되지 않을 수 있습니다.

예를 들어, 다음 코드는 C ++ 2011에서 유효하지만 다른 반환 유형으로 동일한 멤버 함수를 두 번 선언하므로이 국제 표준에서는 유효하지 않습니다.

struct S {
constexpr const int &f();
int &f();
};

C.3.4 Clause 27 : 입력 / 출력 라이브러리

변경 : 가져 오기가 정의되지 않았습니다.

근거 : get의 사용은 위험한 것으로 간주됩니다.

원래 기능에 미치는 영향 :이 국제 표준에서는 gets 함수를 사용하는 유효한 C ++ 2011 코드가 컴파일되지 않을 수 있습니다.

C ++ 14와 C ++ 17 간의 잠재적 인 비 호환성에 대해서는 C.4에서 설명합니다. 모든 비표준 헤더 파일은 C ++ 11 (질문에 명시된대로)로 작성되었으므로 이러한 문제는 발생하지 않으므로 여기서 언급하지 않겠습니다.

이제 링커 수준에서 호환성에 대해 설명하겠습니다. 일반적으로 비 호환성에 대한 잠재적 인 이유는 다음과 같습니다.

결과 개체 파일의 형식이 대상 C ++ 표준에 따라 달라지는 경우 링커는 다른 개체 파일을 연결할 수 있어야합니다. GCC, LLVM 및 VC ++에서는 다행히도 그렇지 않습니다. 즉, 객체 파일의 형식은 컴파일러 자체에 크게 의존하지만 대상 표준에 관계없이 동일합니다. 실제로 GCC, LLVM 및 VC ++의 링커는 대상 C ++ 표준에 대한 지식이 필요하지 않습니다. 이것은 또한 이미 컴파일 된 객체 파일을 링크 할 수 있음을 의미합니다 (런타임을 정적으로 링크).

프로그램 시작 루틴 (을 호출하는 함수 main)이 다른 C ++ 표준에 대해 다르고 다른 루틴이 서로 호환되지 않는 경우 개체 파일을 연결할 수 없습니다. GCC, LLVM 및 VC ++에서는 다행히도 그렇지 않습니다. 또한 main함수 의 서명 (및 이에 적용되는 제한 사항, 표준의 섹션 3.6 참조)은 모든 C ++ 표준에서 동일하므로 어떤 번역 단위에 존재하는지는 중요하지 않습니다.

일반적으로 WPO는 다른 C ++ 표준을 사용하여 컴파일 된 개체 파일에서 제대로 작동하지 않을 수 있습니다. 이것은 정확히 어떤 단계의 컴파일러가 대상 표준에 대한 지식을 필요로하고 어떤 단계가 필요하지 않은지, 객체 파일을 교차하는 절차 간 최적화에 미치는 영향에 따라 다릅니다. 다행히 GCC, LLVM 및 VC ++는 잘 설계되어 있으며이 문제가 없습니다 (내가 알지 못함).

따라서 GCC, LLVM 및 VC ++는 여러 버전의 C ++ 표준에서 이진 호환성 을 지원하도록 설계되었습니다 . 이것은 실제로 표준 자체의 요구 사항은 아닙니다.

그런데 VC ++ 컴파일러는 특정 버전의 C ++ 표준을 대상으로 할 수 있는 std 스위치를 제공하지만 C ++ 11 대상 지정을 지원하지 않습니다. 지정할 수있는 최소 버전은 Visual C ++ 2013 업데이트 3부터 시작되는 기본값 인 C ++ 14입니다. 이전 버전의 VC ++를 사용하여 C ++ 11을 대상으로 할 수 있지만 다른 VC ++ 컴파일러를 사용해야합니다. C ++ 표준의 서로 다른 버전을 대상으로하는 서로 다른 번역 단위를 컴파일하기 위해 최소한 WPO가 중단됩니다.

주의 : 내 대답은 완전하지 않거나 매우 정확하지 않을 수 있습니다.


답변

새로운 C ++ 표준은 언어 기능과 표준 라이브러리 구성 요소의 두 부분으로 제공됩니다.

새로운 표준 이 의미하는 것처럼 언어 자체의 변경 (예 : ranged-for)은 거의 문제가 없습니다 (때로는 새로운 표준 언어 기능을 사용하는 타사 라이브러리 헤더에 충돌이 존재 함).

하지만 표준 라이브러리 …

각 컴파일러 버전은 C ++ 표준 라이브러리 (gcc가있는 libstdc ++, clang이있는 libc ++, VC ++가있는 MS C ++ 표준 라이브러리, …)의 구현과 정확히 하나의 구현과 함께 제공되며 각 표준 버전에 대한 구현은 많지 않습니다. 또한 어떤 경우에는 제공된 컴파일러가 아닌 다른 표준 라이브러리 구현을 사용할 수도 있습니다. 주의해야 할 것은 이전 표준 라이브러리 구현을 최신 라이브러리와 연결하는 것입니다.

타사 라이브러리와 사용자 코드간에 발생할 수있는 충돌은 해당 타사 라이브러리에 연결되는 표준 라이브러리 (및 기타 라이브러리)입니다.