T *를 레지스터로 전달할 수 있지만 unique_ptr <T>는 왜 할 수 없습니까?


85

CppCon 2019에서 Chandler Carruth의 연설을보고 있습니다.

무 비용 추상화가 없습니다

거기에, 그는 그가 당신이 사용하여 발생할 얼마나 많은 오버 헤드 놀랐다 방법의 예를 제공 std::unique_ptr<int>오버를 int*; 해당 세그먼트는 17:25 시점에 시작됩니다.

그의 예제 코드 쌍 (godbolt.org) 의 컴파일 결과 를 볼 수 있습니다 . 실제로 컴파일러가 unique_ptr 값을 기꺼이 전달하지 않는 것으로 보입니다. 단지 주소-레지스터 내부, 오직 직선 메모리.

Carruth가 27:00 경에했던 지점 중 하나는 C ++ ABI가 값이 아닌 매개 변수 (일부는 아니지만 일부는 기본이 아닌 유형이나 사소한 구성이 아닌 유형)가 메모리에 전달되어야한다는 것입니다. 레지스터 내에서보다는

내 질문 :

  1. 실제로 일부 플랫폼에서 ABI 요구 사항입니까? (어떻게?) 아니면 특정 시나리오에서 약간 비관적입니까?
  2. ABI가 왜 그런가요? 즉, 구조체 / 클래스의 필드가 레지스터 또는 단일 레지스터에 맞는 경우 해당 레지스터 내에서 필드를 전달할 수없는 이유는 무엇입니까?
  3. C ++ 표준위원회가 최근 몇 년 동안 또는 지금까지이 점에 대해 논의 했습니까?

추신-코드 없이이 질문을 남기지 않기 위해 :

일반 포인터 :

void bar(int* ptr) noexcept;
void baz(int* ptr) noexcept;

void foo(int* ptr) noexcept {
    if (*ptr > 42) {
        bar(ptr); 
        *ptr = 42; 
    }
    baz(ptr);
}

고유 한 포인터 :

using std::unique_ptr;
void bar(int* ptr) noexcept;
void baz(unique_ptr<int> ptr) noexcept;

void foo(unique_ptr<int> ptr) noexcept {
    if (*ptr > 42) { 
        bar(ptr.get());
        *ptr = 42; 
    }
    baz(std::move(ptr));
}

8
나는 확실히 ABI 요구 사항이 정확히 무엇인지 모르겠지만,이 레지스터에 구조체를 넣어 금지하지 않습니다
해롤드

6
추측해야한다면 this유효한 위치를 가리키는 포인터가 필요한 사소한 비회원 함수와 관련이 있다고 말할 수 있습니다. unique_ptr있습니다. 이러한 목적으로 레지스터를 흘리면 "레지스터 통과"최적화 전체가 무효가됩니다.
StoryTeller-Unslander Monica

2
itanium-cxx-abi.github.io/cxx-abi/abi.html#calls . 따라서이 동작이 필요했습니다. 왜? itanium-cxx-abi.github.io/cxx-abi/cxx-closed.html 에서 C-7 문제를 검색하십시오. 거기에 약간의 설명이 있지만 너무 자세하지는 않습니다. 그러나 그렇습니다.이 행동은 나에게 논리적이지 않습니다. 이러한 객체는 정상적으로 스택을 통과 할 수 있습니다. 그것들을 쌓아 올리고 참조를 전달하는 것 ( "사소하지 않은"객체의 경우)은 낭비로 보입니다.
geza

6
C ++이 여기에서 자체 원칙을 위반하는 것 같습니다. 140 %는 unique_ptr이 컴파일 후에 사라진다고 확신했습니다. 결국 그것은 컴파일 타임에 알려진 지연된 소멸자 호출 일뿐입니다.
One Man Monkey Squad

7
@ MaximEgorushkin : 직접 작성했다면 스택이 아니라 레지스터에 포인터를 놓았을 것입니다.
einpoklum

답변:


49
  1. 이것은 실제로 ABI 요구 사항입니까, 아니면 특정 시나리오에서 약간 비관적입니까?

한 가지 예는 System V Application Binary Interface AMD64 아키텍처 프로세서 부록 입니다. 이 ABI는 64 비트 x86 호환 CPU (Linux x86_64 아키텍처)에 사용됩니다. Solaris, Linux, FreeBSD, macOS, Linux 용 Windows 서브 시스템에서 수행됩니다.

C ++ 오브젝트에 사소하지 않은 사본 생성자 또는 사소하지 않은 소멸자가 있으면 오브젝트는 보이지 않는 참조로 전달됩니다 (오브젝트는 매개 변수 목록에서 INTEGER 클래스가있는 포인터로 대체 됨).

사소하지 않은 복사 생성자 또는 사소하지 않은 소멸자가있는 오브젝트는 값이 잘 정의되어 있지 않으므로 값으로 전달할 수 없습니다. 함수에서 객체를 반환 할 때 비슷한 문제가 적용됩니다.

사소한 복사 생성자와 사소한 소멸자를 사용하여 하나의 오브젝트를 전달하는 데 2 ​​개의 범용 레지스터 만 사용할 수 있습니다. 즉, sizeof16보다 크지 않은 오브젝트의 값만 레지스터에 전달할 수 있습니다. 호출 규칙, 특히 §7.1 오브젝트 전달 및 리턴에 대한 자세한 처리는 Agner Fog의 호출 규칙을 참조하십시오 . 레지스터에서 SIMD 유형을 전달하기위한 별도의 호출 규칙이 있습니다.

다른 CPU 아키텍처에는 다른 ABI가 있습니다.


  1. ABI가 왜 그런가요? 즉, 구조체 / 클래스의 필드가 레지스터 또는 단일 레지스터에 맞는 경우 해당 레지스터 내에서 필드를 전달할 수없는 이유는 무엇입니까?

구현 세부 사항이지만 예외가 처리되면 스택 해제 중에 자동 저장 기간이 소멸되는 객체는 레지스터가 해당 시간 동안 클로버 되었기 때문에 함수 스택 프레임을 기준으로 주소를 지정할 수 있어야합니다. 스택 해제 코드는 소멸자를 호출하기 위해 객체의 주소가 필요하지만 레지스터의 객체에는 주소가 없습니다.

놀랍게도, 소멸자는 객체에서 작동합니다 .

대상은 구성 기간 ([class.cdtor]), 수명 및 파괴 기간에 저장 영역을 차지합니다.

객체의 ID가 주소 이기 때문에 주소 지정 가능한 스토리지가 할당 되지 않은 경우 객체는 C ++에 존재할 수 없습니다 .

간단한 사본 생성자가 레지스터에 보관 된 객체의 주소가 필요한 경우 컴파일러는 객체를 메모리에 저장하고 주소를 얻을 수 있습니다. 복사 생성자가 사소한 것이 아니라면, 컴파일러는 그것을 메모리에 저장할 수 없으며, 참조를 취하는 복사 생성자를 호출해야하기 때문에 레지스터에 객체의 주소가 필요합니다. 호출 규칙은 복사 생성자가 수신자에 인라인되었는지 여부에 의존 할 수 없습니다.

이것에 대해 생각하는 또 다른 방법은 사소하게 복사 가능한 유형의 경우 컴파일러 가 레지스터의 객체 을 전송하여 필요한 경우 일반 메모리 저장소에서 객체를 복구 할 수 있다는 것입니다. 예 :

void f(long*);
void g(long a) { f(&a); }

System V가있는 x86_64에서 ABI는 다음으로 컴파일됩니다.

g(long):                             // Argument a is in rdi.
        push    rax                  // Align stack, faster sub rsp, 8.
        mov     qword ptr [rsp], rdi // Store the value of a in rdi into the stack to create an object.
        mov     rdi, rsp             // Load the address of the object on the stack into rdi.
        call    f(long*)             // Call f with the address in rdi.
        pop     rax                  // Faster add rsp, 8.
        ret                          // The destructor of the stack object is trivial, no code to emit.

챈들러 카루스 (Chandler Carruth) 자신의 생각을 자극하는 대화 에서, 다른 것들 중에서도 ABI의 변화가 상황을 개선 할 수있는 파괴적인 움직임을 구현하기 위해 필요할 수 있다고 언급 했다. IMO, 새로운 ABI를 사용하는 함수가 명시 적으로 새로운 링크를 갖도록 선택하면 (예를 들어 extern "C++20" {}, 기존 API를 마이그레이션하기위한 새로운 인라인 네임 스페이스에서) 새로운 ABI를 사용하는 함수가 명시 적으로 선택되면 ABI 변경이 중단되지 않을 수 있습니다 . 따라서 새 연결을 사용하여 새 함수 선언에 대해 컴파일 된 코드 만 새 ABI를 사용할 수 있습니다.

호출 된 함수가 인라인 된 경우 ABI가 적용되지 않습니다. 링크 타임 코드 생성과 함께 컴파일러는 다른 변환 단위에 정의 된 함수를 인라인하거나 사용자 지정 호출 규칙을 사용할 수 있습니다.


의견은 긴 토론을위한 것이 아닙니다. 이 대화는 채팅 으로 이동 되었습니다 .
Samuel Liew

8

일반적인 ABI를 사용하면 사소한 소멸자-> 레지스터를 전달할 수 없습니다.

(주석에서 @harold의 예를 사용하여 @MaximEgorushkin의 답변에 대한 요점을 보여줍니다. @ Yakk의 의견에 따라 수정되었습니다.)

컴파일하는 경우 :

struct Foo { int bar; };
Foo test(Foo byval) { return byval; }

당신은 얻는다 :

test(Foo):
        mov     eax, edi
        ret

즉, Foo객체는 test레지스터 ( edi) 로 전달되고 레지스터 ( )로 반환됩니다 eax.

소멸자가 사소하지 않은 경우 ( std::unique_ptr예 : OP)-일반 ABI는 스택에 배치해야합니다. 소멸자가 객체의 주소를 전혀 사용하지 않는 경우에도 마찬가지입니다.

따라서 컴파일하지 않으면 아무 것도없는 소멸자의 극단적 인 경우에도 :

struct Foo2 {
    int bar;
    ~Foo2() {  }
};

Foo2 test(Foo2 byval) { return byval; }

당신은 얻는다 :

test(Foo2):
        mov     edx, DWORD PTR [rsi]
        mov     rax, rdi
        mov     DWORD PTR [rdi], edx
        ret

쓸데없는 적재 및 보관.


나는이 주장에 확신이 없다. 사소한 소멸자는 as-if 규칙을 금지하지 않습니다. 주소가 준수되지 않으면 반드시 필요한 이유가 없습니다. 따라서 준수 컴파일러는 관찰 가능한 동작을 변경하지 않으면 행복하게 레지스터에 넣을 수 있습니다 ( 호출자가 알려진 경우 현재 컴파일러는 실제로 그렇게 합니다 ).
ComicSansMS

1
불행히도, 그것은 다른 방법입니다 (이 중 일부는 이미 이유를 벗어났다는 데 동의합니다). 정확히 말하면 : 당신이 제공 한 이유 std::unique_ptr가 레지스터 의 부적합한 전류 를 통과시킬 수있는 상상할 수있는 ABI를 반드시 렌더링 할 것이라고 확신하지 않습니다 .
ComicSansMS

3
"사소한 소멸자 [CITATION NEEDED]"는 분명히 거짓입니다. 코드가 실제로 주소에 의존하지 않으면, as-if 는 실제 머신에 주소가 존재할 필요가 없음을 의미합니다 . 주소는 존재해야합니다 추상 기계에 있지만, 실제 시스템에 영향을 미치지 않는다 추상 기계에 가지 것들 경우로 제거 할 수있다.
Yakk-Adam Nevraumont

2
@einpoklum 표준에 레지스터가 존재한다는 것은 없습니다. register 키워드는 단지 "주소를 가질 수 없습니다"라고 말합니다. 표준에 관한 한 추상 기계 만 있습니다. "as as"는 실제 머신 구현이 추상 머신이 "있는 것처럼"동작 할 필요가 있고 표준에 의해 정의되지 않은 동작까지만 필요함을 의미합니다. 이제 레지스터에 객체를 두는 것과 관련하여 매우 어려운 문제가 있습니다. 또한 표준에서 언급하지 않은 전화 규칙에는 실질적인 요구가 있습니다.
Yakk-Adam Nevraumont

1
@einpoklum 아니오, 그 추상 기계에서 모든 것은 주소를 가지고 있습니다; 그러나 주소는 특정 상황에서만 볼 수 있습니다. 이 register키워드는 실제 시스템에서 "주소가없는"것을 실제로 어렵게 만드는 것들을 차단하여 실제 시스템이 레지스터에 무언가를 저장하는 것을 간단하게하기위한 것입니다.
Yakk-Adam Nevraumont

2

실제로 일부 플랫폼에서 ABI 요구 사항입니까? (어떻게?) 아니면 특정 시나리오에서 약간 비관적입니까?

보완 단위 경계에서 무언가가 보이는 경우, 그것이 암시 적으로 또는 명시 적으로 정의되는지 여부는 ABI의 일부가됩니다.

ABI가 왜 그런가요?

근본적인 문제는 호출 스택을 위아래로 이동할 때 레지스터가 항상 저장되고 복원된다는 것입니다. 따라서 참조 또는 포인터를 갖는 것은 실용적이지 않습니다.

인라인 및 그로 인한 최적화는 좋은 일이지만 ABI 디자이너는 그 일에 의존 할 수 없습니다. 최악의 경우를 가정하여 ABI를 설계해야합니다. 프로그래머가 ABI가 최적화 수준에 따라 변경된 컴파일러에 매우 만족하지 않을 것이라고 생각합니다.

논리적 복사 작업을 두 부분으로 나눌 수 있으므로 사소한 복사 가능한 유형을 레지스터에 전달할 수 있습니다. 매개 변수는 호출자가 매개 변수를 전달하는 데 사용 된 레지스터에 복사 된 다음 호출자가 로컬 변수에 복사합니다. 로컬 변수에 메모리 위치가 있는지 여부는 수신자의 관심사입니다.

반면에 복사 또는 이동 생성자를 사용해야하는 유형은 복사 작업을 이런 식으로 나눌 수 없으므로 메모리에 전달되어야합니다.

C ++ 표준위원회가 최근 몇 년 동안 또는 지금까지이 점에 대해 논의 했습니까?

표준기구가 이것을 고려했는지 전혀 모른다.

나에게 명백한 해결책은 현재 "중요하지만 지정되지 않은 상태"의 중간 집이 아닌 적절한 파괴적인 움직임을 언어에 추가 한 다음 "사소한 파괴적인 움직임을 허용하는 것으로 유형을 표시하는 방법을 소개하는 것입니다. "사소한 사본을 허용하지 않더라도.

그러나 그러한 솔루션 WOULD는 기존 유형에 대해 구현하기 위해 기존 코드의 ABI를 깨뜨릴 것을 요구합니다. C ++ 11에서는 ABI 중단이 발생했습니다.


적절한 파괴적인 움직임으로 인해 unique_ptr이 레지스터로 전달되는 방법을 자세히 설명 할 수 있습니까? 어 드레서 블 스토리지에 대한 요구 사항을 제거 할 수 있기 때문입니까?
einpoklum

적절한 파괴적인 움직임은 사소한 파괴적인 움직임의 개념을 도입 할 수있게합니다. 이것은 사소한 사본이 오늘날의 사소한 사본과 같은 방식으로 ABI에 의해 분할 될 수있게합니다.
plugwash

그러나 컴파일러가 매개 변수 전달을 규칙적인 이동 또는 복사로 구현 한 다음 "사소한 파괴적 이동"으로 매개 변수의 출처에 상관없이 항상 레지스터를 전달할 수 있도록하는 규칙을 추가하려고합니다.
plugwash

레지스터 크기가 포인터를 가질 수 있지만 unique_ptr 구조를 가질 수 있습니까? sizeof (unique_ptr <T>)는 무엇입니까?
Mel Viso Martinez 5

당신은 혼동 될 수 있습니다 @MelVisoMartinez unique_ptrshared_ptr의미 : shared_ptr<T>당신이 ctor에로 w / 표현 U가 정적 유형 U와 함께 삭제 될 1) PTR X로 파생 된 개체 제공 할 수 있습니다 delete x;여기에 가상 dtor 필요하지 않습니다 (그래서) 2) 또는 심지어 사용자 정의 정리 기능. 이는 런타임 상태가 shared_ptr제어 블록 내에서 사용되어 해당 정보를 인코딩 함을 의미합니다. OTOH unique_ptr는 그러한 기능이 없으며 상태에서 삭제 동작을 인코딩하지 않습니다. 정리를 사용자 정의하는 유일한 방법은 다른 템플리트 인스턴스 (다른 클래스 유형)를 작성하는 것입니다.
curiousguy

-1

먼저 우리는 가치와 참조로 전달한다는 의미로 돌아 가야합니다.

Java 및 SML과 같은 언어의 경우 모든 변수가 스칼라이고 복사 의미론이 내장되어 있으므로 변수 값을 복사하는 것처럼 값으로 전달하는 것이 간단합니다 (참조에 의한 전달도 없음). C ++ 또는 "참조"(이름과 구문이 다른 포인터)로 입력하십시오.

C에는 스칼라 및 사용자 정의 유형이 있습니다.

  • 스칼라에는 복사되는 숫자 또는 추상 값 (포인터가 숫자가 아니며 추상 값이 있음)이 있습니다.
  • 집계 유형에는 초기화 가능한 모든 멤버가 복사됩니다.
    • 제품 유형 (배열 및 구조)의 경우 : 재귀 적으로 모든 구조의 멤버 및 배열 요소가 복사됩니다 (C 함수 구문은 배열을 값으로 직접 전달할 수는 없지만 구조체의 배열 만 전달할 수는 있지만 세부 사항입니다) ).
    • 합계 유형 (유니언)의 경우 : "활성 멤버"의 값이 유지됩니다. 분명히, 멤버 별 복사는 모든 멤버를 초기화 할 수있는 순서가 아닙니다.

C ++에서 사용자 정의 유형은 사용자 정의 사본 의미론을 가질 수 있으며, 이는 자원 소유권과 "딥 카피"작업을 통해 객체를 사용한 진정한 "객체 지향"프로그래밍을 가능하게합니다. 이러한 경우 복사 작업은 실제로 임의 작업을 거의 수행 할 수있는 함수를 호출하는 것입니다.

C ++로 컴파일 된 C 구조체의 경우 "복사"는 여전히 컴파일러가 암시 적으로 생성 한 사용자 정의 복사 작업 (생성자 또는 할당 연산자)을 호출하는 것으로 정의됩니다. 이는 C / C ++ 공통 서브 세트 프로그램의 의미가 C와 C ++에서 다르다는 것을 의미합니다. C에서는 전체 집계 유형이 복사되고 C ++에서는 내재적으로 생성 된 복사 함수가 호출되어 각 멤버를 복사합니다. 최종 결과는 두 경우 모두 각 멤버가 복사되는 것입니다.

(조합 내부의 구조체가 복사 될 때 예외가 있다고 생각합니다.)

따라서 클래스 유형의 경우 새 인스턴스를 만드는 유일한 방법 (유니온 복사본 외부)은 생성자를 사용하는 것입니다 (사소한 컴파일러 생성 생성자를 가진 경우에도).

단항 연산자를 통해 rvalue의 주소를 사용할 수 &는 없지만 rvalue 객체가 없다는 의미는 아닙니다. 및 목적은, 정의에 의해, 어드레스를 가진다 ; 그리고 그 주소는 심지어 구문 구문으로 표현된다 : 클래스 타입의 객체는 생성자에 의해서만 생성 될 수 있고, this포인터를 가진다; 그러나 사소한 유형의 경우 사용자 작성 생성자가 없으므로 this사본을 구성하고 이름 을 지정할 때까지 넣을 장소가 없습니다 .

스칼라 유형의 경우 객체의 값은 객체의 rvalue, 객체에 저장된 순수한 수학적 값입니다.

클래스 유형의 경우 객체 값의 유일한 개념은 객체의 다른 사본입니다. 복사 생성자, 실제 함수로만 만들 수 있습니다 (함수가 너무 사소한 사소한 유형의 경우 때때로 생성자를 호출하지 않고 생성). 즉 , 객체의 값은 실행에 의한 전역 프로그램 상태 변경의 결과입니다 . 수학적으로 액세스하지 않습니다.

따라서 값으로 전달하는 것은 실제로 중요하지 않습니다. 복사 생성자 호출에 의해 전달됩니다 . 복사 생성자는 내부 불변 (내재적 C ++ 속성이 아닌 추상 사용자 속성)을 고려하여 오브젝트 유형의 적절한 의미에 따라 적절한 "복사"조작을 수행해야합니다.

클래스 객체의 값으로 전달은 다음을 의미합니다.

  • 다른 인스턴스를 만들
  • 그런 다음 호출 된 함수가 해당 인스턴스에서 작동하도록합니다.

이 문제는 복사본 자체가 주소를 가진 객체인지 여부와 관련이 없습니다. 모든 함수 매개 변수는 객체이며 주소를 가지고 있습니다 (언어 시맨틱 레벨).

문제는 :

  • 카피는 스칼라와 같이 원래 객체의 순수 수학 값 (true pure rvalue)으로 초기화새 객체입니다 .
  • 또는 copy는 클래스와 마찬가지로 original object의 값입니다 .

사소한 클래스 유형의 경우에도 원본의 멤버 사본 멤버를 정의 할 수 있으므로 사소한 복사 작업 (복사 생성자 및 할당)으로 인해 원본의 순수한 rvalue를 정의 할 수 있습니다. 임의의 특수 사용자 기능으로는 그렇지 않습니다. 원본의 값은 생성 된 사본이어야합니다.

클래스 객체는 호출자가 구성해야합니다. 생성자는 공식적으로 this포인터를 가지고 있지만 형식은 여기에 관련이 없습니다. 모든 객체는 공식적으로 주소를 가지고 있지만 실제로 *&i = 1;는 순수하게 로컬이 아닌 방식으로 주소를 얻는 객체 만 (순수하게 로컬 주소를 사용하는 것과는 달리 ) 잘 정의되어 있어야합니다 주소.

개별적으로 컴파일 된이 두 함수 모두에 주소가있는 것처럼 보이는 경우, 반드시 주소를 통해 전달해야합니다.

void callee(int &i) {
  something(&i);
}

void caller() {
  int i;
  callee(i);
  something(&i);
}

여기에 경우에도이 something(address)순수 함수 또는 매크로이든 (처럼 printf("%p",arg)주소를 저장하거나 다른 기업에 통신 할 수 없습니다) 주소가 아니라 고유의 객체에 대해 정의해야하기 때문에, 우리는 주소로 전달하는 요구 사항이 int독특한있다 정체.

전달 된 주소의 관점에서 외부 함수가 "순수한"지 여부는 알 수 없습니다.

여기서 발신자 가 아닌 간단한 생성자 또는 소멸자 에서 주소를 실제로 사용할 가능성 은 아마도 안전하고 간단한 경로를 취하고 발신자에게 객체에 정체성을 부여하고 주소를 전달 하는 이유 일 것입니다. 확인 생성자의 주소가 아닌 사소한 사용, 건축 후 소멸자하는 것은 일관성이 있음 : 객체의 존재를 통해 동일하게 표시되어야합니다.this

다른 함수와 마찬가지로 사소하지 않은 생성자 또는 소멸자는 this사소한 것이 아닌 일부 객체는 다음과 같은 경우에도 해당 값보다 일관성이 필요한 방식으로 포인터를 사용할 수 있습니다 .

struct file_handler { // don't use that class!
    file_handler () { this->fileno = -1; }
    file_handler (int f) { this->fileno = f; }
    file_handler (const file_handler& rhs) {
        if (this->fileno != -1)
            this->fileno = dup(rhs.fileno);
        else
            this->fileno = -1;
    }
    ~file_handler () {
        if (this->fileno != -1)
            close(this->fileno); 
    }
    file_handler &operator= (const file_handler& rhs);
};

이 경우 포인터를 명시 적으로 사용하더라도 (명시 적 구문 this->) 객체 ID는 관련이 없습니다. 컴파일러는 객체를 비트 단위로 복사하여 객체를 이동하고 "복사 제거"를 수행 할 수 있습니다. 이것은 this특별한 멤버 함수에서 의 사용의 "순도"수준을 기반으로합니다 (주소는 이스케이프되지 않습니다).

그러나 순도는 표준 선언 수준에서 사용할 수있는 속성이 아닙니다 (비 인라인 함수 선언에 순도 설명을 추가하는 컴파일러 확장이 존재 함) 사용할 수없는 코드 순도에 따라 ABI를 정의 할 수 없습니다 (코드는 인라인이 아니고 분석에 사용 가능할 수 있습니다).

순도는 "확실히 순수한"또는 "불순하거나 알려지지 않은"것으로 측정됩니다. 공통 접지 또는 의미의 상한 (실제 최대) 또는 LCM (최소 공통 배수)은 "알 수 없음"입니다. 따라서 ABI는 알려지지 않은 상태로 정착합니다.

요약:

  • 일부 구문에서는 컴파일러가 객체 ID를 정의해야합니다.
  • ABI는 프로그램 클래스의 용어로 정의되며 최적화 될 수있는 특정 사례는 아닙니다.

가능한 미래의 일 :

순도 주석은 일반화 및 표준화하기에 충분히 유용합니까?


1
첫 번째 예는 잘못된 것으로 보입니다. 나는 당신이 일반적으로 요점을 지적한다고 생각하지만, 처음 에는 질문의 코드 와 유사 하다고 생각했습니다 . 그러나 void foo(unique_ptr<int> ptr)클래스 객체 를 value로 가져옵니다 . 이 객체에는 포인터 멤버가 있지만 클래스 객체 자체가 참조로 전달되는 것에 대해 이야기하고 있습니다. (사소하게 복사 할 수 없기 때문에 생성자 / 소멸자가 일관된 정보를 필요로합니다 this.) 그것은 실제적인 주장이며 명시 적으로 참조로 전달하는 첫 번째 예제와 관련이 없습니다 . 이 경우 포인터는 레지스터로 전달됩니다.
Peter Cordes

@PeterCordes " 당신은 문제의 코드와 유사했습니다. "나는 정확히 그렇게했습니다. " 값에 의한 클래스 객체 "네, 아마도 일반적으로 클래스 객체의 "값"과 같은 것이 없기 때문에 수학 이외의 타입에 대한 값은 "값별"이 아니라고 설명해야합니다. " 그 객체는 포인터 멤버를 가지고있다 " "스마트 한 ptr"의 ptr-like 성질은 관련이 없다; "스마트 ptr"의 ptr 멤버도 마찬가지입니다. ptr은 다음과 같은 스칼라 일뿐 int입니다. "소유권"이 "ptr을 나르는"것과 아무 관련이 없음을 보여주는 "smart fileno"예제를 작성했습니다.
curiousguy

1
클래스 객체의 값은 객체 표현입니다. 의 경우 unique_ptr<T*>, 이것은 크기와 레이아웃이 같고 T*레지스터에 맞습니다. 사소하게 복사 가능한 클래스 객체는 대부분의 호출 규칙과 같이 x86-64 System V의 레지스터 으로 전달 될 수 있습니다 . 예를 들어 수신자 발신자의 주소 인 경우 와 같이 객체 의 사본 을 만듭니다. 여기서 asm 구현 세부 사항뿐만 아니라 C ++ 레벨에서 참조 전달했기 때문 입니다. unique_ptrint&i i
Peter Cordes

1
Err, 내 마지막 의견 수정. 단지 객체 의 사본 을 만드는 것이 아닙니다 unique_ptr. 그것을 사용하고 std::move있기 때문에 그것을 복사하는 것이 안전합니다 unique_ptr. 그러나 사소하게 복사 가능한 유형의 경우 예, 전체 집계 객체를 복사합니다. 이것이 단일 멤버 인 경우 올바른 호출 규칙은 해당 유형의 스칼라와 동일하게 취급합니다.
Peter Cordes

1
더 좋아 보인다. 참고 : C ++로 컴파일 된 C 구조체의 경우-C ++ 의 차이점을 알려주는 유용한 방법이 아닙니다. C ++에서는 C ++ struct{}구조체입니다. 아마도 "plain structs"또는 "C와 달리"라고 말해야합니다. 그렇기 때문에 차이가 있습니다. atomic_int구조체 멤버로 사용 하는 경우 C는 원자 적으로 복사하지 않고 삭제 된 복사 생성자에서 C ++ 오류가 발생합니다. volatile멤버 가있는 구조체에서 C ++의 기능을 잊어 버렸습니다 . C는 당신이 struct tmp = volatile_struct;모든 것을 복사 하도록 할 것입니다 (SeqLock에 유용합니다); C ++은 그렇지 않습니다.
Peter Cordes
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.