Nie jestem dobrze zorientowany w standardzie C, więc proszę o wyrozumiałość.
Chciałbym wiedzieć, czy standard gwarantuje, że memcpy(0,0,0)
jest to bezpieczne.
Jedynym ograniczeniem, jakie mogłem znaleźć, jest to, że jeśli regiony pamięci nakładają się, to zachowanie jest niezdefiniowane ...
Ale czy możemy wziąć pod uwagę, że obszary pamięci nakładają się tutaj?
c
memcpy
language-lawyer
null-pointer
Matthieu M.
źródło
źródło
memcpy(0,0,0)
jest to jeden z najdziwniejszych fragmentów kodu C, jakie widziałem.memcpy(outp, inp, len)
,? I że to może się zdarzyć w kodzie, gdzieoutp
iinp
są dynamicznie przydzielane i są początkowo0
? Działa to np. Zp = realloc(p, len+n)
kiedyp
ilen
są0
. Sam użyłem takiegomemcpy
wywołania - chociaż technicznie jest to UB, nigdy nie spotkałem implementacji, w której nie jest to operacja typu no-op i nigdy się tego nie spodziewam.memcpy(0, 0, 0)
najprawdopodobniej ma reprezentować dynamiczne, a nie statyczne wywołanie ... tj. te wartości parametrów nie muszą być literałami.Odpowiedzi:
Mam roboczą wersję standardu C (ISO / IEC 9899: 1999) i mam kilka fajnych rzeczy do powiedzenia na temat tego połączenia. Na początek, to wspomina (§7.21.1 / 2) w odniesieniu do
memcpy
, żeWskazane tutaj odniesienie wskazuje na to:
Więc wygląda na to, że zgodnie ze specyfikacją C, dzwonienie
memcpy(0, 0, 0)
powoduje niezdefiniowane zachowanie, ponieważ puste wskaźniki są traktowane jako „nieprawidłowe wartości”.
To powiedziawszy, byłbym całkowicie zdziwiony, gdyby jakakolwiek rzeczywista implementacja
memcpy
złamała się, gdybyś to zrobił, ponieważ większość intuicyjnych implementacji, o których przychodzi mi do głowy, nie zrobiłaby nic, gdybyś powiedział, że kopiujesz zero bajtów.źródło
realloc(0, 0)
. Przypadki użycia są podobne i użyłem ich obu (zobacz mój komentarz pod pytaniem). To bezcelowe i niefortunne, że Standard tworzy ten UB.Dla zabawy, uwagi do wydania dla gcc-4.9 wskazują, że jego optymalizator korzysta z tych reguł i na przykład może usunąć warunek w
int copy (int* dest, int* src, size_t nbytes) { memmove (dest, src, nbytes); if (src != NULL) return *src; return 0; }
który następnie daje nieoczekiwane wyniki, gdy
copy(0,0,0)
zostanie wywołany (patrz https://gcc.gnu.org/gcc-4.9/porting_to.html ).Jestem nieco ambiwalentny co do zachowania gcc-4.9; zachowanie może być zgodne ze standardami, ale możliwość wywołania memmove (0,0,0) jest czasami przydatnym rozszerzeniem tych standardów.
źródło
char *p = 0; int i=something;
, ocena wyrażenia(p+i)
przyniesie niezdefiniowane zachowanie, nawet jeślii
wynosi zero.memcpy()
powinno się zezwolić na wykonywanie jakiejkolwiek arytmetyki wskaźnikowej na jego argumentach przed zapewnieniem niezerowej liczby, to inna kwestia [gdybym projektował standardy, prawdopodobnie określiłbym, że jeślip
jest zerowy,p+0
mógłby pułapkować, alememcpy(p,p,0)
nic nie zrobiłbym]. O wiele większym problemem, IMHO, jest otwartość większości niezdefiniowanych zachowań. Chociaż jest kilka rzeczy, które naprawdę powinny reprezentować niezdefiniowane zachowanie (np.free(p)
p[0]=1;
) istnieje wiele rzeczy, które powinny być określone jako dające nieokreślony wynik (np. relacyjne porównanie między niepowiązanymi wskaźnikami nie powinno być określone jako zgodne z jakimkolwiek innym porównaniem, ale powinno być określone jako dające 0 lub a 1), lub powinno być określone jako dające zachowanie nieco luźniejsze niż zdefiniowane w implementacji (kompilatory powinny być zobowiązane do udokumentowania wszystkich możliwych konsekwencji np. przepełnienia liczb całkowitych, ale nie powinny określać, jakie konsekwencje wystąpiłyby w danym przypadku).Możesz również rozważyć to użycie
memmove
widoczne w Git 2.14.x (Q3 2017)Zobacz zatwierdzenie 168e635 (16 lipca 2017) i zatwierdzenie 1773664 , zatwierdzenie f331ab9 , zatwierdzenie 5783980 (15 lipca 2017) autorstwa René Scharfe (
rscharfe
) .(Scalone przez Junio C Hamano -
gitster
- w zatwierdzeniu 32f9025 , 11 sierpnia 2017)Używa makra pomocniczego,
MOVE_ARRAY
które oblicza rozmiar na podstawie określonej dla nas liczby elementów i obsługujeNULL
wskaźniki, gdy ta liczba wynosi zero. Wywołaniasurowe
memmove(3)
withNULL
mogą spowodować, że kompilator (zbyt chętnie) zoptymalizuje późniejszeNULL
sprawdzenia.#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)); }
Przykłady :
- memmove(dst, src, (n) * sizeof(*dst)); + MOVE_ARRAY(dst, src, n);
Używa makra,
BUILD_ASSERT_OR_ZERO
które zapewnia zależność od czasu kompilacji, jako wyrażenie (przy@cond
czym warunek czasu kompilacji musi być prawdziwy).Kompilacja zakończy się niepowodzeniem, jeśli warunek nie jest prawdziwy lub nie może zostać oceniony przez kompilator.
#define BUILD_ASSERT_OR_ZERO(cond) \ (sizeof(char [1 - 2*!(cond)]) - 1)
Przykład:
#define foo_to_char(foo) \ ((char *)(foo) \ + BUILD_ASSERT_OR_ZERO(offsetof(struct foo, string) == 0))
źródło
Nie,
memcpy(0,0,0)
nie jest bezpieczne. Biblioteka standardowa prawdopodobnie nie zawiedzie podczas tego wywołania. Jednak w środowisku testowym memcpy () może zawierać dodatkowy kod w celu wykrycia przepełnienia bufora i innych problemów. A jak ta specjalna wersja memcpy () reaguje na wskaźniki NULL jest… no cóż, nieokreślone.źródło