2010-03-16 16 views
10

Mimo że możliwe jest napisanie kodu ogólnego w C przy użyciu wskaźnika pustego (wskaźnik ogólny), okazuje się, że dość trudno jest debugować kod, ponieważ wskaźnik void może przyjąć dowolny typ wskaźnika bez ostrzeżenia z kompilatora. (np. Funkcja foo() przyjmuje void wskaźnik, który ma być wskaźnikiem do struct, ale kompilator nie będzie narzekał, jeśli tablica char zostanie przekazana.) Jakiego rodzaju podejście/strategia używasz, gdy używasz pustego wskaźnika w C?Programowanie ogólne w C z pustym wskaźnikiem

+4

Jeśli ta to jest tylko C pytanie usunąć tag C++ –

+13

Hmmm ... "Kiedy uderzam palcem młotkiem to boli wielki czas.Jakiego podejścia używasz, uderzając części ciała młotkiem?" – sharptooth

+2

Jeśli uważasz, że ogólne programowanie C z pustką * może być eleganckie/przyjemne w użyciu, zobacz, co musisz zrobić, aby użyć funkcji qsort ... –

Odpowiedz

8

Rozwiązaniem jest nie używać void*, chyba że naprawdę, naprawdę musisz. Miejsca, w których wymagany jest pusty wskaźnik, są bardzo małe: parametry do gwintowania funkcji oraz garść innych miejsc, w których należy przekazywać dane dotyczące implementacji za pomocą funkcji ogólnej. W każdym przypadku kod akceptujący parametr void* powinien akceptować tylko jeden typ danych przekazywany za pomocą pustego wskaźnika, a typ powinien być udokumentowany w komentarzach i niewolniczo przestrzegany przez wszystkich wywołujących.

+4

Program OP specjalnie wymienił ogólne programowanie, które jest całkiem dobrym przykładem użycia dla void * (w przypadku braku obiektów języka wyższego poziomu, takich jak szablony lub ładne makra.) –

+0

To znaczy, na przykład , qsort() i bsearch() –

+0

@Matt, qsort i bsearch są przykładami "danych specyficznych dla implementacji poprzez ogólną funkcję". –

4

To może pomóc:

comp.lang.c FAQ list · Question 4.9

P: Załóżmy, że chcę napisać funkcję, która pobiera ogólny wskaźnik jako argument i chcę symulować przepuszczenie go przez odniesienie. Czy mogę podać formalny typ parametru void ** i zrobić coś takiego?

void f(void **); 
double *dp; 
f((void **)&dp); 

A: Niezgodne ze sobą. Kod taki jak ten może działać, a czasami jest zalecany, ale opiera się na wszystkich typach wskaźników mających tę samą wewnętrzną reprezentację (co jest powszechne, ale nie uniwersalne, patrz pytanie 5.17).

Brak typowego wskaźnika typu wskaźnik-wskaźnik w C. void * działa jak wskaźnik ogólny tylko dlatego, że konwersje (jeśli to konieczne) są stosowane automatycznie, gdy inne typy wskaźników są przypisane do iz pustych *; tych konwersji nie można wykonać, jeśli podjęto próbę pośredniego na wartość void **, która wskazuje na typ wskaźnika inny niż void *. Kiedy używasz wartości void ** wskaźnik (na przykład, gdy użyjesz operatora * do uzyskania wartości void *, do której void ** wskazuje), kompilator nie ma możliwości sprawdzenia, czy ta wartość void * została raz przekonwertowany z innego typu wskaźnika. Musi przyjąć, że to nic więcej niż pustka *; nie może wykonywać żadnych niejawnych konwersji.

Innymi słowy, dowolna wartość pustki **, którą grasz, musi być adresem rzeczywistej pustki * gdzieś; casts like (void **) & dp, choć mogą one zamknąć kompilator, są nonportable (i nie mogą nawet robić, co chcesz, zobacz także pytanie 13.9). Jeśli wskaźnik, do którego odnosi się pustka **, nie jest pustką *, a jeśli ma inny rozmiar lub reprezentację niż pusta *, to kompilator nie będzie mógł uzyskać do niego dostępu poprawnie.

Aby powyższy fragment kodu pracy, trzeba by użyć pośrednią void * zmienna:

double *dp; 
void *vp = dp; 
f(&vp); 
dp = vp; 

Zadania do iz vp dać kompilator możliwość wykonywania żadnych konwersji, jeśli to konieczne.

Ponownie, dotychczasowa dyskusja zakłada, że ​​różne typy wskaźników mogą mieć różne rozmiary lub reprezentacje, co jest dziś rzadkością, ale nie jest niespotykane. Aby lepiej zrozumieć problem pustki **, porównaj sytuację z analogiczną, na przykład z typami int i double, które prawdopodobnie mają różne rozmiary iz pewnością mają różne reprezentacje. Jeśli mamy funkcję

void incme(double *p) 
{ 
    *p += 1; 
} 

wtedy możemy coś zrobić jak

int i = 1; 
double d = i; 
incme(&d); 
i = d; 

i będzie zwiększany o 1. (Jest to analogiczne do prawidłowego pustce ** Kod udziałem VP pomocniczego.) Jeśli, z drugiej strony, byliśmy próbować coś podobnego

int i = 1; 
incme((double *)&i); /* WRONG */ 

(kod ten jest analogiczny do fragmentu w pytaniu), byłoby wysoce nieprawdopodobne, aby pracować.

-2

Wszyscy wiemy, że system typu C jest w gruncie rzeczy bzdurą, ale staraj się tego nie robić ... Nadal masz kilka możliwości poradzenia sobie z typami generycznymi: związki i nieprzejrzyste wskaźniki.

W każdym razie, jeśli funkcja ogólna przyjmuje jako parametr parametr pustego wskaźnika, nie powinien próbować go usuwać !.

1

Podejście/strategia polega na zminimalizowaniu wykorzystania pustych * wskaźników. Są one potrzebne w konkretnych przypadkach. Jeśli naprawdę chcesz przekazać void *, powinieneś również przekazać rozmiar celu wskaźnika.

0

Ta ogólna swap pozwoli Ci bardzo pomóc w zrozumieniu rodzajowe void *

#include<stdio.h> 
void swap(void *vp1,void *vp2,int size) 
{ 
     char buf[100]; 
     memcpy(buf,vp1,size); 
     memcpy(vp1,vp2,size); 
     memcpy(vp2,buf,size); //memcpy ->inbuilt function in std-c 
} 

int main() 
{ 
     int a=2,b=3; 
     float d=5,e=7; 
     swap(&a,&b,sizeof(int)); 
     swap(&d,&e,sizeof(float)); 
     printf("%d %d %.0f %.0f\n",a,b,d,e); 
return 0; 
} 
+2

Co się stanie, jeśli rozmiar> 100? –

2

rozwiązanie Arya można zmienić trochę wspierać zmienny rozmiar:

#include <stdio.h> 
#include <string.h> 

void swap(void *vp1,void *vp2,int size) 
{ 
    char buf[size]; 
    memcpy(buf,vp1,size); 
    memcpy(vp1,vp2,size); 
    memcpy(vp2,buf,size); //memcpy ->inbuilt function in std-c 
} 

int main() 
{ 
    int array1[] = {1, 2, 3}; 
    int array2[] = {10, 20, 30}; 
    swap(array1, array2, 3 * sizeof(int)); 

    int i; 
    printf("array1: "); 
    for (i = 0; i < 3; i++) 
    printf(" %d", array1[i]); 
    printf("\n"); 

    printf("array2: "); 
    for (i = 0; i < 3; i++) 
    printf(" %d", array2[i]); 
    printf("\n"); 

    return 0; 
}