memcpy (0,0,0)을 수행하는 것이 안전합니까?


80

나는 C 표준에 익숙하지 않으니 참아주세요.

그것이 memcpy(0,0,0)안전하다고 표준에 의해 보장되는지 알고 싶습니다 .

내가 찾을 수있는 유일한 제한은 메모리 영역이 겹치면 동작이 정의되지 않는다는 것입니다.

그러나 여기서 메모리 영역이 겹친다 고 생각할 수 있습니까?


7
수학적으로 두 개의 빈 세트의 교차점은 비어 있습니다.
Benoit 2011 년

(x) libC가 원하는지 확인하고 싶었지만 asm (elibc / glibc)이므로 이른 아침에는 너무 복잡합니다. :)
Kevin

16
+1 저는이 질문이 매우 이상한 경우이고 memcpy(0,0,0)제가 본 C 코드 중 가장 이상한 부분 중 하나 라고 생각하기 때문에이 질문을 좋아합니다 .
templatetypedef

2
@eq 정말 알고 싶습니까, 아니면 원하는 상황이 없다는 것을 암시합니까? 실제 호출이 다음과 같을 수 있다고 생각 memcpy(outp, inp, len)하십니까? 그리고이 곳 코드에서 발생할 수 outpinp동적으로 할당되며 처음이다 0? 이것은 예를 들어 p = realloc(p, len+n)when plenare 와 함께 작동합니다 0. 필자는 그런 memcpy호출 을 사용 했습니다. 기술적으로는 UB이지만 작동하지 않고 예상하지도 않는 구현을 본 적이 없습니다.
Jim Balter 2011 년

7
@templatetypedef memcpy(0, 0, 0)는 정적 호출이 아닌 동적 호출을 나타 내기위한 것입니다. 즉, 해당 매개 변수 값은 리터럴 일 필요가 없습니다.
Jim Balter 2011 년

답변:


71

나는 C 표준 (ISO / IEC 9899 : 1999)의 초안 버전을 가지고 있으며, 그 호출에 대해 몇 가지 재미있는 이야기를 할 수 있습니다. 우선, 다음과 관련하여 (§7.21.1 / 2)를 언급 memcpy합니다.

size_tn으로 선언 된 인수 가 함수의 배열 길이를 지정하는 경우 n은 해당 함수에 대한 호출에서 값 0을 가질 수 있습니다. 이 하위 절의 특정 함수에 대한 설명에서 달리 명시 적으로 언급되지 않는 한, 그러한 호출에 대한 포인터 인수는 7.1.4에 설명 된대로 여전히 유효한 값을 가져야합니다 . 이러한 호출에서 문자를 찾는 함수는 발생을 찾지 못하고 두 문자 시퀀스를 비교하는 함수는 0을 반환하며 문자를 복사하는 함수는 0 개의 문자를 복사합니다.

여기에 표시된 참조는 다음을 가리 킵니다.

함수에 대한 인수에 유효하지 않은 값이있는 경우 (예 : 함수 도메인 외부의 값, 프로그램의 주소 공간 외부의 포인터 , 널 포인터 또는 해당 매개 변수가 수정 불가능한 스토리지에 대한 포인터 인수가 가변적 인 함수에서 예상하지 않는 유형 (프로모션 후) 또는 유형 (승격 후) 인 경우 동작이 정의되지 않습니다 .

따라서 C 사양에 따르면

널 포인터는 "잘못된 값"으로 간주되기 때문에 정의되지 않은 동작이 발생합니다.

즉, 이렇게하면 실제 구현이 memcpy중단되면 완전히 놀랍습니다. 제가 생각할 수있는 대부분의 직관적 구현은 0 바이트를 복사한다고 말하면 아무 작업도 수행하지 않기 때문입니다.


3
표준 초안에서 인용 된 부분이 최종 문서에서 동일하다는 것을 확인할 수 있습니다. 이러한 호출에는 문제가 없어야하지만 여전히 의존하고있는 정의되지 않은 동작입니다. 따라서 "보장되어 있습니까"에 대한 대답은 "아니오"입니다.
DevSolar 2011 년

8
프로덕션에서 사용할 구현은 이러한 호출에 대해 no-op 이외의 다른 것을 생성하지 않지만 그렇지 않은 경우 허용되고 합리적입니다 ... 예 : C 인터프리터 또는 오류 검사 기능이있는 증강 컴파일러 부적합하기 때문에 전화하십시오. 물론 표준이 호출을 허용했다면 그것은 합리적이지 않을 것입니다 realloc(0, 0). 사용 사례는 비슷하며 둘 다 사용했습니다 (질문 아래의 내 의견 참조). 표준이이 UB를 만드는 것은 무의미하고 불행한 일입니다.
Jim Balter 2011 년

5
"이렇게한다면 memcpy의 실제 구현이 망가 졌다면 정말 놀랐을 것입니다."-그럴 것입니다. 실제로 유효한 포인터로 길이 0을 전달하면 실제로 65536 바이트를 복사했습니다. (그 루프는 길이를 줄인 다음 테스트했습니다).
MM

14
@MattMcNabb 구현이 중단되었습니다.
Jim Balter 2014-07-13

3
@MattMcNabb : 아마도 "실제"에 "올바른"을 추가하십시오. 나는 우리 모두가 오래된 게토 C 도서관에 대한별로 좋아하지 않는 기억을 가지고 있다고 생각하며, 우리 중 얼마나 많은 사람들이 그 기억을 되찾아 감사하는지 잘 모르겠습니다. :)
tmyklebu 2014 년

24

재미를 위해 gcc-4.9의 릴리스 노트는 옵티마이 저가 이러한 규칙을 사용함을 나타내며 예를 들어

copy(0,0,0)호출 될 때 예기치 않은 결과를 제공합니다 ( https://gcc.gnu.org/gcc-4.9/porting_to.html 참조 ).

나는 gcc-4.9 동작에 대해 다소 양가 적입니다. 동작은 표준을 준수 할 수 있지만 memmove (0,0,0)을 호출 할 수있는 것은 때때로 이러한 표준에 대한 유용한 확장입니다.


2
흥미 롭군. 나는 당신의 양면성을 이해하지만 이것이 C에서 최적화의 핵심입니다. 컴파일러 개발자가 특정 규칙을 따른다고 가정 하고 따라서 일부 최적화가 유효하다고 추론 합니다 (규칙을 따르는 경우).
Matthieu M.

2
@tmyklebu : 주어지면 char *p = 0; int i=something;(p+i)을 평가 하면 값 i이 0 인 경우에도 정의되지 않은 동작이 생성 됩니다.
supercat 2014-12-06

1
@tmyklebu : 널 포인터 트랩에서 모든 포인터 산술 (비교 제외)을 갖는 것은 IMHO가 좋은 일입니다. memcpy()0이 아닌 카운트를 보장하기 전에 인수에 대한 포인터 산술을 수행 할 수 있는지 여부 는 또 다른 질문입니다. [표준을 설계하는 경우, 아마도 pnull이면 p+0트랩 할 수 있지만 memcpy(p,p,0)아무것도하지 않을 것임을 지정할 것입니다 ]. 훨씬 더 큰 문제인 IMHO는 대부분의 정의되지 않은 동작의 개방형입니다. 정말 몇 가지가 있지만 해야 정의되지 않은 동작 (예를 들어 전화 대표 free(p)...
supercat

1
... 그리고 나중에 수행 p[0]=1;) 불확실한 결과를 산출하는 것으로 지정되어야하는 많은 것들이 있습니다 (예 : 관련되지 않은 포인터 간의 관계 비교는 다른 비교와 일치하는 것으로 지정되어서는 안되지만 0을 산출하는 것으로 지정되어야합니다. 또는 1) 또는 구현 정의 된 것보다 약간 느슨한 동작을 생성하도록 지정되어야합니다 (예 : 정수 오버플로의 가능한 모든 결과를 문서화해야하지만 특정 경우에 어떤 결과가 발생할지 지정하지 않아야 함).
supercat

8
나는 유래 배지를하지 않는 이유 누군가가 말해주세요 :-) "불꽃 전쟁 시작"
user1998586을

0

memmoveGit 2.14.x (2017 년 3 분기) 에서이 사용법을 고려할 수도 있습니다.

참조 168e635 커밋 (2017 7월 16일를) 및 1,773,664을 투입 , f331ab9 커밋 , 5,783,980 커밋 에 의해 (2017년 7월 15일) 르네 Scharfe을 ( rscharfe) .
(Merged by Junio ​​C gitsterHamano -- in commit 32f9025 , 11 Aug 2017)

지정된 요소 수를 기반으로 크기를 계산하고 해당 숫자가 0 일 때 포인터를 지원 하는 도우미 매크로MOVE_ARRAY 를 사용합니다 NULL.
로 원시 memmove(3)호출을 사용 NULL하면 컴파일러가 이후 NULL검사 를 (과도하게) 최적화 할 수 있습니다 .

MOVE_ARRAY겹칠 수있는 배열 항목 범위를 이동하기위한 안전하고 편리한 도우미를 추가합니다.
요소 크기를 추론하고, 바이트 단위로 크기를 얻기 위해 자동으로 안전하게 곱하고, 요소 크기를 비교하여 기본 유형 안전성 검사를 수행 memmove(3)하며 NULL0 요소가 이동되는 경우 포인터를 지원하는 것과는 다릅니다 .

:

그것은 사용하는 매크로BUILD_ASSERT_OR_ZERO (과 표현으로, 빌드 시간 의존성을 주장 @cond충족해야 컴파일시 조건 인을).
조건이 참이 아니거나 컴파일러에서 평가할 수없는 경우 컴파일이 실패합니다.

예:


2
"영리한"과 "멍청한"이 반의어라고 생각하는 옵티마이 저의 존재로 인해 n에 대한 테스트가 필요하지만 memmove (any, any, 0)가 작동하지 않을 것임을 보장하는 구현에서는 일반적으로보다 효율적인 코드가 가능합니다. . 컴파일러가 memmove ()에 대한 호출을 memmoveAtLeastOneByte ()에 대한 호출로 대체 할 수없는 경우, 영리하고 어리석은 컴파일러의 "최적화"를 방지하기위한 해결 방법은 일반적으로 컴파일러가 제거 할 수없는 추가 비교 결과를 가져옵니다.
supercat

-3

아니요, memcpy(0,0,0)안전하지 않습니다. 표준 라이브러리는 해당 호출에서 실패하지 않을 것입니다. 그러나 테스트 환경에서 버퍼 오버런 및 기타 문제를 감지하기 위해 memcpy ()에 추가 코드가있을 수 있습니다. 그리고 memcpy ()의 특수 버전이 NULL 포인터에 어떻게 반응하는지는 정의되지 않았습니다.


귀하의 답변이 이미 안전하지 않다고 표준에서 발췌 한 기존 답변에 추가하는 것을 볼 수 없습니다.
Matthieu M.

memcpy ( "in a testing environment")의 특정 구현이 memcpy (0, 0, 0)에 문제를 일으킬 수있는 이유를 작성했습니다. 나는 새롭다 고 생각한다.
Kai Petzke
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.