C는 C ++에서 std :: less에 해당합니까?


26

나는 최근에 일의 정의되지 않은 동작에 질문에 대답 한 p < q경우 C에서 pq다른 개체 / 배열에 대한 포인터이다. C ++ <은이 경우 와 동일한 (정의되지 않은) 동작을 하지만 포인터를 비교할 수있을 때 std::less와 동일한 것을 반환하고 <할 수없는 경우 일관된 순서 를 반환하는 표준 라이브러리 템플릿 을 제공합니다 .

C는 임의의 포인터를 같은 유형으로 안전하게 비교할 수있는 유사한 기능을 가진 것을 제공합니까? 나는 C11 표준을 살펴 보았지만 아무것도 찾지 못했지만 C에 대한 내 경험은 C ++보다 수십 배 작아서 쉽게 놓칠 수있었습니다.


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

답변:


20

플랫 메모리 모델 (기본적으로 모든 것)을 사용하는 구현에서는 캐스트가 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 비트 "오프셋"부분 만 포인터로 사용됩니다. .)

예를 들어 기본적으로 플랫이라고 가정하거나 #ifdefx86 리얼 모드를 감지 uintptr_t하고 16 비트 반으로 분할 seg -= off>>4; off &= 0xf;한 다음 해당 부분을 다시 32 비트 숫자로 결합하는 등 다양한 특정 플랫폼에 대한 코드를 수동으로 추가 할 수 있습니다 .


세그먼트가 동일하지 않은 경우 왜 UB입니까?
Acorn

@Acorn : 다른 방법으로 말할 수 있습니다. 결정된. 동일한 객체에 대한 포인터는 동일한 세그먼트, 그렇지 않으면 UB를 갖습니다.
Peter Cordes

그러나 어쨌든 그것이 UB라고 생각합니까? (거꾸로 된 논리 여부, 실제로는 눈치 채지 못했습니다)
Acorn

p < q다른 객체를 가리키는 경우 C의 UB입니까? 알고 p - q있습니다.
Peter Cordes

1
@Acorn : 어쨌든, UB가없는 프로그램에서 별칭 (다른 seg : off, 동일한 선형 주소)을 생성하는 메커니즘을 보지 못했습니다. 따라서 컴파일러가이를 피하기 위해 벗어나는 것과는 다릅니다. 객체에 대한 모든 액세스는 해당 객체의 seg값과 해당 객체가 시작되는 세그먼트 내의 오프셋보다 큰 오프셋을 사용합니다. C는 UB를 사용하여 tmp = a-b다음 과 같은 것을 포함하여 다른 객체에 대한 포인터 사이에서 많은 것을 수행 b[tmp]합니다 a[0]. 세그먼트 포인터 앨리어싱에 대한이 토론은 왜 디자인 선택이 적합한 지에 대한 좋은 예입니다.
Peter Cordes

17

나는 이 문제를 해결하기 위해 한 번 시도하고 겹치는 객체에 대해 작동하는 솔루션을 찾았으며 대부분의 경우 컴파일러가 "일반적인"일을한다고 가정했습니다.

먼저 중간 사본없이 표준 C에서 memmove를 구현하는 방법 의 제안 을 구현할 수 있습니까? 다음이가 작업 캐스트를하지 않는 경우 uintptr(하나의 래퍼 형 uintptr_t또는 unsigned long long여부에 따라 uintptr_t그리고 가장 가능성이 정확한 결과를 얻을 수 (비록 그것이 아마 어쨌든 문제가 아니다 것)이 가능합니다)

#include <stdint.h>
#ifndef UINTPTR_MAX
typedef unsigned long long uintptr;
#else
typedef uintptr_t uintptr;
#endif

int pcmp(const void *p1, const void *p2, size_t len)
{
    const unsigned char *s1 = p1;
    const unsigned char *s2 = p2;
    size_t l;

    /* Check for overlap */
    for( l = 0; l < len; l++ )
    {
        if( s1 + l == s2 || s1 + l == s2 + len - 1 )
        {
            /* The two objects overlap, so we're allowed to
               use comparison operators. */
            if(s1 > s2)
                return 1;
            else if (s1 < s2)
                return -1;
            else
                return 0;
        }
    }

    /* No overlap so the result probably won't really matter.
       Cast the result to `uintptr` and hope the compiler
       does the "usual" thing */
    if((uintptr)s1 > (uintptr)s2)
        return 1;
    else if ((uintptr)s1 < (uintptr)s2)
        return -1;
    else
        return 0;
}

5

C는 임의의 포인터를 안전하게 비교할 수있는 유사한 기능을 가진 것을 제공합니까?

아니


먼저 객체 포인터 만 고려하자 . 함수 포인터 는 완전히 다른 관심사를 가져옵니다.

2 포인터 p1, p2는 다른 인코딩을 가질 수 있으며 0이 아닌 p1 == p2경우에도 동일한 주소를 가리킬 수 있습니다 memcmp(&p1, &p2, sizeof p1). 이러한 아키텍처는 거의 없습니다.

그러나 이러한 포인터를로 변환 uintptr_t하는 데 동일한 정수 결과가 필요하지 않습니다 (uintptr_t)p1 != (uinptr_t)p2.

(uintptr_t)p1 < (uinptr_t)p2 그 자체로는 합법적 인 코드이므로 원하는 기능을 제공하지 못할 수도 있습니다.


코드가 실제로 관련없는 포인터를 비교해야하는 경우 도우미 함수를 작성 less(const void *p1, const void *p2)하고 플랫폼 별 코드를 수행하십시오.

혹시:

// return -1,0,1 for <,==,> 
int ptrcmp(const void *c1, const void *c1) {
  // Equivalence test works on all platforms
  if (c1 == c2) {
    return 0;
  }
  // At this point, we know pointers are not equivalent.
  #ifdef UINTPTR_MAX
    uintptr_t u1 = (uintptr_t)c1;
    uintptr_t u2 = (uintptr_t)c2;
    // Below code "works" in that the computation is legal,
    //   but does it function as desired?
    // Likely, but strange systems lurk out in the wild. 
    // Check implementation before using
    #if tbd
      return (u1 > u2) - (u1 < u2);
    #else
      #error TBD code
    #endif
  #else
    #error TBD code
  #endif 
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.