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
Odpowiedz
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.
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.) –
To znaczy, na przykład , qsort() i bsearch() –
@Matt, qsort i bsearch są przykładami "danych specyficznych dla implementacji poprzez ogólną funkcję". –
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ć.
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ć !.
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.
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;
}
Co się stanie, jeśli rozmiar> 100? –
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;
}
- 1. Wektor w C++ ze wskaźnikiem
- 2. Programowanie równoległe w C#
- 3. Typy ogólne z parametrem typu w C#
- 4. Programowanie gniazd w C++
- 5. Programowanie rozproszone w C++
- 6. Klasa C++ ze statycznym wskaźnikiem
- 7. Ogólne C# pytanie
- 8. C# sposób pisania Func z pustym powrotem
- 9. xrandr związane, C programowanie
- 10. C programowanie funkcji sqrt
- 11. Programowanie C# i Bluetooth
- 12. C# Asyn. Programowanie gniazda
- 13. C# ogólne deklaracje własny przedstawieniu
- 14. Ogólne min i maks. - C++
- 15. Funkcje polimorficzne (ogólne) jako argumenty w C++
- 16. Przypisywanie obiektu Object-C w pustym * wskaźniku z ARC
- 17. Jak sumować liczby ogólne w języku C#?
- 18. W C, Ogólne pojemniki lub bezpieczne pojemniki?
- 19. Programowanie szeregowe w C, w DOS
- 20. link_to z docelowymi pustym
- 21. FirebaseRecyclerAdapter z pustym widokiem
- 22. Kotwica z pustym łącznikiem
- 23. mailto z pustym odbiorcą?
- 24. obsada pośrednim wskaźnikiem do wskaźnika Objective-C
- 25. C# Programowanie gniazd, zamykanie okien
- 26. Wywołanie funkcji C z innym wskaźnikiem stosu (gcc)
- 27. Programowanie gier w C, od czego zacząć?
- 28. jak zadeklarować zmienną NSString z podwójnym wskaźnikiem
- 29. Programowanie gniazd: serwer klienta UDP w C
- 30. Podczas gdy Czy oświadczenie z pustym/pustym grepem powraca?
Jeśli ta to jest tylko C pytanie usunąć tag C++ –
Hmmm ... "Kiedy uderzam palcem młotkiem to boli wielki czas.Jakiego podejścia używasz, uderzając części ciała młotkiem?" – sharptooth
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 ... –