Diese Frage geht an die C-Gurus da draußen:
In C kann ein Zeiger wie folgt deklariert werden:
char (* p)[10];
.. was im Grunde besagt, dass dieser Zeiger auf ein Array von 10 Zeichen zeigt. Das Schöne an der Deklaration eines solchen Zeigers ist, dass Sie einen Fehler bei der Kompilierung erhalten, wenn Sie versuchen, p einen Zeiger eines Arrays unterschiedlicher Größe zuzuweisen. Es wird auch ein Fehler bei der Kompilierung angezeigt, wenn Sie versuchen, p den Wert eines einfachen Zeichenzeigers zuzuweisen. Ich habe dies mit gcc versucht und es scheint mit ANSI, C89 und C99 zu funktionieren.
Es scheint mir sehr nützlich zu sein, einen Zeiger wie diesen zu deklarieren - insbesondere, wenn ein Zeiger an eine Funktion übergeben wird. Normalerweise schreiben die Leute den Prototyp einer solchen Funktion wie folgt:
void foo(char * p, int plen);
Wenn Sie einen Puffer einer bestimmten Größe erwarten würden, würden Sie einfach den Wert von plen testen. Es kann jedoch nicht garantiert werden, dass die Person, die p an Sie weitergibt, Ihnen tatsächlich gültige Speicherplätze in diesem Puffer gibt. Sie müssen darauf vertrauen, dass die Person, die diese Funktion aufgerufen hat, das Richtige tut. Andererseits:
void foo(char (*p)[10]);
..wurde den Anrufer zwingen, Ihnen einen Puffer der angegebenen Größe zu geben.
Dies scheint sehr nützlich zu sein, aber ich habe noch nie einen Zeiger gesehen, der in einem Code, auf den ich jemals gestoßen bin, so deklariert wurde.
Meine Frage ist: Gibt es einen Grund, warum Leute solche Zeiger nicht deklarieren? Sehe ich keine offensichtliche Falle?
10
kann das Array durch eine beliebige Variable im Gültigkeitsbereich ersetzt werdenAntworten:
Was Sie in Ihrem Beitrag sagen, ist absolut richtig. Ich würde sagen, dass jeder C-Entwickler zu genau der gleichen Entdeckung und zu genau der gleichen Schlussfolgerung kommt, wenn (wenn) er bestimmte Kenntnisse der C-Sprache erreicht.
Wenn die Besonderheiten Ihres Anwendungsbereichs ein Array mit einer bestimmten festen Größe erfordern (die Arraygröße ist eine Konstante für die Kompilierungszeit), können Sie ein solches Array nur mithilfe eines Zeiger-zu-Array-Parameters an eine Funktion übergeben
(In der C ++ - Sprache erfolgt dies auch mit Referenzen
).
Dies ermöglicht die Typprüfung auf Sprachebene, wodurch sichergestellt wird, dass das Array mit genau der richtigen Größe als Argument angegeben wird. Tatsächlich verwenden Menschen diese Technik in vielen Fällen implizit, ohne es überhaupt zu merken, und verstecken den Array-Typ hinter einem typedef-Namen
Beachten Sie außerdem, dass der obige Code in Bezug auf den
Vector3d
Typ, der ein Array oder ein Array ist, unveränderlich iststruct
. Sie können die Definition vonVector3d
jederzeit von einem Array in einstruct
und zurück ändern, ohne die Funktionsdeklaration ändern zu müssen. In beiden Fällen erhalten die Funktionen ein aggregiertes Objekt "als Referenz" (es gibt Ausnahmen, aber im Kontext dieser Diskussion ist dies wahr).Sie werden diese Methode der Array-Übergabe jedoch nicht allzu oft explizit verwenden, einfach weil zu viele Leute durch eine ziemlich verworrene Syntax verwirrt sind und mit solchen Funktionen der C-Sprache einfach nicht vertraut genug sind, um sie richtig zu verwenden. Aus diesem Grund ist es im durchschnittlichen realen Leben ein beliebterer Ansatz, ein Array als Zeiger auf sein erstes Element zu übergeben. Es sieht einfach "einfacher" aus.
In Wirklichkeit ist die Verwendung des Zeigers auf das erste Element für die Array-Übergabe eine sehr Nischentechnik, ein Trick, der einem ganz bestimmten Zweck dient: Sein einziger Zweck besteht darin, die Übergabe von Arrays unterschiedlicher Größe (dh Laufzeitgröße) zu erleichtern. . Wenn Sie wirklich in der Lage sein müssen, Arrays mit Laufzeitgröße zu verarbeiten, können Sie ein solches Array am besten durch einen Zeiger auf sein erstes Element übergeben, wobei die konkrete Größe durch einen zusätzlichen Parameter angegeben wird
Tatsächlich ist es in vielen Fällen sehr nützlich, Arrays mit Laufzeitgröße verarbeiten zu können, was ebenfalls zur Popularität der Methode beiträgt. Viele C-Entwickler stoßen einfach nie auf die Notwendigkeit (oder erkennen sie nie), ein Array mit fester Größe zu verarbeiten, und sind sich daher der richtigen Technik mit fester Größe nicht bewusst.
Wenn die Arraygröße jedoch fest ist, übergeben Sie sie als Zeiger auf ein Element
ist ein schwerwiegender Fehler auf technischer Ebene, der heutzutage leider ziemlich weit verbreitet ist. Eine Zeiger-zu-Array-Technik ist in solchen Fällen ein viel besserer Ansatz.
Ein weiterer Grund, der die Einführung der Array-Passing-Technik mit fester Größe behindern könnte, ist die Dominanz des naiven Ansatzes zur Typisierung dynamisch zugeordneter Arrays. Wenn das Programm beispielsweise feste Arrays vom Typ
char[10]
(wie in Ihrem Beispiel) aufruft , verwendet ein durchschnittlicher Entwicklermalloc
Arrays wieDieses Array kann nicht an eine als deklarierte Funktion übergeben werden
Dies verwirrt den durchschnittlichen Entwickler und veranlasst ihn, die Parameterdeklaration mit fester Größe aufzugeben, ohne weiter darüber nachzudenken. In Wirklichkeit liegt die Wurzel des Problems jedoch im naiven
malloc
Ansatz. Dasmalloc
oben gezeigte Format sollte für Arrays mit Laufzeitgröße reserviert werden. Wenn der Array-Typ eine Größe zur Kompilierungszeit hat,malloc
sieht der folgende Weg besser ausDies kann natürlich leicht an die oben angegebene weitergegeben werden
foo
und der Compiler führt die richtige Typprüfung durch. Aber auch dies ist für einen unvorbereiteten C-Entwickler zu verwirrend, weshalb Sie es im "typischen" durchschnittlichen Alltagscode nicht allzu oft sehen.
quelle
const
mit dieser Technik mit Eigentum umgehen . Einconst char (*p)[N]
Argument scheint nicht mit einem Zeiger auf kompatibel zu sein.char table[N];
Im Gegensatz dazuchar*
bleibt ein einfaches ptr mit einemconst char*
Argument kompatibel .(*p)[i]
und nicht , um auf ein Element Ihres Arrays zuzugreifen*p[i]
. Letzteres springt um die Größe des Arrays, was mit ziemlicher Sicherheit nicht das ist, was Sie wollen. Zumindest für mich hat das Erlernen dieser Syntax eher einen Fehler verursacht als verhindert; Ich hätte schneller korrekten Code erhalten, wenn ich nur einen Float * übergeben hätte.const
Zeiger auf ein Array von veränderlichen Elementen. Und ja, das ist völlig anders als ein Zeiger auf ein Array unveränderlicher Elemente.Ich möchte die Antwort von AndreyT ergänzen (falls jemand auf diese Seite stößt und nach weiteren Informationen zu diesem Thema sucht):
Als ich anfange, mehr mit diesen Deklarationen zu spielen, stelle ich fest, dass mit ihnen in C ein großes Handicap verbunden ist (anscheinend nicht in C ++). Es kommt häufig vor, dass Sie einem Aufrufer einen konstanten Zeiger auf einen Puffer geben möchten, in den Sie geschrieben haben. Leider ist dies nicht möglich, wenn ein Zeiger wie dieser in C deklariert wird. Mit anderen Worten, der C-Standard (6.7.3 - Absatz 8) steht im Widerspruch zu so etwas:
Diese Einschränkung scheint in C ++ nicht vorhanden zu sein, was diese Art von Deklarationen weitaus nützlicher macht. Im Fall von C ist es jedoch erforderlich, immer dann auf eine reguläre Zeigerdeklaration zurückzugreifen, wenn Sie einen const-Zeiger auf den Puffer mit fester Größe wünschen (es sei denn, der Puffer selbst wurde zunächst als const deklariert). Weitere Informationen finden Sie in diesem Mail-Thread: Link-Text
Dies ist meiner Meinung nach eine schwerwiegende Einschränkung und könnte einer der Hauptgründe sein, warum Menschen in C normalerweise keine Zeiger wie diesen deklarieren. Der andere Grund ist die Tatsache, dass die meisten Menschen nicht einmal wissen, dass Sie einen Zeiger wie diesen als deklarieren können AndreyT hat darauf hingewiesen.
quelle
Der offensichtliche Grund ist, dass dieser Code nicht kompiliert wird:
Die Standard-Heraufstufung eines Arrays erfolgt auf einen nicht qualifizierten Zeiger.
Siehe auch diese Frage ,
foo(&p)
sollte funktionieren.quelle
foo(&p)
.Einfach gesagt, C macht die Dinge nicht so. Ein Array vom Typ
T
wird als Zeiger auf das ersteT
im Array herumgereicht, und das ist alles, was Sie erhalten.Dies ermöglicht einige coole und elegante Algorithmen, wie das Durchlaufen des Arrays mit Ausdrücken wie
Der Nachteil ist, dass die Verwaltung der Größe bei Ihnen liegt. Leider hat das Versäumnis, dies gewissenhaft zu tun, auch zu Millionen von Fehlern in der C-Codierung und / oder zu Möglichkeiten für böswillige Ausbeutung geführt.
Was in C dem nahe kommt, was Sie verlangen, ist, einen
struct
(nach Wert) oder einen Zeiger auf einen (nach Referenz) zu übergeben. Solange auf beiden Seiten dieses Vorgangs derselbe Strukturtyp verwendet wird, stimmen sowohl der Code, der die Referenz ausgibt, als auch der Code, der sie verwendet, über die Größe der verarbeiteten Daten überein.Ihre Struktur kann beliebige Daten enthalten. Es könnte Ihr Array mit einer genau definierten Größe enthalten.
Nichts hindert Sie oder einen inkompetenten oder böswilligen Codierer daran, Casts zu verwenden, um den Compiler dazu zu bringen, Ihre Struktur als eine andere Größe zu behandeln. Die fast ungebundene Fähigkeit, so etwas zu tun, ist Teil von Cs Design.
quelle
Sie können ein Array von Zeichen auf verschiedene Arten deklarieren:
Der Prototyp einer Funktion, die ein Array nach Wert annimmt, lautet:
oder durch Bezugnahme:
oder nach Array-Syntax:
quelle
char (*p)[10] = malloc(sizeof *p)
.Ich würde diese Lösung nicht empfehlen
da es die Tatsache verdeckt, dass Vector3D einen Typ hat, den Sie kennen müssen. Programmierer erwarten normalerweise nicht, dass Variablen desselben Typs unterschiedliche Größen haben. Erwägen :
wobei sizeof a! = sizeof b
quelle
sizeof(a)
nicht dasselbe wiesizeof(b)
?Ich möchte diese Syntax auch verwenden, um mehr Typprüfung zu ermöglichen.
Ich stimme aber auch zu, dass die Syntax und das mentale Modell der Verwendung von Zeigern einfacher und leichter zu merken sind.
Hier sind einige weitere Hindernisse, auf die ich gestoßen bin.
Der Zugriff auf das Array erfordert Folgendes
(*p)[]
:Es ist verlockend, stattdessen einen lokalen Zeiger auf Zeichen zu verwenden:
Dies würde jedoch den Zweck der Verwendung des richtigen Typs teilweise zunichte machen.
Man muss daran denken, den Operator address-of zu verwenden, wenn die Adresse eines Arrays einem Zeiger auf das Array zugewiesen wird:
Der Adressoperator erhält die Adresse des gesamten Arrays
&a
mit dem richtigen Typ, dem er zugewiesen werden sollp
. Ohne den Operatora
wird automatisch in die Adresse des ersten Elements des Arrays konvertiert, wie in&a[0]
, das einen anderen Typ hat.Da diese automatische Konvertierung bereits stattfindet, bin ich immer verwirrt, dass dies
&
notwendig ist. Es ist konsistent mit der Verwendung von&
on-Variablen anderer Typen, aber ich muss bedenken, dass ein Array etwas Besonderes ist und dass ich das benötige&
, um den richtigen Adresstyp zu erhalten, obwohl der Adresswert der gleiche ist.Ein Grund für mein Problem könnte sein, dass ich K & R C bereits in den 80er Jahren gelernt habe, was die Verwendung des
&
Operators für ganze Arrays noch nicht erlaubte (obwohl einige Compiler dies ignorierten oder die Syntax tolerierten). Dies ist übrigens möglicherweise ein weiterer Grund, warum es schwierig ist, Zeiger auf Arrays zu übernehmen: Sie funktionieren erst seit ANSI C ordnungsgemäß, und die&
Bedienerbeschränkung war möglicherweise ein weiterer Grund, sie als zu umständlich zu betrachten.Wenn
typedef
ist nicht eine Art für das Zeiger-to-Array verwendet zu erstellen (in einer gemeinsamen Header - Datei), dann ein globaler Zeiger-to-Array muss eine kompliziertereextern
Erklärung es über Dateien zu teilen:quelle
Vielleicht fehlt mir etwas, aber ... da Arrays konstante Zeiger sind, bedeutet dies im Grunde, dass es keinen Sinn macht, Zeiger auf sie herumzugeben.
Könntest du nicht einfach benutzen
void foo(char p[10], int plen);
?quelle
Auf meinem Compiler (vs2008) wird es
char (*p)[10]
als Array von Zeichenzeigern behandelt , als ob es keine Klammern gäbe, selbst wenn ich als C-Datei kompiliere. Unterstützt der Compiler diese "Variable"? Wenn ja, ist dies ein Hauptgrund, es nicht zu verwenden.quelle