Czy mogę wywołać memcpy () i memmove () z „liczbą bajtów” ustawioną na zero?

103

Czy muszę traktować przypadki, w których faktycznie nie mam nic do przeniesienia / skopiowania z memmove()/ memcpy()jako przypadki skrajne?

int numberOfBytes = ...
if( numberOfBytes != 0 ) {
    memmove( dest, source, numberOfBytes );
}

czy powinienem po prostu wywołać funkcję bez sprawdzania

int numberOfBytes = ...
memmove( dest, source, numberOfBytes );

Czy sprawdzenie w poprzednim fragmencie jest konieczne?

ostry
źródło
6
Pytanie przypomina mi trochę sprawdzanie zerowych wskaźników funkcji takich jak free. Nie jest to konieczne, ale umieściłbym tam komentarz, aby pokazać, że o tym myślisz.
Toad
12
@Toad: Czemu to służy, poza zaśmiecaniem kodu? Czytając czyjś kod nie muszę wiedzieć, że pierwotny programista „myślał o wykonaniu tej operacji, która nie jest tak naprawdę konieczna, ale ponieważ jest niepotrzebna, nie zrobiłem tego”. Jeśli widzę zwolniony wskaźnik, wiem, że może być zerowy, więc nie muszę znać myśli oryginalnego programisty na temat „czy powinienem sprawdzić wartość null”. To samo dotyczy kopiowania 0 bajtów za pomocąmemcpy
jalf
9
@jalf: fakt, że jest to pytanie dotyczące przepełnienia stosu, sprawia, że ​​ludzie w to wątpią. Więc dodanie komentarza może ci nie pomóc, ale może pomóc komuś o mniejszej wiedzy
Ropuch
2
@Toad Tak, komentarze wyraźnie wyjaśniające, dlaczego sprawdzenie, które wygląda na konieczne, naprawdę nie jest, może być w zasadzie wartościowe. Druga strona medalu jest taka, że ten konkretny przykład jest to częsty przypadek z udziałem standardowej funkcji bibliotecznej, że tylko każdy programista musi nauczyć się odpowiedzi na raz; wtedy mogą rozpoznać w każdym czytanym programie, że te kontrole nie są potrzebne. Z tego powodu pomijałbym komentarze. Baza kodów z wieloma wywołaniami, takimi jak to, będzie musiała albo skopiować i wkleić komentarze do każdego z nich, albo arbitralnie użyć ich tylko w niektórych wywołaniach, z których oba są brzydkie.
Mark Amery,

Odpowiedzi:

145

Ze standardu C99 (7.21.1 / 2):

Gdzie argument zadeklarowany jako size_t nokreśla długość tablicy dla funkcji, nmoże mieć wartość zero w wywołaniu tej funkcji. O ile wyraźnie nie określono inaczej w opisie konkretnej funkcji w tym podrozdziale, argumenty wskaźników w takim wywołaniu nadal będą miały prawidłowe wartości, jak opisano w 7.1.4. W takim wywołaniu funkcja, która lokalizuje znak, nie znajduje żadnego wystąpienia, funkcja porównująca dwie sekwencje znaków zwraca zero, a funkcja kopiująca znaki kopiuje zero znaków.

Więc odpowiedź brzmi: nie; sprawdzenie nie jest konieczne (lub tak; możesz przejść zero).

Mike Seymour
źródło
1
Czy wskaźnik byłby uważany za „prawidłowy” dla celów takiej funkcji, gdyby wskazywał na lokalizację po ostatnim elemencie tablicy? Takiego wskaźnika nie można było w uzasadniony sposób zlekceważyć, ale można było bezpiecznie wykonać inne czynności wskazujące, takie jak odjęcie jednego od niego.
supercat
1
@supercat: tak, wskaźnik, który wskazuje jeden poza koniec tablicy, jest prawidłowy dla arytmetyki wskaźników z innymi wskaźnikami w obrębie tej tablicy (lub jeden za jej końcem), ale nie można go usunąć.
Mike Seymour,
@MikeSeymour: Czy cytat nie powinien sugerować przeciwnej odpowiedzi: sprawdzenie jest konieczne i nie można przejść zera za pomocą zerowych wskaźników?
Neverhoodboy
7
@neverhoodboy: Nie, cytat wyraźnie mówi, że „ nmoże mieć wartość zero”. Masz rację, że nie możesz przekazywać zerowych wskaźników, ale nie o to chodzi w pytaniu.
Mike Seymour
1
@MikeSeymour: Moja wina. Naprawdę przykro. Pytanie dotyczy rozmiaru, a nie wskaźnika.
Neverhoodboy
5

Jak powiedział @You, standard określa, że ​​memcpy i memmove powinny obsłużyć ten przypadek bez problemu; ponieważ są one zwykle realizowane w jakiś sposób

void *memcpy(void *_dst, const void *_src, size_t len)
{
    unsigned char *dst = _dst;
    const unsigned char *src = _src;
    while(len-- > 0)
        *dst++ = *src++;
    return _dst;
}

nie powinieneś nawet mieć żadnych kar za wydajność poza wywołaniem funkcji; jeśli kompilator obsługuje funkcje wewnętrzne / wbudowane dla takich funkcji, dodatkowe sprawdzenie może nawet spowodować, że kod będzie odrobinę wolniejszy, ponieważ sprawdzanie jest już wykonywane w chwili.

Matteo Italia
źródło
1
Myślę, że ta funkcja jest prawdopodobnie wykonywana w assemblerze, gdzie można zoptymalizować transfery pamięci znacznie lepiej niż w c
Toad
"w jakiś sposób" :) Właściwie prawie wszystkie implementacje, które widziałem, są w asemblerze i próbuję skopiować większość bitów używając rodzimego rozmiaru słowa (np. uint32_t na x86), ale to nie zmienia istoty odpowiedzi : to podczas pętli, które nie potrzebują wielkich obliczeń przed uruchomieniem, więc kontrola jest już zrobione.
Matteo Italia
9
-1, typowa implementacja nie ma znaczenia, czy wywołanie tych funkcji w języku C jest prawidłowe (które mogą nawet nie być zaimplementowane jako funkcje C) z zerowym argumentem.
R .. GitHub STOP HELPING ICE
4
Fakt, że jest to poprawne C, został już objęty innymi odpowiedziami, jak powiedziałem na samym początku mojej odpowiedzi: „Jak powiedział @You, standard określa, że ​​memcpy i memmove powinny bez problemu obsłużyć ten przypadek”. Właśnie dodałem swoją opinię o tym, że nie powinieneś nawet obawiać się wywoływania memcpy z len = 0 ze względu na wydajność, ponieważ w tym przypadku jest to połączenie z prawie zerowym kosztem.
Matteo Italia