2016-10-10 14 views
6

Chciałbym napisać funkcję ogólną, aby wykryć, czy tablica wskaźników do jakiegoś dowolnego typu zawiera NULL. Moją pierwszą próbą było coś wzdłuż tych linii:Pisanie funkcji ogólnej w celu wykrycia, czy tablica wskaźników zawiera NULL

bool find_null (void *ptrs, size_t num_ptrs) { 
    void **array = ptrs; 
    size_t i; 
    for (i = 0; i < num_ptrs; ++i) { 
     if (array[i] == NULL) return true; 
    } 
    return false; 
} 

Wskazywano, że może to doprowadzić do ścisłego naruszenie aliasingu, ponieważ tablica wskaźników do Foo byłyby dostępne jako tablicy wskaźników do void, którego nie ma na liście jako jeden z dozwolonych sposobów obiekt może być dostępne w C.2011 § 6,5 ¶ 7.

mógłbym przepisać na dostęp do funkcji tablicę wskaźników jak unsigned char * zamiast, ale nie jestem pewien jak wykonać NULL sprawdź bez łamania ścisłego aliasowania. Czy ktoś może podać prawidłową technikę?

bool find_null (void *ptrs, size_t num_ptrs) { 
    unsigned char *array = ptrs; 
    void *p; 
    size_t i; 
    for (i = 0; i < num_ptrs; ++i) { 
     memcpy(&p, array + i * sizeof(p), sizeof(p)); 
     if (p == NULL) return true; 
     /* 
     * Above seems to still break strict aliasing. 
     * What should be done instead? 
     */ 
    } 
    return false; 
} 

Celem jest, aby napisać rodzajowe funkcji, które działają tak samo jak konkretnej funkcji typu. Innymi słowy, rodzajowy wersja funkcji poniżej:

bool find_null_Foo (Foo *array[], size_t num_ptrs) { 
    size_t i; 
    for (i = 0; i < num_ptrs; ++i) { 
     if (array[i] == NULL) return true; 
    } 
    return false; 
} 
+8

Nie jest to ogólnie możliwe. Nie ma wymogu, że 'sizeof (void *)' jest równe 'sizeof (Foo *)'.Mogą być nierówne na maszynach adresowanych bez bajtów, gdzie 'sizeof (void *)' jest trochę większe niż 'sizeof (Foo *)' ze względu na potrzebę zapisania bajtu w słowie, do którego odwołuje się wskaźnik. –

+1

Czy chcesz, aby kod uwzględniał tylko wskaźnik zerowy o wartości '(void *) NULL' lub chciałby obsłużyć możliwość, że platforma ma różne wartości wskaźników null? (Oczywiście wszystkie wskaźniki zerowe są sobie równe - choć mogą mieć różne kodowania). – chux

+0

@RaymondChen: Dzięki za wniesienie tego, jest to ważny punkt. Wysłałem odpowiedź, która powinna to wyjaśnić. Jednak w przypadku takiej maszyny prawdopodobnie skończyłbym projektując ją jako 30-bitową adresowalną (dla słów 4-bajtowych) lub 61-bitową adresowalną (dla słów 8-bajtowych) zamiast próbować radzić sobie ze wskaźnikami o różnych rozmiarach. – jxh

Odpowiedz

1

nie można zrobić go z konkretnym interfejsem jesteś obecny, ale może być w stanie zrobić to w tym nieco przylegający sposób:

bool find_null (const void *array, size_t num_ptrs, size_t ptr_size, 
     const void *null) { 
    const char (*ptr_array)[ptr_size] = array; 
    size_t i; 
    for (i = 0; i < num_ptrs; ++i) { 
     if (!memcmp(array[i], null, ptr_size)) return true; 
    } 
    return false; 
} 

można by nazwać tak:

struct Foo; 

#define ARRAY_SIZE 53 
int main(void) { 
    struct Foo *my_array[ARRAY_SIZE] = { ... }; 
    struct Foo * const foo_null = (struct Foo *) 0; 
    if (find_null(my_array, ARRAY_SIZE, sizeof(*my_array), &foo_null)) { 
     puts("It contains NULL"); 
    } else { 
     puts("It does not contain NULL"); 
    } 
} 

należy pamiętać, że zakłada się, że istnieje tylko jedna reprezentacja zerowych wskaźników z danego rodzaju, co jest prawdą w wielu implementacjach, ale jest niewymagane przez język.

Należy również zauważyć, że nie jest to właściwe do szukania wskaźników zerowych, więc w rzeczywistości można go użyć do przeszukania tablicy wskaźnika dla dowolnej wartości wskaźnika. W rzeczywistości nie jest to nawet specyficzne dla tablic wskaźników - możesz go użyć do przeszukania dowolnej tablicy dla dowolnej wartości, o ile równość bajt-do-bajtu jest odpowiednim kryterium dopasowywania (które nie jest dla struktur lub związki zawodowe i może nie być dla niektórych innych typów).

Dodatkowo, jeśli ci pasuje, prawdopodobnie możesz opracować makro opakowania, dzięki czemu będzie ono nieco łatwiejsze w przypadku niektórych typowych scenariuszy.

+0

Nie ma żadnej gwarancji, że możesz porównać wskaźniki null o różnych rozmiarach. W rzeczywistości nie ma gwarancji, że 'sizeof (void *)> = rozmiar (int *)' – chqrlie

+0

@ chqrlie jest poprawny. Dlatego funkcja, którą przedstawiłem, przyjmuje * wskaźnik na * wartość wskaźnika pustego, aby porównać z elementami tablicy, i używa 'memcmp()' do wykonywania porównań. Porównuję wskaźniki tego samego typu. –

+0

Oops, rzeczywiście, ale jak wspomniałeś, nie ma gwarancji, że istnieje unikalna reprezentacja wskaźników zerowych dla danego typu. – chqrlie

1

Na podstawie Raymond's comment, wydaje się, że funkcja generyczna wymagałaby dodatkowych argumentów. Ponieważ nie ma sposobu przekonwertowania reprezentacji wskaźników na unsigned char, należy to zrobić za pomocą wywołania zwrotnego.

bool find_null_generic (const void *ptrs, size_t ptr_sz, size_t num_ptrs, 
         const void *(*convert)(const void *)) { 
    const unsigned char *array = ptrs; 
    size_t i; 
    for (i = 0; i < num_ptrs; ++i) { 
     if (convert(array + i * ptr_sz) == NULL) return true; 
    } 
    return false; 
} 

Dla hipotetycznego tablicy wskaźnika do Foo:

const void *convert_Foo (const void *data) { 
    const Foo *foo; 
    memcpy(&foo, data, sizeof(foo)); 
    return foo; 
} 

Foo *foo_array[N] = {...}; 
bool result = find_null_generic(foo_array, sizeof(Foo *), N, convert_Foo); 
1

Funkcja rodzajowe nie są gwarantowane do pracy zgodnie z oczekiwaniami na systemach, gdzie wskaźniki do różnych typów mogą mieć różne reprezentacje i/lub rozmiary. Na szczęście takie architektury są obecnie dość rzadkie. Systemy zgodne z Posix zapewniają na przykład taką samą reprezentację i rozmiar dla wszystkich typów wskaźników.

+1

Niestety, naruszenie ścisłego aliasingu może nadal prowadzić kompilator do generowania nieoczekiwanych wyników optymalizacji nawet w systemach, w których rozmiary wskaźnika są takie same. – jxh

+0

@jxh: ten rodzaj zachowania jest tak sprzeczny z intuicją, że należy go uznać za błąd. Zastanawiam się, jak można sformułować ścisłe aliasing, aby zapobiec tak daleko idącym konsekwencjom. – chqrlie

+0

@chqrlie: Najprostszym sformułowaniem byłoby określenie, że rzutowanie wskaźnika z typu 'T *' na 'U *' spowoduje udzielenie licencji na użycie tego celu lub wskaźników uzyskanych z niego jako 'U *' do następnego razu obiektu jest dostępny za pośrednictwem dowolnego wskaźnika nie uzyskanego z wyniku wyżej wspomnianej obsady. To nie wyklucza wielu użytecznych optymalizacji, ale prawdopodobnie uniknie konieczności używania '-fno-strict-aliasing' z 90% programów, które obecnie tego wymagają. – supercat

Powiązane problemy