Fonctions renvoyant des cordes, bon style?

11

Dans mes programmes C, j'ai souvent besoin d'un moyen de faire une représentation sous forme de chaîne de mes ADT. Même si je n'ai pas besoin d'imprimer la chaîne à l'écran de quelque manière que ce soit, il est intéressant d'avoir une telle méthode de débogage. Donc, ce genre de fonction revient souvent.

char * mytype_to_string( const mytype_t *t );

En fait, je me rends compte que j'ai (au moins) trois options ici pour gérer la mémoire pour la chaîne à retourner.

Alternative 1: Stockage de la chaîne de retour dans un tableau de caractères statiques dans la fonction. Je n'ai pas besoin de beaucoup de réflexion, sauf que la chaîne est écrasée à chaque appel. Ce qui peut être un problème à certaines occasions.

Alternative 2: allouez la chaîne sur le tas avec malloc à l'intérieur de la fonction. Vraiment bien puisque je n'aurai alors pas besoin de penser à la taille d'un tampon ou à l'écrasement. Cependant, je dois me rappeler de libérer () la chaîne une fois terminé, puis je dois également attribuer à une variable temporaire que je peux libérer. et puis l'allocation de tas est vraiment beaucoup plus lente que l'allocation de pile, donc soyez un goulot d'étranglement si cela se répète dans une boucle.

Alternative 3: passez le pointeur vers un tampon et laissez l'appelant allouer ce tampon. Comme:

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

Cela apporte plus d'efforts à l'appelant. Je remarque également que cette alternative me donne une autre option sur l'ordre des arguments. Quel argument dois-je avoir en premier et en dernier? (en fait six possibilités)

Alors, que dois-je préférer? Pourquoi? Existe-t-il une sorte de standard non écrit parmi les développeurs C?

Øystein Schønning-Johansen
la source
3
Juste une note d'observation, la plupart des systèmes d'exploitation utilisent l'option 3 - l'appelant alloue le tampon de toute façon; indique le pointeur et la capacité du tampon; l'appelé remplit le tampon et renvoie également la longueur réelle de la chaîne si le tampon est insuffisant. Exemple: sysctlbynamesous OS X et iOS
rwong

Réponses:

11

Les méthodes que j'ai vues le plus sont 2 et 3.

Le tampon fourni par l'utilisateur est en fait assez simple à utiliser:

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

Bien que la plupart des implémentations renvoient la quantité de tampon utilisée.

L'option 2 sera plus lente et est dangereuse lors de l'utilisation de bibliothèques liées dynamiquement où elles peuvent utiliser des temps d'exécution différents (et des tas différents). Vous ne pouvez donc pas libérer ce qui a été mallocé dans une autre bibliothèque. Cela nécessite alors une free_string(char*)fonction pour y faire face.

monstre à cliquet
la source
Merci! Je pense que j'aime mieux l'Alternative 3. Cependant, je veux pouvoir faire des choses comme: printf("MyType: %s\n", mytype_to_string( mt, buf, sizeof(buf));et donc je n'aime pas retourner la longueur utilisée mais plutôt le pointeur sur la chaîne. Le commentaire de bibliothèque dynamique est vraiment important.
Øystein Schønning-Johansen
Cela ne devrait-il pas être sizeof(buffer) - 1pour satisfaire le \0terminateur?
Michael-O
1
@ Michael-O non, le terme nul est inclus dans la taille du tampon, ce qui signifie que la chaîne maximale pouvant être insérée est inférieure de 1 à la taille transmise. Il s'agit du modèle snprintfutilisé par la chaîne de sécurité dans la bibliothèque standard .
monstre à cliquet
@ratchetfreak Merci pour la clarification. Ce serait bien d'étendre la réponse avec cette sagesse.
Michael-O
0

Idée de conception supplémentaire pour # 3

Lorsque cela est possible, indiquez également la taille maximale requise mytypedans le même fichier .h que mytype_to_string().

#define MYTYPE_TO_STRING_SIZE 256

L'utilisateur peut maintenant coder en conséquence.

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

Ordre

La taille des tableaux, lorsqu'elle est d'abord, permet les types VLA.

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

Pas si important avec une seule dimension, mais utile avec 2 ou plus.

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

Je me souviens que la lecture ayant la taille en premier est un idiome préféré dans le prochain C. Besoin de trouver cette référence ....

chux - Réintégrer Monica
la source
0

En complément de l'excellente réponse de @ ratchetfreak, je voudrais souligner que l'alternative # 3 suit un paradigme / modèle similaire à celui des fonctions de bibliothèque C standard.

Par exemple strncpy,.

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

Suivre le même paradigme aiderait à réduire la charge cognitive pour les nouveaux développeurs (ou même votre futur soi) lorsqu'ils ont besoin d'utiliser votre fonction.

La seule différence avec ce que vous avez dans votre article serait que l' destinationargument dans les bibliothèques C a tendance à être répertorié en premier dans la liste des arguments. Donc:

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

Outre le fait que ce que vous proposez de faire est une mauvaise odeur de code, l'alternative 3 me semble la meilleure. Je pense aussi comme @ gnasher729 que vous utilisez la mauvaise langue.

John Pfersich
la source
Que considérez-vous exactement comme une odeur de code? Veuillez développer.
Hulk
-3

Pour être honnête, vous souhaiterez peut-être passer à une autre langue où le retour d'une chaîne n'est pas une opération complexe, exigeante en travail et sujette aux erreurs.

Vous pouvez envisager C ++ ou Objective-C où vous pouvez laisser 99% de votre code inchangé.

gnasher729
la source