C ++ 17, C ++ 14 및 C ++ 11 개체를 연결하는 것이 안전합니까?


101

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

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

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

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


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


6
학교 / 면접 질문이 아닙니다. 질문은 특정한 경우에서 비롯됩니다. 저는 오픈 소스 라이브러리에 의존하는 프로젝트를 진행하고 있습니다. 이 라이브러리는 소스에서 빌드하지만 빌드 시스템은 C ++ 03 / C ++ 11 빌드 중에서 선택할 수있는 플래그 만 허용합니다. 내가 사용하는 컴파일러는 다른 표준을 지원하며 내 프로젝트를 C ++ 17로 업그레이드하는 것을 고려하고 있습니다. 안전한 결정인지 확실하지 않습니다. ABI에 중단이 있거나 접근 방식이 바람직하지 않은 다른 방법이있을 수 있습니까? 명확한 답을 찾지 못해 일반적인 사례에 대한 질문을 게시하기로 결정했습니다.
ricab

6
이것은 전적으로 컴파일러에 달려 있습니다. 이 상황을 제어하는 ​​공식 C ++ 사양에는 아무것도 없습니다. C ++ 03 또는 C + 11 표준으로 작성된 코드가 C ++ 14 및 C ++ 17 수준에서 몇 가지 문제가있을 가능성도 있습니다. 충분한 지식과 경험 (그리고 시작하기에 잘 작성된 코드)이 있으면 이러한 문제를 해결할 수 있어야합니다. 그러나 최신 C ++ 표준에 익숙하지 않은 경우 빌드 시스템이 지원하는 항목을 고수하는 것이 더 좋으며 테스트를 통해 작동합니다.
Sam Varshavchik

10
@Someprogrammerdude : 매우 가치있는 질문입니다. 대답이 있었으면 좋겠어요. 내가 아는 것은 RHEL devtoolset을 통한 libstdc ++는 최신 항목을 정적으로 연결하고 배포판의 "네이티브"libstdc ++를 사용하여 런타임에 동적으로 해결하도록 이전 항목을 남겨 두는 방식으로 설계 상 역 호환된다는 것입니다. 그러나 그것은 질문에 대한 답이 아닙니다.
궤도의 경쾌한 경주

4
@nm : ... 대부분의 경우입니다 ... 배포 독립적 인 C ++ 라이브러리를 배포하는 거의 모든 사람들이 (1) 동적 라이브러리 형식으로, (2) 인터페이스 경계에 C ++ 표준 라이브러리 컨테이너없이 그렇게합니다. Linux 배포판에서 제공되는 라이브러리는 모두 동일한 컴파일러, 동일한 표준 라이브러리 및 거의 동일한 기본 플래그 세트로 빌드되기 때문에 쉽습니다.
Matteo Italia

3
@MatteoItalia의 이전 주석을 명확히하기 위해 "그리고 C ++ 03에서 C ++ 11 모드로 전환 할 때 (특히 std :: string)". 이것은 사실이 아닙니다 std::string. libstdc ++ 의 활성 구현은 사용 -std모드와 무관 합니다 . 이것은 OP와 같은 상황을 정확하게 지원 하는 중요한 속성 입니다. std::stringC ++ 03 코드 의 새로운 코드를 사용할 수 std::string있고 C ++ 11 코드 의 이전 코드를 사용할 수 있습니다 (Mateo의 이후 주석의 링크 참조).
Jonathan Wakely는

답변:


121

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

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가 구성된 방법에 따라 다르지만 기본값은 항상 매크로를 사용하여 컴파일 타임에 재정의 할 수 있습니다.


"GCC 7로 프로그램의 일부를 컴파일하면 GCC 4.9 또는 GCC 5의 libstdc ++. so에있는 기호에 대한 종속성이 발생할 수 있기 때문에"GCC 4.9 또는 GCC 5에는 존재하지 않습니다. 정적 링크에도 적용됩니까? 컴파일러 버전 간의 호환성에 대한 정보에 감사드립니다.
Hadi Brais

1
이 질문에 대한 현상금을 제공하는 데 큰 결함이 있음을 방금 깨달았습니다. 😂
궤도의 Lightness Races

4
@ricab Clang / libc ++에 대한 대답이 동일하다고 90 % 확신하지만 MSVC에 대해서는 전혀 모릅니다.
Jonathan Wakely

1
이 대답은 훌륭합니다. 5.0+가 11/14에 안정적이라고 문서화되어 있습니까?
Barry

1
명확하지 않거나 한곳에서. gcc.gnu.org/gcc-5/changes.html#libstdcxxgcc.gnu.org/onlinedocs/libstdc++/manual/api.html#api.rel_51 은 C ++ 11에 대한 라이브러리 지원이 완료 됨 (언어 지원은 이전에는 기능이 완전했지만 여전히 "실험적"임). C ++ 14 라이브러리 지원은 6.1까지 여전히 실험적인 것으로 나열되어 있지만 실제로는 5.x와 6.x 사이에서 ABI에 영향을 미치는 변경 사항이 없다고 생각합니다.
Jonathan Wakely는

17

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

모든 헤더가 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가 중단됩니다.

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


질문은 실제로 컴파일보다는 링크에 관한 것이 었습니다. 나는 ( 이 의견 덕분에 ) 아마도 명확하지 않았으며 포함 된 헤더가 세 가지 표준 모두에서 동일한 해석을 가지고 있음을 분명히하기 위해 편집했습니다.
ricab

@ricab 대답은 컴파일과 연결을 모두 다룹니다. 둘 다 물어 보는 줄 알았는데
Hadi Brais

1
사실, 그러나 나는 그 대답이 너무 길고 혼란 스럽다는 것을 알았다. 특히 "이제 링커 수준에서 호환성에 대해 논의 할 것이다". 포함 된 헤더가 C ++ 11 및 C ++ 14 / 17에서 동일한 의미를 갖도록 가정 할 수없는 경우 위의 모든 것을 대체 할 수 있습니다 . 그러면 처음에 포함하는 것이 안전하지 않습니다 . 나머지 부분에 대해서는이 세 가지 글 머리 기호가 비 호환성에 대한 유일한 잠재적 인 이유임을 보여주는 출처가 있습니까? 어떤 경우에서 답을 주셔서 감사합니다, 나는 여전히 투표하고
ricab

@ricab 확실히 말할 수 없습니다. 그래서 답변 끝에 경고를 추가했습니다. 내가 뭔가를 놓친 경우를 대비하여 더 정확하거나 완전하게 답변을 확장 할 수 있습니다.
Hadi Brais

"동일한 컴파일러를 사용하면 동일한 표준 라이브러리 헤더와 소스 파일 (...)이 사용됨을 의미합니다." 어떻게 그럴 수 있습니까? gcc5로 컴파일 된 이전 코드가있는 경우 해당 버전에 속한 '컴파일러 파일'은 미래의 증거가 될 수 없습니다. 서로 다른 컴파일러 버전을 사용하여 (매우) 다른 시간에 컴파일 된 소스 코드의 경우 라이브러리 헤더와 소스 파일이 다르다는 것을 확신 할 수 있습니다. 이것들이 동일해야한다는 규칙에 따라 gcc5로 이전 소스 코드를 다시 컴파일해야하고 ... 모두 최신 (동일한) '컴파일러 파일'을 사용하는지 확인해야합니다.
user2943111

2

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

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

하지만 표준 라이브러리 ...

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

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


"각 컴파일러 버전에는 STL 구현이 함께 제공됩니다." 아니요 그들은 그렇지 않습니다
Lightness Races in Orbit

@LightnessRacesinOrbit libstdc ++와 gcc 사이에 관계가 없다는 뜻입니까?
E. Vakili

8
아니요, STL은 20 년 넘게 사실상 쓸모 없게되었습니다. C ++ 표준 라이브러리를 의미했습니다. 나머지 답변에 관해서는 귀하의 주장을 뒷받침하기 위해 몇 가지 참조 / 증거를 제공 할 수 있습니까? 나는 이와 같은 질문에 대해 중요하다고 생각합니다.
궤도의 경쾌함 경주

3
죄송합니다. 텍스트에서 명확하지 않습니다. 흥미로운 주장을했지만 아직 증거로 뒷받침하지 않았습니다.
궤도의 경쾌한 경주
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.