programing

중간 복사본 없이 표준 C에서 memmove를 구현하는 방법은 무엇입니까?

lastmoon 2023. 7. 26. 22:21
반응형

중간 복사본 없이 표준 C에서 memmove를 구현하는 방법은 무엇입니까?

내 시스템의 맨 페이지에서:

void *memmove(void *dst, constvoid *src, size_tlen);

설명
는 렌 .memove로 복사합니다.
두 문자열이 중복될 수 있습니다. 복사는 항상 비파괴적으로 수행됩니다.
예의 범절

C99 표준에서:

두 할 때, 의 주소 에 따라 달라집니다.6.5.8.5 두 비 를 할 때 교 가 결 는 키 서 상 다 과 는 따 니 달 위 집 인 라 라 에 치 적 대 리 에 간 공 물 의 소 터 체 주 인 포 ▁6 ▁pointers ▁depends ▁are ▁on ▁pointed ▁to 6 ▁in 다 ▁the ▁result 니 집 개체 또는 불완전한 유형에 대한 두 포인터가 모두 동일한 개체를 가리키거나 두 포인터가 동일한 배열 개체의 마지막 요소를 지나 한 점을 가리킬 경우 동일한 개체를 비교합니다.가리키는 개체가 동일한 집계 개체의 멤버인 경우 나중에 선언된 구조체 멤버에 대한 포인터는 구조체의 이전에 선언된 멤버에 대한 포인터보다 크고, 첨자 값이 큰 배열 요소에 대한 포인터는 첨자 값이 낮은 동일한 배열의 요소에 대한 포인터보다 큽니다.동일한 유니온 개체의 멤버에 대한 모든 포인터가 동일하게 비교됩니다. 만약그표이라는 표현이P배열 개체의 요소를 가리키고 표현식 Q는 동일한 배열 개체의 마지막 요소인 포인터 표현식을 가리킵니다.Q+1 큰비보다 큰 P다른 모든 경우에는 동작이 정의되지 않습니다.

강조점은 저의 것입니다.

그 주 들은장dst그리고.src▁to다▁pointers니▁can▁be있습▁to에 대한 포인터로 변환할 수 있습니다.char엄격한 앨리어싱 문제를 완화하기 위해, 하지만 다른 블록 내부를 가리킬 수 있는 두 포인터를 비교하여 동일한 블록 내부를 가리킬 경우 올바른 순서로 복사할 수 있습니까?

한 해결책은 한해은책결입니다.if (src < dst)하지만 그것은 정의되지 않았습니다.src그리고.dst정의되지 않음1을 는 안 것을 되지 않음"이라고입니다)."정의되지 않음"은 조건이 0 또는 1을 반환한다고 가정해서는 안 된다는 것을 의미합니다(이는 표준의 어휘에서 "지정되지 않음"으로 불림).

은 대은다과같다니습음안다같니▁an습▁is입니다.if ((uintptr_t)src < (uintptr_t)dst)그것은 적어도 명시되지 않았지만, 나는 표준이 그것을 보장하는지 확신할 수 없습니다.src < dst정의되며, 이는 다음과 같습니다.(uintptr_t)src < (uintptr_t)dst)포인터 비교는 포인터 산술에서 정의됩니다.를 들어가 섹션 6.6을 때 은 예들어, 추섹 6.5.6을의 반대 갈 수 .uintptr_t 컴파일러가 술산, 즉컴가일 때 가질 수 입니다.p이 형의입니다.char*:

((uintptr_t)p)+1==((uintptr_t)(p-1)

이것은 단지 예시일 뿐입니다.일반적으로 포인터를 정수로 변환할 때는 거의 말할 수 없습니다.

이것은 순수하게 학문적인 질문입니다, 왜냐하면memmove컴파일러와 함께 제공됩니다.실제로 컴파일러 작성자는 정의되지 않은 포인터를 지정되지 않은 동작과 비교하거나 관련된 플러그마를 사용하여 컴파일러가 강제로 컴파일하도록 할 수 있습니다.memmove정확하게를 들어, 이 구현에는 다음과 같은 스니펫이 있습니다.

if ((uintptr_t)dst < (uintptr_t)src) {
            /*
             * As author/maintainer of libc, take advantage of the
             * fact that we know memcpy copies forwards.
             */
            return memcpy(dst, src, len);
    }

저는 여전히 이 예를 기준이 정의되지 않은 행동으로 너무 지나치다는 증거로 사용하고 싶습니다, 만약 그것이 사실이라면.memmove표준 C에서는 효율적으로 구현할 수 없습니다.예를 들어, 아무도 이 SO 질문에 대답할 때 체크 표시를 하지 않았습니다.

당신 말이 맞는 것 같아요, 구현이 불가능해요.memmove표준 C에서 효율적으로

지역이 겹치는지 테스트할 수 있는 유일한 휴대용 방법은 다음과 같습니다.

for (size_t l = 0; l < len; ++l) {
    if (src + l == dst) || (src + l == dst + len - 1) {
      // they overlap, so now we can use comparison,
      // and copy forwards or backwards as appropriate.
      ...
      return dst;
    }
}
// No overlap, doesn't matter which direction we copy
return memcpy(dst, src, len);

둘 다 구현할 수 없습니다.memcpy또는memmove플랫폼별 구현은 당신이 무엇을 하든지 간에 당신의 엉덩이를 걷어찰 수 있기 때문에 휴대용 코드에서 이 모든 것을 효율적으로 수행할 수 있습니다.하지만 휴대용은memcpy적어도 그럴듯해 보입니다.

C++는 포인터 전문화를 도입했습니다.std::less동일한 유형의 임의의 두 포인터에 대해 작동하도록 정의됩니다.이론상으로는 보다 느릴 수 있습니다.<하지만 비선택적 아키텍처에서는 그렇지 않습니다.

C는 그런 것이 없기 때문에, 어떤 의미에서, C++ 표준은 C가 충분히 정의된 행동을 가지고 있지 않다는 것에 동의합니다.하지만 C++는 그것이 필요합니다.std::map등등.훨씬 더 많은 가능성이 있습니다.std::map구현에 대한 지식이 없는 경우(또는 유사한 경우) 구현할 수 있습니다.memmove구현에 대한 지식 없이 (또는 비슷한 것).

두 메모리 영역이 유효하고 중복되려면 6.5.8.5의 정의된 상황 중 하나에 있어야 합니다.즉, 배열의 두 영역, 결합, 구조 등입니다.

다른 상황이 정의되지 않은 이유는 두 개의 서로 다른 개체가 같은 종류의 포인터로 같은 종류의 메모리에 있지 않을 수 있기 때문입니다.PC 아키텍처에서 주소는 일반적으로 가상 메모리에 대한 32비트 주소에 불과하지만, C는 메모리가 전혀 그렇지 않은 모든 종류의 이상한 아키텍처를 지원합니다.

C가 정의되지 않은 상태로 두는 이유는 상황을 정의할 필요가 없을 때 컴파일러 작성자에게 여유를 주기 위해서입니다.6.5.8.5를 읽는 방법은 C가 지원하고자 하는 아키텍처를 신중하게 설명하는 단락으로, 포인터 비교가 동일한 개체 안에 있지 않으면 의미가 없습니다.

또한 memmove와 memcpy가 컴파일러에 의해 제공되는 이유는 때때로 특수 명령어를 사용하여 대상 CPU에 맞게 조정된 어셈블리로 작성되기 때문입니다.이들은 동일한 효율성으로 C에서 구현될 수 없습니다.

우선, C 표준은 이와 같은 세부 사항에 문제가 있는 것으로 악명 높습니다.문제의 일부는 C가 여러 플랫폼에서 사용되고 표준이 현재 및 미래의 모든 플랫폼을 포함할 수 있을 정도로 추상적이기 때문입니다(지금까지 본 적이 없는 복잡한 메모리 레이아웃을 사용할 수 있음).컴파일러 작성자가 대상 플랫폼에 대해 "올바른 작업"을 수행하기 위해 정의되지 않았거나 구현에 특정한 동작이 많이 있습니다.모든 플랫폼에 대한 세부 정보를 포함하는 것은 비현실적일 것입니다. 대신 C 표준은 컴파일러 작성자에게 이러한 경우에 무슨 일이 일어나는지 문서화하도록 맡깁니다."지정되지 않은" 동작은 C 표준이 결과를 예측할 수 없음을 의미할 뿐입니다.대상 플랫폼 및 컴파일러에 대한 설명서를 읽으면 결과를 예측할 수 있습니다.

두 포인터가 동일한 블록, 메모리 세그먼트 또는 주소 공간을 가리키는지 여부를 결정하는 것은 해당 플랫폼의 메모리가 배치되는 방식에 따라 다르기 때문에 사양에서는 이러한 결정을 내리는 방법을 정의하지 않습니다.컴파일러가 이러한 결정을 내리는 방법을 알고 있다고 가정합니다.당신이 인용한 사양의 부분은 포인터 비교 결과가 포인터의 "주소 공간의 상대적 위치"에 따라 다르다고 말했습니다.여기서 "주소 공간"은 단수형입니다.이 섹션에서는 동일한 주소 공간에 있는 포인터, 즉 직접 비교 가능한 포인터만 참조합니다.포인터가 서로 다른 주소 공간에 있으면 결과는 C 표준에 의해 정의되지 않고 대신 대상 플랫폼의 요구 사항에 의해 정의됩니다.

의 경우에는memmove구현자는 일반적으로 주소가 직접 비교 가능한지 여부를 먼저 결정합니다.그렇지 않은 경우 나머지 기능은 플랫폼별로 다릅니다.대부분의 경우, 서로 다른 메모리 공간에 있는 것만으로도 영역이 중복되지 않고 기능이 다음과 같이 변합니다.memcpy주소가 직접 비교 가능한 경우 첫 번째 바이트에서 시작하여 앞으로 또는 마지막 바이트에서 뒤로 이동하는 간단한 바이트 복사 프로세스입니다.

대체로, C 표준은 어떤 대상 플랫폼에서도 작동하는 간단한 규칙을 작성할 수 없는 많은 부분을 의도적으로 지정하지 않은 채로 남깁니다.그러나 표준 작성자는 일부 사항이 정의되지 않은 이유를 설명하고 "아키텍처 의존적"과 같은 더 설명적인 용어를 사용할 수 있었습니다.

여기 다른 생각이 있습니다만, 저는 그것이 맞는지 모르겠습니다.다음을 방지하기 위해O(len)스티브의 대답을 반복하면, 누군가는 그것을 넣을 수 있습니다.#else조항#ifdef UINTPTR_MAX출연자와 함께uintptr_t실행.만약 그 배역들이unsigned char *로.uintptr_t오프셋이 포인터와 함께 유효할 때마다 정수 오프셋을 추가하여 통근하므로 포인터 비교가 잘 정의됩니다.

이 교환성이 표준에 의해 정의되는지는 확실하지 않지만 포인터의 하위 비트만 실제 숫자 주소이고 상위 비트는 일종의 블랙박스인 경우에도 작동하기 때문에 말이 됩니다.

저는 여전히 표준 C에서 memmove가 효율적으로 구현될 수 없는 것이 사실이라면 표준이 정의되지 않은 동작으로 너무 지나치다는 증거로 이 예를 사용하고 싶습니다.

하지만 증거는 아닙니다.임의의 기계 아키텍처에서 두 개의 임의 포인터를 비교할 수 있다는 보장은 전혀 없습니다.이러한 포인터 비교의 동작은 C 표준 또는 컴파일러에 의해 법제화될 수 없습니다.RAM에서 세그먼트를 구성하는 방법에 따라 다른 결과를 생성하거나 다른 세그먼트에 대한 포인터를 비교할 때 예외를 발생시킬 수 있는 세그먼트 아키텍처를 가진 기계를 상상할 수 있습니다.이것이 행동이 "정의되지 않은" 이유입니다.동일한 컴퓨터에서 동일한 프로그램을 사용하면 실행마다 다른 결과를 얻을 수 있습니다.

두 포인터의 관계를 사용하여 memmove()의 "해법"이 주어진 경우는 모든 메모리 블록이 동일한 주소 공간에서 할당된 경우에만 작동합니다.다행히도, 16비트 x86 코드 시절에는 그렇지 않았지만, 일반적으로 그렇습니다.

언급URL : https://stackoverflow.com/questions/4023320/how-to-implement-memmove-in-standard-c-without-an-intermediate-copy

반응형