Bitte schauen Sie sich den folgenden Code an. Es wird versucht, ein Array als char**
an eine Funktion zu übergeben:
#include <stdio.h>
#include <stdlib.h>
static void printchar(char **x)
{
printf("Test: %c\n", (*x)[0]);
}
int main(int argc, char *argv[])
{
char test[256];
char *test2 = malloc(256);
test[0] = 'B';
test2[0] = 'A';
printchar(&test2); // works
printchar((char **) &test); // crashes because *x in printchar() has an invalid pointer
free(test2);
return 0;
}
Die Tatsache, dass ich es nur durch explizites Casting &test2
zum Kompilieren bringen kann, char**
deutet bereits darauf hin, dass dieser Code falsch ist.
Trotzdem frage ich mich, was genau daran falsch ist. Ich kann einen Zeiger auf einen Zeiger auf ein dynamisch zugewiesenes Array übergeben, aber ich kann keinen Zeiger auf einen Zeiger für ein Array auf dem Stapel übergeben. Natürlich kann ich das Problem leicht umgehen, indem ich das Array zuerst einer temporären Variablen zuweise, wie folgt:
char test[256];
char *tmp = test;
test[0] = 'B';
printchar(&tmp);
Kann mir trotzdem jemand erklären, warum es nicht funktioniert char[256]
, char**
direkt zu besetzen?
char (*)[256]
zu übergebenchar**
?char**
. Ohne diese Besetzung wird es nicht kompiliert.test
ist ein Array, kein Zeiger und&test
ein Zeiger auf das Array. Es ist kein Zeiger auf einen Zeiger.Möglicherweise wurde Ihnen mitgeteilt, dass ein Array ein Zeiger ist, dies ist jedoch falsch. Der Name eines Arrays ist ein Name des gesamten Objekts - aller Elemente. Es ist kein Zeiger auf das erste Element. In den meisten Ausdrücken wird ein Array automatisch in einen Zeiger auf sein erstes Element konvertiert. Das ist eine Annehmlichkeit, die oft nützlich ist. Es gibt jedoch drei Ausnahmen von dieser Regel:
sizeof
.&
.In
&test
ist das Array der Operand von&
, sodass die automatische Konvertierung nicht erfolgt. Das Ergebnis von&test
ist ein Zeiger auf ein Array von 256char
, das den Typchar (*)[256]
nicht hatchar **
.Um einen Zeiger auf einen Zeiger
char
von zu erhaltentest
, müssten Sie zuerst einen Zeiger auf erstellenchar
. Zum Beispiel:Eine andere Möglichkeit, darüber nachzudenken, besteht darin, zu erkennen, dass
test
das gesamte Objekt benannt wird - das gesamte Array von 256char
. Es wird kein Zeiger benannt, daher&test
gibt es in keinen Zeiger, dessen Adresse verwendet werden kann, sodass dies keinen erzeugen kannchar **
. Um ein zu erstellenchar **
, müssen Sie zuerst ein habenchar *
.quelle
_Alignof
nebensizeof
und auch der Operator erwähnt&
. Ich frage mich, warum sie es entfernt haben ..._Alignof
Akzeptiert nur einen Typnamen als Operanden und niemals ein Array oder ein anderes Objekt als Operanden. (Ich weiß nicht warum; es scheint syntaktisch und grammatikalisch so zu seinsizeof
, aber es ist nicht so.)Die Art von
test2
istchar *
. Der Typ von&test2
wird alsochar **
mit dem Parametertypx
von kompatibel seinprintchar()
.Die Art von
test
istchar [256]
. Der Typ von&test
wird also sein,char (*)[256]
der nicht mit dem Typ des Parametersx
von kompatibel istprintchar()
.Lassen Sie mich Ihnen den Unterschied in Bezug auf Adressen von
test
und zeigentest2
.Ausgabe:
Hier ist zu beachten:
Die Ausgabe (Speicheradresse) von
test2
und&test2[0]
ist numerisch gleich und ihr Typ ist ebenfalls gleichchar *
.Aber die
test2
und&test2
sind unterschiedliche Adressen und ihr Typ ist auch unterschiedlich.Die Art von
test2
istchar *
.Die Art von
&test2
istchar **
.Die Ausgang (Speicheradresse) aus
test
,&test
und&test[0]
ist numerisch gleiche aber ihre Art unterscheidet .Die Art von
test
istchar [256]
.Die Art von
&test
istchar (*) [256]
.Die Art von
&test[0]
istchar *
.Wie die Ausgabe zeigt,
&test
ist das gleiche wie&test[0]
.Daher erhalten Sie einen Segmentierungsfehler.
quelle
Sie können nicht auf einen Zeiger auf einen Zeiger zugreifen, da
&test
es sich nicht um einen Zeiger handelt, sondern um ein Array.Wenn Sie die Adresse eines Arrays verwenden, das Array und die Adresse des Arrays umwandeln
(void *)
und vergleichen, sind sie (mit Ausnahme möglicher Zeigerpedanterie) gleichwertig.Was Sie wirklich tun, ist ähnlich (abgesehen von striktem Aliasing):
das ist ganz offensichtlich falsch.
quelle
Ihr Code erwartet, dass das Argument
x
vonprintchar
auf den Speicher verweist, der a enthält(char *)
.Beim ersten Aufruf zeigt es auf den Speicher, für den es verwendet wird,
test2
und ist somit tatsächlich ein Wert, der auf a zeigt(char *)
, wobei letzterer auf den zugewiesenen Speicher zeigt.Beim zweiten Aufruf gibt es jedoch keinen Ort, an dem ein solcher
(char *)
Wert gespeichert werden könnte, und daher ist es unmöglich, auf einen solchen Speicher zu verweisen. Die von(char **)
Ihnen hinzugefügte Besetzung hätte einen Kompilierungsfehler (beim Konvertieren(char *)
in(char **)
) beseitigt , aber der Speicher würde nicht aus dem Nichts erscheinen und eine(char *)
Initialisierung enthalten , die auf die ersten Zeichen des Tests verweist. Das Zeiger-Casting in C ändert den tatsächlichen Wert des Zeigers nicht.Um zu bekommen, was Sie wollen, müssen Sie es explizit tun:
Ich gehe davon aus, dass Ihr Beispiel eine Destillation eines viel größeren Codeteils ist. Als Beispiel möchten Sie möglicherweise
printchar
den(char *)
Wert erhöhen, auf den der übergebenex
Wert zeigt, damit beim nächsten Aufruf das nächste Zeichen gedruckt wird. Wenn dies nicht der Fall ist, geben Sie dann einfach einen(char *)
Hinweis auf das zu druckende Zeichen oder geben Sie das Zeichen selbst weiter.quelle
char **
. Array - Variablen / Objekte einfach sind die Array, mit der Adresse ist implizit, nicht gespeichert überall. Keine zusätzliche Indirektionsebene für den Zugriff, im Gegensatz zu einer Zeigervariablen, die auf einen anderen Speicher verweist.Anscheinend ist die Adresse von
test
die gleiche wie die Adresse vontest[0]
:Kompilieren Sie das und führen Sie Folgendes aus:
Die ultimative Ursache für den Segmentierungsfehler ist also, dass dieses Programm versucht, die absolute Adresse
0x42
(auch bekannt als'B'
) zu dereferenzieren , die Ihr Programm nicht lesen darf.Obwohl bei einem anderen Compiler / Computer die Adressen unterschiedlich sind: Probieren Sie es online aus! , aber Sie werden dies aus irgendeinem Grund immer noch bekommen:
Die Adresse, die den Segmentierungsfehler verursacht, kann jedoch sehr unterschiedlich sein:
quelle
test
ist nicht dasselbe wie die Adresse von zu nehmentest[0]
. Ersteres hat Typchar (*)[256]
und letzteres hat Typchar *
. Sie sind nicht kompatibel und der C-Standard erlaubt ihnen unterschiedliche Darstellungen.%p
, sollte er in konvertiert werdenvoid *
(wiederum aus Gründen der Kompatibilität und Darstellung).printchar(&test);
kann für Sie abstürzen, aber das Verhalten ist nicht durch den C-Standard definiert, und Personen können unter anderen Umständen andere Verhaltensweisen beobachten.&test == &test[0]
verstößt gegen die Einschränkungen in C 2018 6.5.9 2, da die Typen nicht kompatibel sind. Der C-Standard erfordert eine Implementierung, um diese Verletzung zu diagnostizieren, und das resultierende Verhalten wird vom C-Standard nicht definiert. Das bedeutet, dass Ihr Compiler möglicherweise Code erstellt, der sie als gleich bewertet, ein anderer Compiler jedoch möglicherweise nicht.Die Darstellung von
char [256]
ist implementierungsabhängig. Es darf nicht dasselbe sein wiechar *
.Gießen
&test
des Typschar (*)[256]
zuchar **
Ausbeuten nicht definiertes Verhalten.Bei einigen Compilern kann es das tun, was Sie erwarten, bei anderen nicht.
BEARBEITEN:
Nach dem Testen mit gcc 9.2.1 scheint dies
printchar((char**)&test)
tatsächlichtest
als Wert übergeben zu werdenchar**
. Es ist, als ob die Anweisung warprintchar((char**)test)
. In derprintchar
Funktionx
ist ein Zeiger auf das erste Zeichen des Array-Tests, kein Doppelzeiger auf das erste Zeichen. Eine doppelte Referenzentfernungx
führt zu einem Segmentierungsfehler, da die 8 ersten Bytes des Arrays keiner gültigen Adresse entsprechen.Ich bekomme genau das gleiche Verhalten und Ergebnis beim Kompilieren des Programms mit clang 9.0.0-2.
Dies kann als Compiler-Fehler oder als Ergebnis eines undefinierten Verhaltens angesehen werden, dessen Ergebnis möglicherweise compilerspezifisch ist.
Ein weiteres unerwartetes Verhalten ist der Code
Die Ausgabe ist
Das seltsame Verhalten ist das
x
und*x
hat den gleichen Wert.Dies ist eine Compilersache. Ich bezweifle, dass dies durch die Sprache definiert ist.
quelle
char (*)[256]
implementierungsabhängig ist? Die Darstellung vonchar [256]
ist in dieser Frage nicht relevant - es handelt sich nur um eine Reihe von Bits. Aber selbst wenn Sie meinen, dass sich die Darstellung eines Zeigers auf ein Array von der Darstellung eines Zeigers auf einen Zeiger unterscheidet, verfehlt dies auch den Punkt. Selbst wenn sie dieselben Darstellungen haben, würde der OP-Code nicht funktionieren, da der Zeiger auf einen Zeiger wie in zweimal zweimal dereferenziert werden kann,printchar
der Zeiger auf ein Array jedoch nicht, unabhängig von der Darstellung.char (*)[256]
bischar **
wird vom Compiler akzeptiert, liefert jedoch nicht das erwartete Ergebnis, da achar [256]
nicht mit a identisch istchar *
. Ich nahm an, dass die Codierung anders ist, sonst würde es das erwartete Ergebnis liefern.char **
das Verhalten bei unzureichender Ausrichtung undefiniert ist und ansonsten, wenn das Ergebnis zurück in konvertiert wirdchar (*)[256]
, dem ursprünglichen Zeiger entspricht. Mit "erwartetes Ergebnis" meinen Sie möglicherweise, dass, wenn(char **) &test
es weiter in a konvertiert wirdchar *
, es gleich ist&test[0]
. Dies ist bei Implementierungen, die einen flachen Adressraum verwenden, kein unwahrscheinliches Ergebnis, aber keine reine Repräsentationssache.char **
ischar *
) nicht korrekt ausgerichtet ist , ist das Verhalten undefiniert. Andernfalls wird die Konvertierung gemäß meinem obigen Kommentar definiert, obwohl der Wert nur teilweise definiert ist.char (*x)[256]
ist nicht dasselbe wiechar **x
. Der Grundx
und der*x
Druck des gleichen Zeigerwerts ist, dassx
es sich lediglich um einen Zeiger auf das Array handelt. Ihr*x
ist das Array , und wenn Sie es in einem Zeigerkontext verwenden, wird es auf die Adresse des Arrays zurückgesetzt . Kein Compiler-Fehler (oder was(char **)&test
auch immer), nur ein wenig mentale Gymnastik, um herauszufinden, was mit Typen los ist. (cdecl erklärt es als "deklariere x als Zeiger auf das Array 256 von char"). Selbst die Verwendungchar*
für den Zugriff auf die Objektdarstellung von achar**
ist nicht UB; es kann alles alias.