Frage C-Funktionszeiger, der auf den Zeiger ablehnt


Ich versuche, das folgende Programm auszuführen, aber einige seltsame Fehler zu erhalten:

Datei 1.c:

typedef unsigned long (*FN_GET_VAL)(void);

FN_GET_VAL gfnPtr;

void setCallback(const void *fnPointer)
{
    gfnPtr = *((FN_GET_VAL*) (&fnPointer));
}

Datei 2.c:

extern FN_GET_VAL gfnPtr;

unsigned long myfunc(void)
{
    return 0;
}

main()
{
   setCallback((void*)myfunc);
   gfnPtr(); /* Crashing as value was not properly 
                assigned in setCallback function */
}

Hier stürzt der gfnPtr () auf 64-Bit suse linux ab, wenn er mit gcc kompiliert wird. Aber es erfolgreich aufrufen gfnPtr () VC6 und SunOS.

Aber wenn ich die Funktion wie unten angegeben ändere, funktioniert es erfolgreich.

void setCallback(const void *fnPointer)
{
    int i; // put any statement here
    gfnPtr = *((FN_GET_VAL*) (&fnPointer));
}

Bitte helfen Sie mit der Ursache des Problems. Vielen Dank.


16
2018-04-07 10:46


Ursprung


Antworten:


Der Standard erlaubt keine Funktionszeiger auf void*. Sie können nur auf einen anderen Funktionszeigertyp umstellen. 6.3.2.3 §8:

Ein Zeiger auf eine Funktion eines Typs   kann in einen Zeiger auf a konvertiert werden   Funktion eines anderen Typs und zurück   nochmal

Wichtig ist, dass Sie zum ursprünglichen Typ zurückkehren müssen, bevor Sie den Zeiger verwenden, um die Funktion aufzurufen (technisch zu einem kompatiblen Typ. Definition von "kompatibel" bei 6.2.7).


30
2018-04-07 10:51



Der Standard lässt leider keine Umwandlung zwischen Datenzeigern und Funktionszeigern zu (da dies auf einigen wirklich obskuren Plattformen keinen Sinn ergibt), obwohl POSIX und andere solche Umwandlungen erfordern. Eine Problemumgehung besteht nicht darin, den Zeiger zu werfen, sondern einen Zeiger auf den Zeiger zu werfen (dies ist vom Compiler OK und es wird die Aufgabe auf allen normalen Plattformen erledigen).

typedef void (*FPtr)(void); // Hide the ugliness
FPtr f = someFunc;          // Function pointer to convert
void* ptr = *(void**)(&f);  // Data pointer
FPtr f2 = *(FPtr*)(&ptr);   // Function pointer restored

13
2018-05-22 02:51



Ich habe drei Faustregeln, wenn es um Datenzeiger und Codezeiger geht:

  • Machen nicht Mischen Sie Datenzeiger und Codezeiger
  • Unterlassen Sie mischen Datenzeiger und Codezeiger
  • Unterlassen Sie je Mischen Sie Datenzeiger und Codezeiger!

In der folgenden Funktion:

void setCallback(const void *fnPointer)
{
    gfnPtr = *((FN_GET_VAL*) (&fnPointer));
}

Du hast ein Datenzeiger dass Sie mit einem Funktionszeiger umgehen. (Ganz zu schweigen davon, dass Sie dies tun, indem Sie zuerst die Adresse des Zeigers selbst übernehmen, ihn auf einen Zeiger auf einen Zeiger werfen, bevor Sie ihn de-referenzieren).

Versuche es wie folgt umzuschreiben:

void setCallback(FN_GET_VAL fnPointer)
{
     gfnPtr = fnPointer;
}

Außerdem können (oder sollten) Sie die Besetzung beim Setzen des Zeigers abbrechen:

main()
{
   setCallback(myfunc);
   gfnPtr();
}

Als zusätzlichen Bonus können Sie nun die normalen Typprüfungen des Compilers verwenden.


10
2018-04-07 10:55



Ich werde ein mögliches vorschlagen teilweise Erläuterung.

@Manoj Wenn Sie die von beiden Compilern generierte Assembly-Auflistung für SetCallback untersuchen (oder bereitstellen) können wir eine definitive Antwort erhalten.

Zum einen sind Pascal Couqs Aussagen korrekt und Lindydancer zeigt, wie man den Callback richtig setzt. Meine Antwort ist nur ein Versuch, das eigentliche Problem zu erklären.

Ich denke, das Problem rührt von der Tatsache her, dass Linux und die andere Plattform verschiedene 64-Bit-Modelle verwenden (siehe 64-Bit-Modelle auf Wikipedia). Beachten Sie, dass Linux LP64 verwendet (Int ist 32 Bit). Wir brauchen mehr Details auf der anderen Plattform. Wenn es SPARC64 ist, verwendet es ILP64 (Int ist 64 Bit).

Wie ich Sie verstehe, wurde das Problem nur unter Linux beobachtet und ging weg, wenn Sie eine int lokale Variable einführten. Hast du das mit Optimierungen probiert? Höchstwahrscheinlich hätte dieser Hack bei Optimierungen keinen positiven Effekt.

Unter beiden 64-Bit-Modellen sollten Zeiger 64-Bit sein, unabhängig davon, ob sie auf Code oder Daten zeigen. Es ist jedoch möglich, dass dies nicht der Fall wäre (z. B. segmentierte Speichermodelle); daher die Ermahnungen von Pascal und Lindydancer.

Wenn die Zeiger die gleiche Größe haben, bleibt ein mögliches Stapelausrichtungsproblem übrig. Die Einführung eines lokalen int (das 32 Bit unter Linux ist) könnte die Ausrichtung verändern. Dies würde nur Auswirkungen haben, wenn void * - und Funktionszeiger unterschiedliche Ausrichtungsanforderungen haben. Ein zweifelhaftes Szenario.

Dennoch sind die verschiedenen 64-Bit-Speichermodelle wahrscheinlich die Ursache für das, was Sie beobachtet haben. Sie können gerne die Montageauflistungen zur Verfügung stellen, damit wir sie analysieren können.


2
2017-12-02 10:15



im Gegensatz zu dem, was andere sagen, ja, du kannst eine haben void* Zeiger als Funktionszeiger, aber die Semantik ist sehr schwierig zu verwenden.

Wie Sie sehen können, müssen Sie es nicht als void*Ordne es einfach wie üblich zu. Ich habe deinen Code so ausgeführt und ich habe ihn bearbeitet.

Datei1.c:

typedef unsigned long (*FN_GET_VAL)(void);

extern FN_GET_VAL gfnPtr;

void setCallback(const void *fnPointer)
{
    gfnPtr = ((FN_GET_VAL) fnPointer);
}

file2.c:

int main(void)
{
    setCallback(myfunc);
    (( unsigned long(*)(void) )gfnPtr)();
}

0
2017-10-16 16:47