플랫 메모리 모델 (기본적으로 모든 것)을 사용하는 구현에서는 캐스트가 uintptr_t
작동합니다.
(그러나 C에서 UB 인 객체 외부에서 포인터를 형성하는 문제를 포함하여 포인터를 부호있는 것으로 취급해야하는지 여부에 대한 논의는 64 비트 x86에서 포인터 비교에 서명 또는 서명 해제해야합니까?를 참조하십시오 .)
그러나 비 플랫 메모리 모델과 시스템이 존재 할, 그리고 그들에 대해 생각하는 것은 다른 사양을 가진 C ++와 같은 현재의 상황을 설명 할 수 <
대를 std::less
.
지점의 파트 <
C에 UB (또는 C ++ 개정 적어도 지정되지 않은)에있는 별도의 객체에 대한 포인터에 비 플랫 메모리 모델을 포함 이상한 기계를 허용하는 것입니다.
잘 알려진 예는 포인터가 segment : offset 인 x86-16 리얼 모드이며를 통해 20 비트 선형 주소를 형성합니다 (segment << 4) + offset
. 동일한 선형 주소는 여러 개의 다른 seg : off 조합으로 표시 될 수 있습니다.
std::less
이상한 ISA의 포인터에 대한 C ++ 은 비용이 많이들 수 있습니다 . 예를 들어 x86-16에서 segment : offset을 "정규화"하면 오프셋 <= 15를 갖습니다. 그러나 이를 구현하는 이식 가능한 방법은 없습니다 . (또는 포인터 객체의 객체 표현) 을 정규화하는 데 필요한 조작 uintptr_t
은 구현에 따라 다릅니다.
그러나 C ++ std::less
가 비싸야하는 시스템에서도 <
그럴 필요는 없습니다. 예를 들어, 객체가 한 세그먼트 내에 들어가는 "대형"메모리 모델을 가정 <
하면 오프셋 부분 만 비교할 수 있으며 세그먼트 부분과도 상관이 없습니다. (동일한 객체 내부의 포인터는 동일한 세그먼트를 갖습니다. 그렇지 않으면 C의 UB입니다. C ++ 17은 단순히 "지정되지 않음"으로 변경되어 정규화를 건너 뛰고 오프셋을 비교할 수 있습니다.) 이것은 모든 부분에 대한 모든 포인터를 가정합니다. 개체의는 항상 같은 seg
값을 사용하지만 정규화되지는 않습니다. 이것이 바로 "거대한"메모리 모델이 아니라 ABI가 "큰"을 요구하는 것입니다. ( 의견 토론 참조 ).
(이러한 메모리 모델의 최대 오브젝트 크기는 64kiB이지만, 이러한 최대 크기의 오브젝트를위한 공간이있는 훨씬 더 큰 최대 총 주소 공간입니다. ISO C를 사용하면 구현시 최대 값 (부호없는) size_t
은을 나타낼 수 있습니다 SIZE_MAX
. 예를 들어 플랫 메모리 모델 시스템에서도 GNU C는 최대 객체 크기를 제한 PTRDIFF_MAX
하여 크기 계산이 부호있는 오버플로를 무시할 수 있습니다.) 이 답변 과 설명은 주석으로 표시됩니다.
세그먼트보다 큰 객체를 허용 p++
하려면 배열을 반복 할 때 또는 인덱싱 / 포인터 산술을 수행 할 때 포인터의 오프셋 부분이 오버플로 될 염려가있는 "거대한"메모리 모델이 필요 합니다. 이것은 어디에서나 코드를 느리게 만들지 만 p < q
"거대한"메모리 모델을 대상으로하는 구현은 일반적으로 모든 포인터를 항상 정규화 상태로 유지하도록 선택하기 때문에 다른 객체에 대한 포인터에서 작동 할 수 있습니다. 근거리, 원거리 및 거대한 포인터 란 무엇입니까?를 참조하십시오 . -x86 리얼 모드에 대한 일부 실제 C 컴파일러에는 달리 선언하지 않는 한 모든 포인터의 기본값이 "거대한"인 "거대한"모델을 컴파일 할 수있는 옵션이 있습니다.
x86 리얼 모드 세그먼테이션은 가능한 비 플랫 메모리 모델 일뿐만 아니라 C / C ++ 구현에 의해 어떻게 처리되는지를 설명하는 유용한 구체적인 예일뿐입니다. 실제 구현에서는 포인터 far
대 vs. 개념을 사용하여 ISO C를 확장 near
하여 프로그래머가 일부 공통 데이터 세그먼트를 기준으로 16 비트 오프셋 부분을 저장 / 전달하는 것만으로 도망 갈 수있는 시점을 선택할 수 있습니다.
그러나 순수한 ISO C 구현은 작은 메모리 모델 (16 비트 포인터가있는 동일한 64kiB의 코드를 제외한 모든 것) 또는 모든 포인터가 32 비트 인 크거나 큰 것 중에서 선택해야합니다. 일부 루프는 오프셋 부분 만 증가시켜 최적화 할 수 있지만 포인터 객체는 더 작게 최적화 할 수 없습니다.
주어진 구현에 대한 마법 조작이 무엇인지 알고 있다면 순수한 C로 구현할 수 있습니다. 문제는 시스템마다 다른 주소 지정을 사용하고 세부 정보는 휴대용 매크로로 매개 변수화되지 않는다는 것입니다.
또는 어쩌면 그렇지 않을 수도 있습니다. 주소의 세그먼트 부분이 색인이고 시프트 된 값이 아닌 실제 모드 대신 x86 보호 모드와 같은 특수 세그먼트 테이블이나 무언가에서 무언가를 찾는 것이 포함될 수 있습니다. 보호 모드에서 부분적으로 겹치는 세그먼트를 설정할 수 있으며 주소의 세그먼트 선택기 부분이 해당 세그먼트 기본 주소와 동일한 순서로 정렬되지 않아도됩니다. GDT 및 / 또는 LDT가 프로세스에서 읽을 수있는 페이지에 매핑되지 않은 경우 x86 보호 모드에서 seg : off 포인터에서 선형 주소를 가져 오는 경우 시스템 호출이 필요할 수 있습니다.
(물론 x86의 주류 OS는 플랫 메모리 모델을 사용하므로 세그먼트베이스는 항상 0 (스레드 로컬 스토리지를 사용 fs
하거나 gs
세그먼트를 사용하는 경우 제외 )이며 32 비트 또는 64 비트 "오프셋"부분 만 포인터로 사용됩니다. .)
예를 들어 기본적으로 플랫이라고 가정하거나 #ifdef
x86 리얼 모드를 감지 uintptr_t
하고 16 비트 반으로 분할 seg -= off>>4; off &= 0xf;
한 다음 해당 부분을 다시 32 비트 숫자로 결합하는 등 다양한 특정 플랫폼에 대한 코드를 수동으로 추가 할 수 있습니다 .