Funktionen, die Zeichenfolgen zurückgeben, guter Stil?

11

In meinen C-Programmen benötige ich häufig eine Möglichkeit, eine Zeichenfolgendarstellung meiner ADTs zu erstellen. Auch wenn ich die Zeichenfolge nicht drucken muss, um sie in irgendeiner Weise auf dem Bildschirm anzuzeigen, ist es ordentlich, eine solche Methode zum Debuggen zu haben. Diese Art von Funktion kommt also oft vor.

char * mytype_to_string( const mytype_t *t );

Mir ist tatsächlich klar, dass ich hier (mindestens) drei Optionen habe, um den Speicher für die Rückgabe der Zeichenfolge zu verwalten.

Alternative 1: Speichern der Rückgabezeichenfolge in einem statischen Zeichenarray in der Funktion. Ich brauche nicht viel nachzudenken, außer dass die Zeichenfolge bei jedem Aufruf überschrieben wird. Was in manchen Fällen ein Problem sein kann.

Alternative 2: Ordnen Sie die Zeichenfolge auf dem Heap mit malloc innerhalb der Funktion zu. Wirklich ordentlich, da ich dann nicht an die Größe eines Puffers oder das Überschreiben denken muss. Ich muss jedoch daran denken, die Zeichenfolge freizugeben (), wenn ich fertig bin, und dann muss ich auch einer temporären Variablen zuweisen, damit ich sie freigeben kann. und dann ist die Heap-Zuweisung wirklich viel langsamer als die Stapelzuweisung. Seien Sie daher ein Engpass, wenn dies in einer Schleife wiederholt wird.

Alternative 3: Übergeben Sie den Zeiger auf einen Puffer und lassen Sie den Aufrufer diesen Puffer zuweisen. Mögen:

char * mytype_to_string( const mytype_t *mt, char *buf, size_t buflen ); 

Dies bringt dem Anrufer mehr Aufwand. Ich stelle auch fest, dass diese Alternative mir eine andere Option in der Reihenfolge der Argumente gibt. Welches Argument sollte ich zuerst und zuletzt haben? (eigentlich sechs Möglichkeiten)

Also, was soll ich bevorzugen? Warum? Gibt es einen ungeschriebenen Standard unter C-Entwicklern?

Øystein Schønning-Johansen
quelle
3
Nur eine Anmerkung, die meisten Betriebssysteme verwenden Option 3 - der Anrufer weist ohnehin Puffer zu; teilt den Pufferzeiger und die Kapazität mit; Angerufene füllt den Puffer und gibt auch die tatsächliche Länge der Zeichenfolge zurück, wenn der Puffer nicht ausreicht. Beispiel: sysctlbynameunter OS X und iOS
Uhr

Antworten:

11

Die Methoden, die ich am meisten gesehen habe, sind 2 und 3.

Der vom Benutzer bereitgestellte Puffer ist eigentlich recht einfach zu verwenden:

char[128] buffer;
mytype_to_string(mt, buffer, 128);

Die meisten Implementierungen geben jedoch die Menge des verwendeten Puffers zurück.

Option 2 ist langsamer und gefährlich, wenn dynamisch verknüpfte Bibliotheken verwendet werden, in denen möglicherweise unterschiedliche Laufzeiten (und unterschiedliche Heaps) verwendet werden. Sie können also nicht freigeben, was in einer anderen Bibliothek gespeichert wurde. Dies erfordert dann eine free_string(char*)Funktion, um damit umzugehen.

Ratschenfreak
quelle
Vielen Dank! Ich denke, Alternative 3 gefällt mir auch am besten. Ich möchte jedoch in der Lage sein, Dinge zu tun wie: printf("MyType: %s\n", mytype_to_string( mt, buf, sizeof(buf));und daher möchte ich nicht die verwendete Länge zurückgeben, sondern den Zeiger auf die Zeichenfolge. Der dynamische Bibliothekskommentar ist wirklich wichtig.
Øystein Schønning-Johansen
Sollte dies nicht sein sizeof(buffer) - 1, um den \0Terminator zufrieden zu stellen ?
Michael-O
1
@ Michael-O nein, der Nullterm ist in der Puffergröße enthalten, was bedeutet, dass die maximale Zeichenfolge, die eingegeben werden kann, 1 weniger als die übergebene Größe ist. Dies ist das Muster, mit dem die sichere Zeichenfolge in der Standardbibliothek wie snprintfverwendet funktioniert .
Ratschenfreak
@ratchetfreak Danke für die Klarstellung. Wäre schön, die Antwort mit dieser Weisheit zu erweitern.
Michael-O
0

Zusätzliche Designidee für # 3

mytypeGeben Sie nach Möglichkeit auch die maximale Größe an, die für dieselbe .h-Datei wie erforderlich ist mytype_to_string().

#define MYTYPE_TO_STRING_SIZE 256

Jetzt kann der Benutzer entsprechend codieren.

char buf[MYTYPE_TO_STRING_SIZE];
puts(mytype_to_string(mt, buf, sizeof buf));

Bestellen

Die Größe der Arrays ermöglicht, wenn zuerst, VLA-Typen.

char * mytype_to_string( const mytype_t *mt, size_t bufsize, char *buf[bufsize]); 

Nicht so wichtig bei einer Dimension, aber nützlich bei 2 oder mehr.

void matrix(size_t row, size_t col, double matrix[row][col]);

Ich erinnere mich, dass das Lesen der Größe zuerst eine bevorzugte Redewendung im nächsten C ist. Ich muss diese Referenz finden ...

chux - Monica wieder einsetzen
quelle
0

Als Ergänzung zu @ ratchetfreaks hervorragender Antwort möchte ich darauf hinweisen, dass Alternative Nr. 3 einem ähnlichen Paradigma / Muster folgt wie Standardfunktionen der C-Bibliothek.

Zum Beispiel strncpy.

char * strncpy ( char * destination, const char * source, size_t num );

Das Befolgen des gleichen Paradigmas würde dazu beitragen, die kognitive Belastung für neue Entwickler (oder sogar Ihr zukünftiges Selbst) zu verringern, wenn diese Ihre Funktion nutzen müssen.

Der einzige Unterschied zu dem, was Sie in Ihrem Beitrag haben, besteht darin, dass das destinationArgument in den C-Bibliotheken in der Regel zuerst in der Argumentliste aufgeführt wird. So:

char * mytype_to_string( char *buf, const mytype_t *mt, size_t buflen ); 
John Go-Soco
quelle
-2

Neben der Tatsache, dass Sie einen schlechten Code-Geruch vorschlagen, klingt Alternative 3 für mich am besten. Ich denke auch wie @ gnasher729, dass Sie die falsche Sprache verwenden.

John Pfersich
quelle
Was genau halten Sie für einen Codegeruch? Bitte erläutern Sie.
Hulk
-3

Um ehrlich zu sein, möchten Sie möglicherweise zu einer anderen Sprache wechseln, in der das Zurückgeben einer Zeichenfolge keine komplexe, arbeitsintensive und fehleranfällige Operation ist.

Sie könnten C ++ oder Objective-C in Betracht ziehen, bei denen Sie 99% Ihres Codes unverändert lassen könnten.

gnasher729
quelle