나는 C 표준에 익숙하지 않으니 참아주세요.
그것이 memcpy(0,0,0)
안전하다고 표준에 의해 보장되는지 알고 싶습니다 .
내가 찾을 수있는 유일한 제한은 메모리 영역이 겹치면 동작이 정의되지 않는다는 것입니다.
그러나 여기서 메모리 영역이 겹친다 고 생각할 수 있습니까?
나는 C 표준에 익숙하지 않으니 참아주세요.
그것이 memcpy(0,0,0)
안전하다고 표준에 의해 보장되는지 알고 싶습니다 .
내가 찾을 수있는 유일한 제한은 메모리 영역이 겹치면 동작이 정의되지 않는다는 것입니다.
그러나 여기서 메모리 영역이 겹친다 고 생각할 수 있습니까?
memcpy(0,0,0)
제가 본 C 코드 중 가장 이상한 부분 중 하나 라고 생각하기 때문에이 질문을 좋아합니다 .
memcpy(outp, inp, len)
하십니까? 그리고이 곳 코드에서 발생할 수 outp
및 inp
동적으로 할당되며 처음이다 0
? 이것은 예를 들어 p = realloc(p, len+n)
when p
및 len
are 와 함께 작동합니다 0
. 필자는 그런 memcpy
호출 을 사용 했습니다. 기술적으로는 UB이지만 작동하지 않고 예상하지도 않는 구현을 본 적이 없습니다.
memcpy(0, 0, 0)
는 정적 호출이 아닌 동적 호출을 나타 내기위한 것입니다. 즉, 해당 매개 변수 값은 리터럴 일 필요가 없습니다.
답변:
나는 C 표준 (ISO / IEC 9899 : 1999)의 초안 버전을 가지고 있으며, 그 호출에 대해 몇 가지 재미있는 이야기를 할 수 있습니다. 우선, 다음과 관련하여 (§7.21.1 / 2)를 언급 memcpy
합니다.
size_t
n으로 선언 된 인수 가 함수의 배열 길이를 지정하는 경우 n은 해당 함수에 대한 호출에서 값 0을 가질 수 있습니다. 이 하위 절의 특정 함수에 대한 설명에서 달리 명시 적으로 언급되지 않는 한, 그러한 호출에 대한 포인터 인수는 7.1.4에 설명 된대로 여전히 유효한 값을 가져야합니다 . 이러한 호출에서 문자를 찾는 함수는 발생을 찾지 못하고 두 문자 시퀀스를 비교하는 함수는 0을 반환하며 문자를 복사하는 함수는 0 개의 문자를 복사합니다.
여기에 표시된 참조는 다음을 가리 킵니다.
함수에 대한 인수에 유효하지 않은 값이있는 경우 (예 : 함수 도메인 외부의 값, 프로그램의 주소 공간 외부의 포인터 , 널 포인터 또는 해당 매개 변수가 수정 불가능한 스토리지에 대한 포인터 인수가 가변적 인 함수에서 예상하지 않는 유형 (프로모션 후) 또는 유형 (승격 후) 인 경우 동작이 정의되지 않습니다 .
따라서 C 사양에 따르면
memcpy(0, 0, 0)
널 포인터는 "잘못된 값"으로 간주되기 때문에 정의되지 않은 동작이 발생합니다.
즉, 이렇게하면 실제 구현이 memcpy
중단되면 완전히 놀랍습니다. 제가 생각할 수있는 대부분의 직관적 구현은 0 바이트를 복사한다고 말하면 아무 작업도 수행하지 않기 때문입니다.
realloc(0, 0)
. 사용 사례는 비슷하며 둘 다 사용했습니다 (질문 아래의 내 의견 참조). 표준이이 UB를 만드는 것은 무의미하고 불행한 일입니다.
재미를 위해 gcc-4.9의 릴리스 노트는 옵티마이 저가 이러한 규칙을 사용함을 나타내며 예를 들어
int copy (int* dest, int* src, size_t nbytes) {
memmove (dest, src, nbytes);
if (src != NULL)
return *src;
return 0;
}
copy(0,0,0)
호출 될 때 예기치 않은 결과를 제공합니다 ( https://gcc.gnu.org/gcc-4.9/porting_to.html 참조 ).
나는 gcc-4.9 동작에 대해 다소 양가 적입니다. 동작은 표준을 준수 할 수 있지만 memmove (0,0,0)을 호출 할 수있는 것은 때때로 이러한 표준에 대한 유용한 확장입니다.
char *p = 0; int i=something;
식 (p+i)
을 평가 하면 값 i
이 0 인 경우에도 정의되지 않은 동작이 생성 됩니다.
memcpy()
0이 아닌 카운트를 보장하기 전에 인수에 대한 포인터 산술을 수행 할 수 있는지 여부 는 또 다른 질문입니다. [표준을 설계하는 경우, 아마도 p
null이면 p+0
트랩 할 수 있지만 memcpy(p,p,0)
아무것도하지 않을 것임을 지정할 것입니다 ]. 훨씬 더 큰 문제인 IMHO는 대부분의 정의되지 않은 동작의 개방형입니다. 정말 몇 가지가 있지만 해야 정의되지 않은 동작 (예를 들어 전화 대표 free(p)
...
p[0]=1;
) 불확실한 결과를 산출하는 것으로 지정되어야하는 많은 것들이 있습니다 (예 : 관련되지 않은 포인터 간의 관계 비교는 다른 비교와 일치하는 것으로 지정되어서는 안되지만 0을 산출하는 것으로 지정되어야합니다. 또는 1) 또는 구현 정의 된 것보다 약간 느슨한 동작을 생성하도록 지정되어야합니다 (예 : 정수 오버플로의 가능한 모든 결과를 문서화해야하지만 특정 경우에 어떤 결과가 발생할지 지정하지 않아야 함).
memmove
Git 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 gitster
Hamano -- in commit 32f9025 , 11 Aug 2017)
지정된 요소 수를 기반으로 크기를 계산하고
해당 숫자가 0 일 때 포인터를 지원 하는 도우미 매크로MOVE_ARRAY
를 사용합니다 NULL
.
로 원시 memmove(3)
호출을 사용 NULL
하면 컴파일러가 이후 NULL
검사 를 (과도하게) 최적화 할 수 있습니다 .
MOVE_ARRAY
겹칠 수있는 배열 항목 범위를 이동하기위한 안전하고 편리한 도우미를 추가합니다.
요소 크기를 추론하고, 바이트 단위로 크기를 얻기 위해 자동으로 안전하게 곱하고, 요소 크기를 비교하여 기본 유형 안전성 검사를 수행memmove(3)
하며NULL
0 요소가 이동되는 경우 포인터를 지원하는 것과는 다릅니다 .
#define MOVE_ARRAY(dst, src, n) move_array((dst), (src), (n), sizeof(*(dst)) + \
BUILD_ASSERT_OR_ZERO(sizeof(*(dst)) == sizeof(*(src))))
static inline void move_array(void *dst, const void *src, size_t n, size_t size)
{
if (n)
memmove(dst, src, st_mult(size, n));
}
예 :
- memmove(dst, src, (n) * sizeof(*dst));
+ MOVE_ARRAY(dst, src, n);
그것은 사용하는 매크로BUILD_ASSERT_OR_ZERO
(과 표현으로, 빌드 시간 의존성을 주장 @cond
충족해야 컴파일시 조건 인을).
조건이 참이 아니거나 컴파일러에서 평가할 수없는 경우 컴파일이 실패합니다.
#define BUILD_ASSERT_OR_ZERO(cond) \
(sizeof(char [1 - 2*!(cond)]) - 1)
예:
#define foo_to_char(foo) \
((char *)(foo) \
+ BUILD_ASSERT_OR_ZERO(offsetof(struct foo, string) == 0))
아니요, memcpy(0,0,0)
안전하지 않습니다. 표준 라이브러리는 해당 호출에서 실패하지 않을 것입니다. 그러나 테스트 환경에서 버퍼 오버런 및 기타 문제를 감지하기 위해 memcpy ()에 추가 코드가있을 수 있습니다. 그리고 memcpy ()의 특수 버전이 NULL 포인터에 어떻게 반응하는지는 정의되지 않았습니다.