Pojęcie void pointer w programowaniu w C.


130

Czy można wyłuskać wskaźnik void bez rzutowania typów w języku programowania C?

Ponadto, czy istnieje sposób na uogólnienie funkcji, która może otrzymać wskaźnik i przechowywać go we wskaźniku void, a używając tego wskaźnika void, czy możemy utworzyć funkcję uogólnioną?

na przykład:

void abc(void *a, int b)
{
   if(b==1)
      printf("%d",*(int*)a);     // If integer pointer is received
   else if(b==2)
      printf("%c",*(char*)a);     // If character pointer is received
   else if(b==3)
      printf("%f",*(float*)a);     // If float pointer is received
}

Chcę, aby ta funkcja była ogólna bez używania instrukcji if-else - czy to możliwe?

Również jeśli istnieją dobre artykuły internetowe, które wyjaśniają pojęcie void pointer, byłoby korzystne, gdybyś mógł podać adresy URL.

Czy możliwa jest również arytmetyka wskaźnika ze wskaźnikami do pustych przestrzeni?

Odpowiedzi:


97

Czy można wyłuskać wskaźnik void bez rzutowania typów w języku programowania C ...

Nie, voidwskazuje na brak typu, nie można go wyłuskać ani przypisać.

czy istnieje sposób na uogólnienie funkcji, która może odbierać wskaźnik i przechowywać go w void pointer, a używając tego void pointer, możemy utworzyć uogólnioną funkcję.

Nie można go po prostu wyłuskać w sposób przenośny, ponieważ może nie być odpowiednio wyrównany. Może to stanowić problem na niektórych architekturach, takich jak ARM, gdzie wskaźnik do typu danych musi być wyrównany na granicy rozmiaru typu danych (np. Wskaźnik do 32-bitowej liczby całkowitej musi być wyrównany na 4-bajtowej granicy, aby można było wyodrębnić).

Na przykład czytając uint16_tz void*:

/* may receive wrong value if ptr is not 2-byte aligned */
uint16_t value = *(uint16_t*)ptr;
/* portable way of reading a little-endian value */
uint16_t value = *(uint8_t*)ptr
                | ((*((uint8_t*)ptr+1))<<8);

Czy możliwa jest również arytmetyka wskaźnika ze wskaźnikami void ...

Arytmetyka wskaźnika nie jest możliwa na wskaźnikach z voidpowodu braku konkretnej wartości pod wskaźnikiem, a tym samym rozmiaru.

void* p = ...
void *p2 = p + 1; /* what exactly is the size of void?? */

2
O ile nie pamiętam niepoprawnie, możesz bezpiecznie wyłuskać pustkę * na dwa sposoby. Rzutowanie na char * jest zawsze dopuszczalne, a jeśli znasz oryginalny typ, który wskazuje, możesz rzutować na ten typ. Pusta * utraciła informacje o typie, więc musiałaby być przechowywana w innym miejscu.
Dan Olson,

1
Tak, zawsze możesz wyłuskać odniesienie, jeśli rzucisz na inny typ, ale nie możesz zrobić tego samego, jeśli nie rzucisz. Krótko mówiąc, kompilator nie będzie wiedział, jakich instrukcji asemblacji użyć do operacji matematycznych, dopóki ich nie rzucisz.
Zachary Hamm

20
Myślę, że GCC traktuje arytmetykę na void *wskaźnikach tak samo jak char *, ale to nie jest standard i nie powinieneś na nim polegać.
ephemient

5
Nie jestem pewien, czy nadążam. Na przykład, że PO wymienione powyżej typy przychodzące są gwarantowane przez użytkownika funkcji jako poprawne. Czy mówisz, że jeśli mamy coś, w którym przypisujemy wskaźnikowi wartość znanego typu, rzutujemy go na wskaźnik void, a następnie rzutujemy z powrotem na pierwotny typ wskaźnika, aby mógł stać się niewyrównany? Nie rozumiem, dlaczego kompilator miałby cię tak podważyć, po prostu losowo nie wyrównując rzeczy po prostu dlatego, że rzucasz je na wskaźnik pustki. A może po prostu mówisz, że nie możemy ufać, że użytkownik zna typ argumentów przekazanych do funkcji?
doliver

2
@doliver Miałem na myśli # 2 („nie możemy ufać, że użytkownik zna typ argumentów, które przekazał do funkcji”). Ale wracając do mojej odpowiedzi sprzed 6 lat (ugh, czy to naprawdę tak długo?) Rozumiem, co masz na myśli, nie zawsze tak jest: kiedy oryginalny wskaźnik wskazywał na właściwy typ, byłby już wyrównany, więc byłoby bezpieczne rzucanie bez problemów z wyrównaniem.
Alex B

32

W języku C a void *można przekonwertować na wskaźnik do obiektu innego typu bez jawnego rzutowania:

void abc(void *a, int b)
{
    int *test = a;
    /* ... */

Nie pomaga to jednak w pisaniu funkcji w bardziej ogólny sposób.

Nie można wyłuskać a void *poprzez konwersję go na inny typ wskaźnika, ponieważ wyłuskiwanie wskaźnika jest uzyskiwaniem wartości wskazywanego obiektu. Naked voidnie jest prawidłowym typem, więc usunięcie dostępu void *nie jest możliwe.

Arytmetyka wskaźników polega na zmianie wartości wskaźnika przez wielokrotność sizeofwskazanych obiektów. Ponownie, ponieważ voidnie jest to prawdziwy typ, sizeof(void)nie ma znaczenia, więc arytmetyka wskaźnika nie jest poprawna void *. (Niektóre implementacje na to pozwalają, używając równoważnej arytmetyki wskaźnika dla char *.)


1
@CharlesBailey W swoim kodzie piszesz void abc(void *a, int b). Ale dlaczego nie użyć void abc(int *a, int b), skoro w swojej funkcji ostatecznie zdefiniujesz int *test = a;, zapisałoby to jedną zmienną, a nie widzę żadnych innych zalet definiowania innej zmiennej. Widzę wiele kodów napisanych w ten sposób, używając void *jako parametrów i rzutujących zmienne później w funkcji. Ale skoro ta funkcja jest napisana przez autora, więc musi znać użycie zmiennej, więc po co jej używać void *? Dzięki
Lion Lai

15

Należy mieć świadomość, że w C, w przeciwieństwie do Javy czy C #, nie ma absolutnie żadnej możliwości pomyślnego „odgadnięcia” typu obiektu, na który void*wskazuje wskaźnik. Coś podobnego getClass()po prostu nie istnieje, ponieważ nigdzie nie można znaleźć tych informacji. Z tego powodu rodzaj „generycznego”, którego szukasz, zawsze zawiera jawną metainformację, taką jak int bw twoim przykładzie lub ciąg formatu w printfrodzinie funkcji.


7

Wskaźnik void jest znany jako wskaźnik ogólny, który może odnosić się do zmiennych dowolnego typu.


6

Jak dotąd moje zaniżenie dotyczące wskaźnika void jest następujące.

Gdy zmienna wskaźnikowa jest zadeklarowana przy użyciu słowa kluczowego void - staje się zmienną wskaźnikową ogólnego przeznaczenia. Adres dowolnej zmiennej dowolnego typu danych (char, int, float itp.) Można przypisać do zmiennej wskaźnika void.

main()
{
    int *p;

    void *vp;

    vp=p;
} 

Ponieważ do wskaźnika void można przypisać inny wskaźnik typu danych, użyłem go w funkcji absolut_value (kod pokazany poniżej). Aby zrobić ogólną funkcję.

Próbowałem napisać prosty kod C, który przyjmuje liczbę całkowitą lub zmiennoprzecinkową jako argument i stara się, aby był + ve, jeśli jest ujemny. Napisałem następujący kod,

#include<stdio.h>

void absolute_value ( void *j) // works if used float, obviously it must work but thats not my interest here.
{
    if ( *j < 0 )
        *j = *j * (-1);

}

int main()
{
    int i = 40;
    float f = -40;
    printf("print intiger i = %d \n",i);
    printf("print float f = %f \n",f);
    absolute_value(&i);
    absolute_value(&f);
    printf("print intiger i = %d \n",i);
    printf("print float f = %f \n",f);
    return 0;
}   

Ale otrzymywałem błąd, więc dowiedziałem się, że moje rozumienie ze wskaźnikiem void jest nieprawidłowe :(. Więc teraz przejdę do zbierania punktów, dlaczego tak jest.

Na temat wskaźników pustki muszę lepiej zrozumieć, że.

Musimy typecastować zmienną void pointer, aby ją usunąć. Dzieje się tak, ponieważ ze wskaźnikiem void nie jest powiązany żaden typ danych. W żaden sposób kompilator nie może wiedzieć (lub zgadywać?), Na jaki typ danych wskazuje wskaźnik void. Tak więc, aby wziąć dane wskazywane przez wskaźnik void, wpisujemy je na maszynie z poprawnym typem danych przechowywanych w lokalizacji void pointers.

void main()

{

    int a=10;

    float b=35.75;

    void *ptr; // Declaring a void pointer

    ptr=&a; // Assigning address of integer to void pointer.

    printf("The value of integer variable is= %d",*( (int*) ptr) );// (int*)ptr - is used for type casting. Where as *((int*)ptr) dereferences the typecasted void pointer variable.

    ptr=&b; // Assigning address of float to void pointer.

    printf("The value of float variable is= %f",*( (float*) ptr) );

}

Wskaźnik void może być naprawdę przydatny, jeśli programista nie jest pewien typu danych wprowadzanych przez użytkownika końcowego. W takim przypadku programista może użyć wskaźnika void, aby wskazać lokalizację nieznanego typu danych. Program można ustawić w taki sposób, aby prosić użytkownika o poinformowanie o typie danych, a rzutowanie typu może być wykonywane zgodnie z informacjami wprowadzonymi przez użytkownika. Fragment kodu znajduje się poniżej.

void funct(void *a, int z)
{
    if(z==1)
    printf("%d",*(int*)a); // If user inputs 1, then he means the data is an integer and type casting is done accordingly.
    else if(z==2)
    printf("%c",*(char*)a); // Typecasting for character pointer.
    else if(z==3)
    printf("%f",*(float*)a); // Typecasting for float pointer
}

Inną ważną kwestią, o której należy pamiętać w przypadku wskaźników void, jest to, że arytmetyka wskaźnika nie może być wykonywana w przypadku wskaźnika void.

void *ptr;

int a;

ptr=&a;

ptr++; // This statement is invalid and will result in an error because 'ptr' is a void pointer variable.

Więc teraz zrozumiałem, jaki był mój błąd. Poprawiam to samo.

Bibliografia :

http://www.antoarts.com/void-pointers-in-c/

http://www.circuitstoday.com/void-pointers-in-c .

Nowy kod jest przedstawiony poniżej.


#include<stdio.h>
#define INT 1
#define FLOAT 2

void absolute_value ( void *j, int *n)
{
    if ( *n == INT) {
        if ( *((int*)j) < 0 )
            *((int*)j) = *((int*)j) * (-1);
    }
    if ( *n == FLOAT ) {
        if ( *((float*)j) < 0 )
            *((float*)j) = *((float*)j) * (-1);
    }
}


int main()
{
    int i = 0,n=0;
    float f = 0;
    printf("Press 1 to enter integer or 2 got float then enter the value to get absolute value\n");
    scanf("%d",&n);
    printf("\n");
    if( n == 1) {
        scanf("%d",&i);
        printf("value entered before absolute function exec = %d \n",i);
        absolute_value(&i,&n);
        printf("value entered after absolute function exec = %d \n",i);
    }
    if( n == 2) {
        scanf("%f",&f);
        printf("value entered before absolute function exec = %f \n",f);
        absolute_value(&f,&n);
        printf("value entered after absolute function exec = %f \n",f);
    }
    else
    printf("unknown entry try again\n");
    return 0;
}   

Dziękuję Ci,


2

Nie, to niemożliwe. Jaki typ powinna mieć wyłuskana wartość?


2
void abc(void *a, int b) {
  char *format[] = {"%d", "%c", "%f"};
  printf(format[b-1], a);
}

Czy ten program jest możliwy… Proszę sprawdzić, czy to możliwe w C programowaniu…
AGeek

2
Tak, jest to możliwe (chociaż kod jest niebezpieczny bez sprawdzenia zakresu dla b). Printf przyjmuje zmienną liczbę argumentów, a a jest po prostu umieszczany na stosie jako dowolny wskaźnik (bez żadnych informacji o typie) i wstawiany do funkcji printf przy użyciu makr va_arg przy użyciu informacji z łańcucha formatującego.
hlovdal

@SiegeX: opisz, co nie jest przenośne, a co może być nieokreślone.
Gauthier

@Gauthier przekazuje zmienną, która zawiera specyfikatory formatu, nie jest przenośna i potencjalnie niezdefiniowana. Jeśli chcesz zrobić coś takiego, spójrz na smak „vs” printf. Ta odpowiedź ma na to dobry przykład.
SiegeX

W praktyce prawdopodobnie zastąpiłbym int bwyliczeniem, ale dokonanie jakiejkolwiek późniejszej zmiany tego wyliczenia spowodowałoby przerwanie tej funkcji.
Jack Stout

1

Chcę, aby ta funkcja była ogólna, bez używania ifs; Czy to możliwe?

Jedynym prostym sposobem, jaki widzę, jest użycie przeciążenia .. które nie jest dostępne w języku programowania C AFAIK.

Czy zastanawiałeś się nad językiem programowania C ++ w swoim programie? Czy jest jakieś ograniczenie, które zabrania jego używania?


Ok, szkoda. Z mojego punktu widzenia nie widzę wtedy rozwiązań. Właściwie jest to to samo, co printf () & cout: 2 różne sposoby implementacji drukowania. printf () użyj instrukcji if () do zdekodowania ciągu formatu (przypuszczam, że lub coś podobnego), podczas gdy dla operatora przypadku cout << jest przeciążoną funkcją
yves Baumes

1

Oto krótka wskazówka na temat voidwskaźników: https://www.learncpp.com/cpp-tutorial/613-void-pointers/

6.13 - Wskaźniki pustki

Ponieważ wskaźnik void nie wie, na jaki typ obiektu wskazuje, nie można go bezpośrednio wyłuskać! Zamiast tego void wskaźnik musi najpierw zostać jawnie rzutowany na inny typ wskaźnika, zanim zostanie wyłuskiwany.

Jeśli wskaźnik pustki nie wie, na co wskazuje, skąd mamy wiedzieć, na co go rzutować? Ostatecznie to do Ciebie należy śledzenie.

Void pointer różnorodność

Nie jest możliwe wykonanie arytmetyki wskaźnika na pustym wskaźniku. Dzieje się tak, ponieważ arytmetyka wskaźnika wymaga, aby wskaźnik wiedział, jaki rozmiar obiektu wskazuje, aby mógł odpowiednio zwiększać lub zmniejszać wskaźnik.

Zakładając, że pamięć urządzenia jest bajt adresowalnych i nie wymaga wyrównane dostępy, najbardziej ogólny i atomowej (najbliżej reprezentacji poziomu maszyna) sposób interpretacji void*jest jako wskaźnik do a-bajt, uint8_t*. Rzutowanie a void*na a uint8_t*pozwoliłoby ci na przykład wydrukować pierwsze 1/2/4/8 / dowolną liczbę bajtów zaczynając od tego adresu, ale nie możesz zrobić nic więcej.

uint8_t* byte_p = (uint8_t*)p;
for (uint8_t* i = byte_p; i < byte_p + 8; i++) {
  printf("%x ",*i);
}

0

Możesz łatwo wydrukować pustą drukarkę

int p=15;
void *q;
q=&p;
printf("%d",*((int*)q));

1
Kto gwarantuje, że voidma taki sam rozmiar jak int?
Ant_222,

To nie jest technicznie drukowanie voidwskaźnika, to drukowanie intpod adresem pamięci, który zawiera wskaźnik (który w tym przypadku byłby wartością p).
Marionumber1,

0

Ponieważ C jest językiem z typami statycznymi i silnymi typami, przed kompilacją należy zdecydować o typie zmiennej. Kiedy spróbujesz emulować typy generyczne w C, w końcu spróbujesz przepisać C ++ ponownie, więc lepiej byłoby zamiast tego użyć C ++.


0

void pointer to ogólny wskaźnik. Adres dowolnego typu danych dowolnej zmiennej może być przypisany do wskaźnika void.

int a = 10;
float b = 3.14;
void *ptr;
ptr = &a;
printf( "data is %d " , *((int *)ptr)); 
//(int *)ptr used for typecasting dereferencing as int
ptr = &b;
printf( "data is %f " , *((float *)ptr));
//(float *)ptr used for typecasting dereferencing as float

0

Nie można wyłuskać wskaźnika bez określenia jego typu, ponieważ różne typy danych będą miały różne rozmiary w pamięci, tj. Int to 4 bajty, a char to 1 bajt.


-1

Zasadniczo w C „typy” są sposobem interpretacji bajtów w pamięci. Na przykład, jaki jest następujący kod

struct Point {
  int x;
  int y;
};

int main() {
  struct Point p;
  p.x = 0;
  p.y = 0;
}

Mówi: „Kiedy uruchamiam main, chcę przydzielić 4 (rozmiar liczby całkowitej) + 4 (rozmiar liczby całkowitej) = 8 (łącznie bajtów) pamięci. Kiedy piszę„ .x ”jako lwartość na wartości z etykietą typu Wskaż na czas kompilacji, pobierz dane z lokalizacji pamięci wskaźnika plus cztery bajty. Podaj zwracanej wartości etykietę czasu kompilacji „int”.

Wewnątrz komputera w czasie wykonywania struktura „Point” wygląda następująco:

00000000 00000000 00000000 00000000 00000000 00000000 00000000

A oto jak void*może wyglądać twój typ danych: (zakładając komputer 32-bitowy)

10001010 11111001 00010010 11000101

To nie odpowiada na pytanie
MM

-2

To nie zadziała, ale void * może bardzo pomóc w zdefiniowaniu ogólnego wskaźnika do funkcji i przekazaniu go jako argumentu do innej funkcji (podobnie jak wywołanie zwrotne w Javie) lub zdefiniowaniu struktury podobnej do oop.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.