Konzept des Leerzeigers in der C-Programmierung


129

Ist es möglich, einen ungültigen Zeiger ohne Typumwandlung in der Programmiersprache C zu dereferenzieren?

Gibt es auch eine Möglichkeit, eine Funktion zu verallgemeinern, die einen Zeiger empfangen und in einem ungültigen Zeiger speichern kann, und können wir mithilfe dieses ungültigen Zeigers eine verallgemeinerte Funktion erstellen?

für zB:

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
}

Ich möchte diese Funktion generisch machen, ohne if-else-Anweisungen zu verwenden. Ist dies möglich?

Auch wenn es gute Internetartikel gibt, die das Konzept eines ungültigen Zeigers erklären, wäre es vorteilhaft, wenn Sie die URLs angeben könnten.

Ist auch eine Zeigerarithmetik mit leeren Zeigern möglich?

Antworten:


97

Ist es möglich, den Void-Zeiger ohne Typumwandlung in der Programmiersprache C zu dereferenzieren ...

Nein, voidzeigt an, dass kein Typ vorhanden ist. Dies kann nicht dereferenziert oder zugewiesen werden.

Gibt es eine Möglichkeit, eine Funktion zu verallgemeinern, die einen Zeiger empfangen und in einem leeren Zeiger speichern kann, und indem wir diesen leeren Zeiger verwenden, können wir eine verallgemeinerte Funktion erstellen.

Sie können es nicht einfach auf tragbare Weise dereferenzieren, da es möglicherweise nicht richtig ausgerichtet ist. Bei einigen Architekturen wie ARM kann dies ein Problem sein, bei dem der Zeiger auf einen Datentyp an der Grenze der Größe des Datentyps ausgerichtet sein muss (z. B. muss der Zeiger auf eine 32-Bit-Ganzzahl an der 4-Byte-Grenze ausgerichtet sein, um dereferenziert zu werden).

Lesen Sie zum Beispiel uint16_taus 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);

Auch ist Zeigerarithmetik mit leeren Zeigern möglich ...

Zeigerarithmetik ist bei Zeigern voidaufgrund des Fehlens eines konkreten Werts unter dem Zeiger und damit der Größe nicht möglich .

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

2
Wenn ich mich nicht falsch erinnere, können Sie eine Leere * auf zwei Arten sicher dereferenzieren. Das Umwandeln in char * ist immer akzeptabel. Wenn Sie den ursprünglichen Typ kennen, auf den es verweist, können Sie auf diesen Typ umwandeln. Die Leere * hat die Typinformationen verloren, sodass sie an anderer Stelle gespeichert werden müssten.
Dan Olson

1
Ja, Sie können immer dereferenzieren, wenn Sie in einen anderen Typ umwandeln, aber Sie können nicht dasselbe tun, wenn Sie nicht umwandeln. Kurz gesagt, der Compiler weiß erst, welche Montageanweisungen für mathematische Operationen verwendet werden sollen, wenn Sie sie umwandeln.
Zachary Hamm

20
Ich denke, GCC behandelt Arithmetik für void *Zeiger genauso wie char *, aber es ist kein Standard und Sie sollten sich nicht darauf verlassen.
Ephemient

5
Ich bin mir nicht sicher, ob ich folge. In dem Beispiel, dass das oben aufgeführte OP für die eingehenden Typen vom Benutzer der Funktion garantiert korrekt ist. Wollen Sie damit sagen, dass wir, wenn wir etwas haben, bei dem wir einem Zeiger einen Wert bekannten Typs zuweisen, ihn in einen ungültigen Zeiger umwandeln und ihn dann auf den ursprünglichen Zeigertyp zurücksetzen, damit er möglicherweise nicht mehr ausgerichtet wird? Ich verstehe nicht, warum der Compiler Sie so untergraben würde, indem Sie Dinge zufällig ausrichten, nur weil Sie sie in einen leeren Zeiger umwandeln. Oder sagen Sie einfach, dass wir dem Benutzer nicht vertrauen können, dass er die Art der Argumente kennt, die er an die Funktion übergeben hat?
Doliver

2
@doliver Ich meinte # 2 ("Wir können dem Benutzer nicht vertrauen, dass er die Art der Argumente kennt, die er an die Funktion übergeben hat"). Aber wenn ich meine Antwort von vor 6 Jahren noch einmal betrachte (ugh, war es wirklich so lange her?), Kann ich sehen, was Sie meinen, es ist nicht immer der Fall: Wenn der ursprüngliche Zeiger auf den richtigen Typ zeigt, ist er bereits ausgerichtet, also so Ohne Ausrichtungsprobleme wäre es sicher zu gießen.
Alex B

31

In C void *kann a ohne explizite Umwandlung in einen Zeiger auf ein Objekt eines anderen Typs konvertiert werden:

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

Dies hilft jedoch nicht dabei, Ihre Funktion allgemeiner zu schreiben.

Sie können a nicht dereferenzieren, void *wenn Sie es in einen anderen Zeigertyp konvertieren, da das Dereferenzieren eines Zeigers den Wert des Objekts erhält, auf das gezeigt wird. Ein nackter voidTyp ist kein gültiger Typ, daher ist eine Dereferenzierung void *nicht möglich.

Bei der Zeigerarithmetik geht es darum, Zeigerwerte um ein Vielfaches der sizeofObjekte zu ändern, auf die verwiesen wird. Da dies voidkein wahrer Typ ist, sizeof(void)hat er keine Bedeutung, sodass die Zeigerarithmetik für nicht gültig ist void *. (Einige Implementierungen erlauben dies unter Verwendung der entsprechenden Zeigerarithmetik für char *.)


1
@ CharlesBailey In deinen Code schreibst du void abc(void *a, int b). Aber warum nicht verwenden void abc(int *a, int b), da in Ihrer Funktion, die Sie schließlich definieren werden int *test = a;, eine Variable gespeichert wird, und ich sehe keine weiteren Vorteile, wenn Sie eine andere Variable definieren. Ich sehe viele Codes, die auf diese Weise void *als Parameter geschrieben wurden und später in der Funktion Variablen umwandeln . Da diese Funktion jedoch vom Autor geschrieben wurde, muss er die Verwendung der Variablen kennen. Warum also void *? Vielen Dank
Lion Lai

15

Sie sollten sich bewusst sein, dass es in C im Gegensatz zu Java oder C # absolut keine Möglichkeit gibt, den Objekttyp, auf den ein void*Zeiger zeigt , erfolgreich zu "erraten" . Ähnliches gibt es getClass()einfach nicht, da diese Informationen nirgends zu finden sind. Aus diesem Grund enthält die Art von "generisch", nach der Sie suchen, immer explizite Metainformationen, wie die int bin Ihrem Beispiel oder die Formatzeichenfolge in der printfFunktionsfamilie.


7

Ein ungültiger Zeiger wird als generischer Zeiger bezeichnet, der auf Variablen eines beliebigen Datentyps verweisen kann.


6

Bisher verstehe ich den leeren Zeiger wie folgt.

Wenn eine Zeigervariable mit dem Schlüsselwort void deklariert wird, wird sie zu einer universellen Zeigervariable. Die Adresse einer Variablen eines beliebigen Datentyps (char, int, float usw.) kann einer ungültigen Zeigervariable zugewiesen werden.

main()
{
    int *p;

    void *vp;

    vp=p;
} 

Da ein anderer Datentypzeiger dem ungültigen Zeiger zugewiesen werden kann, habe ich ihn in der Funktion absolut_value (Code unten gezeigt) verwendet. Um eine allgemeine Funktion zu machen.

Ich habe versucht, einen einfachen C-Code zu schreiben, der Integer oder Float als Argument verwendet und versucht, ihn + ve zu machen, wenn er negativ ist. Ich habe den folgenden Code geschrieben:

#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;
}   

Aber ich habe einen Fehler bekommen, also habe ich erfahren, dass mein Verständnis mit dem leeren Zeiger nicht korrekt ist :(. Also werde ich jetzt darauf zugehen, Punkte zu sammeln, warum ist das so?

Die Dinge, die ich mehr über leere Zeiger verstehen muss, sind die folgenden.

Wir müssen die Void-Zeigervariable typisieren, um sie zu dereferenzieren. Dies liegt daran, dass einem ungültigen Zeiger kein Datentyp zugeordnet ist. Der Compiler kann auf keinen Fall wissen (oder raten?), Auf welche Art von Daten der Void-Zeiger zeigt. Um die Daten zu erfassen, auf die ein ungültiger Zeiger zeigt, geben wir sie mit dem richtigen Typ der Daten ein, die sich in der Position der ungültigen Zeiger befinden.

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) );

}

Ein ungültiger Zeiger kann sehr nützlich sein, wenn der Programmierer nicht sicher ist, welchen Datentyp die vom Endbenutzer eingegebenen Daten haben. In einem solchen Fall kann der Programmierer einen leeren Zeiger verwenden, um auf die Position des unbekannten Datentyps zu zeigen. Das Programm kann so eingestellt werden, dass der Benutzer aufgefordert wird, die Art der Daten zu informieren, und das Typ-Casting kann gemäß den vom Benutzer eingegebenen Informationen durchgeführt werden. Ein Code-Snippet ist unten angegeben.

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
}

Ein weiterer wichtiger Punkt, den Sie bei leeren Zeigern beachten sollten, ist, dass - Zeigerarithmetik nicht in einem leeren Zeiger ausgeführt werden kann.

void *ptr;

int a;

ptr=&a;

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

Jetzt verstand ich, was mein Fehler war. Ich korrigiere das gleiche.

Verweise :

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

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

Der neue Code ist wie unten gezeigt.


#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;
}   

Danke dir,


2

Nein, das ist nicht möglich. Welchen Typ sollte der dereferenzierte Wert haben?


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

Ist dieses Programm möglich .... Bitte überprüfen Sie, ob dies in der C-Programmierung möglich ist ...
AGeek

2
Ja, dies ist möglich (obwohl der Code ohne eine Bereichsprüfung für b gefährlich ist). Printf verwendet eine variable Anzahl von Argumenten und a wird einfach als Zeiger (ohne Typinformationen) auf den Stapel geschoben und mithilfe von va_arg-Makros unter Verwendung von Informationen aus der Formatzeichenfolge in die Funktion printf eingefügt.
Hlovdal

@SiegeX: Bitte beschreiben Sie, was nicht portabel ist und was möglicherweise undefiniert ist.
Gauthier

@Gauthier, das eine Variable übergibt, die Formatspezifizierer enthält, ist kein portables und möglicherweise undefiniertes Verhalten. Wenn Sie so etwas tun möchten, dann schauen Sie sich das "vs" -Geschmack von an printf. Diese Antwort hat ein gutes Beispiel dafür.
SiegeX

In der Praxis würde ich wahrscheinlich durch int beine Aufzählung ersetzen , aber eine spätere Änderung dieser Aufzählung würde diese Funktion beeinträchtigen.
Jack Stout

1

Ich möchte diese Funktion generisch machen, ohne ifs zu verwenden. ist es möglich?

Der einzige einfache Weg, den ich sehe, ist die Verwendung von Überladung. Dies ist in der C-Programmiersprache AFAIK nicht verfügbar.

Haben Sie die C ++ - Programmiersprache für Ihr Programm berücksichtigt? Oder gibt es eine Einschränkung, die die Verwendung verbietet?


Ok, das ist eine Schande. Aus meiner Sicht sehe ich dann keine Lösungen. Tatsächlich ist es dasselbe wie printf () & cout: 2 verschiedene Möglichkeiten, das Drucken zu implementieren. printf () benutze die if () - Anweisung, um die
Formatzeichenfolge

1

Hier ist ein kurzer Hinweis auf voidZeiger: https://www.learncpp.com/cpp-tutorial/613-void-pointers/

6.13 - Leere Zeiger

Da der Void-Zeiger nicht weiß, auf welchen Objekttyp er zeigt, kann er nicht direkt dereferenziert werden! Vielmehr muss der void-Zeiger zuerst explizit in einen anderen Zeigertyp umgewandelt werden, bevor er dereferenziert wird.

Wenn ein ungültiger Zeiger nicht weiß, auf was er zeigt, woher wissen wir, wohin er gewirkt werden soll? Letztendlich liegt es an Ihnen, den Überblick zu behalten.

Leere Zeiger-Verschiedenartigkeit

Es ist nicht möglich, Zeigerarithmetik für einen leeren Zeiger durchzuführen. Dies liegt daran, dass die Zeigerarithmetik erfordert, dass der Zeiger weiß, auf welche Objektgröße er zeigt, damit er den Zeiger entsprechend inkrementieren oder dekrementieren kann.

Angenommen, der Arbeitsspeicher der Maschine ist byteadressierbar und erfordert keine ausgerichteten Zugriffe. Die allgemeinste und atomarste (der Darstellung auf Maschinenebene am nächsten liegende) Art, a zu interpretieren, void*ist ein Zeiger auf ein Byte uint8_t*. Wenn Sie a void*in a uint8_t*umwandeln, können Sie beispielsweise die ersten 1/2/4/8 / beliebig vielen Bytes ausdrucken, die an dieser Adresse beginnen, aber Sie können nicht viel anderes tun.

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

0

Sie können problemlos einen leeren Drucker drucken

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

1
Wer garantiert, dass voiddie gleiche Größe hat wie int?
Ant_222

Dies ist technisch gesehen kein Drucken eines voidZeigers, sondern das Drucken intan der Speicheradresse, die der Zeiger enthält (was in diesem Fall der Wert von wäre p).
Marionumber1

0

Da C eine statisch typisierte, stark typisierte Sprache ist, müssen Sie vor dem Kompilieren den Variablentyp festlegen. Wenn Sie versuchen, Generika in C zu emulieren, werden Sie am Ende versuchen, C ++ erneut zu schreiben. Daher ist es besser, stattdessen C ++ zu verwenden.


0

Der Void-Zeiger ist ein generischer Zeiger. Die Adresse eines beliebigen Datentyps einer Variablen kann einem Void-Zeiger zugewiesen werden.

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

Sie können einen Zeiger nicht dereferenzieren, ohne seinen Typ anzugeben, da verschiedene Datentypen unterschiedliche Speichergrößen haben, dh ein int ist 4 Byte, ein Zeichen ist 1 Byte.


-1

Grundsätzlich sind in C "Typen" eine Möglichkeit, Bytes im Speicher zu interpretieren. Zum Beispiel was der folgende Code

struct Point {
  int x;
  int y;
};

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

Sagt "Wenn ich main ausführe, möchte ich 4 (Größe der Ganzzahl) + 4 (Größe der Ganzzahl) = 8 (Gesamtbytes) Speicher zuweisen. Wenn ich '.x' als l-Wert auf einen Wert mit der Typbezeichnung schreibe Zeigen Sie zur Kompilierungszeit, rufen Sie Daten aus dem Speicherort des Zeigers plus vier Bytes ab. Geben Sie dem Rückgabewert die Bezeichnung zur Kompilierungszeit "int". "

Zur Laufzeit im Computer sieht Ihre "Punkt" -Struktur folgendermaßen aus:

00000000 00000000 00000000 00000000 00000000 00000000 00000000

Und so void*könnte Ihr Datentyp aussehen: (unter der Annahme eines 32-Bit-Computers)

10001010 11111001 00010010 11000101

Dies beantwortet die Frage nicht
MM

-2

Dies wird nicht funktionieren, aber void * kann viel dazu beitragen, einen generischen Zeiger auf Funktionen zu definieren und ihn als Argument an eine andere Funktion zu übergeben (ähnlich wie beim Rückruf in Java) oder eine Struktur zu definieren, die oop ähnelt.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.