Je travaille actuellement sur une bibliothèque écrite en C. De nombreuses fonctions de cette bibliothèque attendent une chaîne au fur char*
et const char*
à mesure de leurs arguments. J'ai commencé avec ces fonctions en m'attendant toujours à la longueur de la chaîne size_t
afin que la terminaison nulle ne soit pas requise. Cependant, lors de l'écriture des tests, cela a entraîné une utilisation fréquente de strlen()
, comme ceci:
const char* string = "Ugh, strlen is tedious";
libFunction(string, strlen(string));
Faire confiance à l'utilisateur pour passer des chaînes correctement terminées conduirait à un code moins sûr, mais plus concis et (à mon avis) lisible:
libFunction("I hope there's a null-terminator there!");
Alors, quelle est la pratique raisonnable ici? Rendre l'API plus compliquée à utiliser, mais forcer l'utilisateur à penser à son entrée, ou documenter l'exigence d'une chaîne terminée par un caractère nul et faire confiance à l'appelant?
CreateFile
prend unLPTCSTR lpFileName
paramètre en entrée. Aucune longueur de la chaîne n'est attendue de l'appelant. En fait, l'utilisation de chaînes terminées par NUL est tellement ancrée que la documentation ne mentionne même pas que le nom de fichier doit être terminé par NUL (mais bien sûr il doit l'être).LPSTR
genre dit que les chaînes peuvent être NUL-fin, et si non , qui sera indiqué dans la spécification associée. Donc, sauf indication contraire, ces chaînes dans Win32 devraient se terminer par NUL.StringCbCat
par exemple, seule la destination a un tampon maximum, ce qui est logique. La source est toujours une chaîne C ordinaire terminée par NUL. Vous pourriez peut-être améliorer votre réponse en clarifiant la différence entre un paramètre d' entrée et un paramètre de sortie . Les paramètres de sortie doivent toujours avoir une longueur de tampon maximale; les paramètres d'entrée sont généralement terminés par NUL (il existe des exceptions, mais rares dans mon expérience).En C, l'idiome est que les chaînes de caractères sont terminées par NUL, il est donc logique de respecter la pratique courante - il est en fait relativement peu probable que les utilisateurs de la bibliothèque aient des chaînes non terminées par NUL (car celles-ci nécessitent un travail supplémentaire pour imprimer en utilisant printf et utiliser dans un autre contexte). L'utilisation de tout autre type de chaîne n'est pas naturelle et probablement relativement rare.
De plus, dans les circonstances, vos tests me semblent un peu étranges, car pour fonctionner correctement (en utilisant strlen), vous supposez une chaîne terminée par NUL en premier lieu. Vous devez tester le cas des chaînes non terminées par NUL si vous souhaitez que votre bibliothèque fonctionne avec elles.
la source
Votre argument "sécurité" ne tient pas vraiment. Si vous ne faites pas confiance à l'utilisateur pour vous remettre une chaîne terminée par un caractère nul lorsque c'est ce que vous avez documenté (et ce qui est "la norme" pour le C ordinaire), vous ne pouvez pas vraiment faire confiance à la longueur qu'il vous donne non plus (ce qu'il obtenir probablement en utilisant
strlen
comme vous le faites s'ils ne l'ont pas à portée de main, et qui échouera si la "chaîne" n'était pas une chaîne en premier lieu).Il existe cependant des raisons valables d'exiger une longueur: si vous voulez que vos fonctions fonctionnent sur des sous-chaînes, il est peut-être beaucoup plus facile (et efficace) de passer une longueur que de demander à l'utilisateur de faire de la magie d'avant en arrière pour obtenir l'octet nul. au bon endroit (et risquez des erreurs ponctuelles en cours de route).
Être capable de gérer des encodages où les octets nuls ne sont pas des terminaisons, ou être capable de gérer des chaînes qui ont des valeurs nulles incorporées (exprès) peut être utile dans certaines circonstances (cela dépend de ce que font exactement vos fonctions).
Être capable de gérer des données non nulles (tableaux de longueur fixe) est également pratique.
En bref: cela dépend de ce que vous faites dans votre bibliothèque et du type de données que vous attendez de vos utilisateurs.
Il y a aussi peut-être un aspect de performance à cela. Si votre fonction a besoin de connaître la longueur de la chaîne à l'avance et que vous vous attendez à ce que vos utilisateurs connaissent déjà au moins ces informations, les faire passer (plutôt que de les calculer) pourrait raser quelques cycles.
Mais si votre bibliothèque attend des chaînes de texte ASCII ordinaires et que vous n'avez pas de contraintes de performance atroces et une très bonne compréhension de la façon dont vos utilisateurs interagiront avec votre bibliothèque, l'ajout d'un paramètre de longueur ne semble pas une bonne idée. Si la chaîne n'est pas correctement terminée, il est probable que le paramètre de longueur sera tout aussi faux. Je ne pense pas que vous y gagnerez beaucoup.
la source
Non. Les chaînes sont toujours terminées par définition, la longueur de la chaîne est redondante.
Les données de caractère non terminées par null ne doivent jamais être appelées une "chaîne". Le traiter (et jeter des longueurs) doit généralement être encapsulé dans une bibliothèque et ne pas faire partie de l'API. Exiger la longueur comme paramètre juste pour éviter les appels strlen () est probablement une optimisation prématurée.
Faire confiance à l'appelant d'une fonction API n'est pas dangereux ; un comportement indéfini est parfaitement correct si les conditions préalables documentées ne sont pas remplies.
Bien sûr, une API bien conçue ne devrait pas contenir d'embûches et devrait faciliter son utilisation correcte. Et cela signifie simplement qu'il devrait être aussi simple et direct que possible, en évitant les redondances et en suivant les conventions du langage.
la source
Vous devez toujours garder votre longueur. D'une part, vos utilisateurs peuvent souhaiter y contenir des valeurs NULL. Et deuxièmement, n'oubliez pas que
strlen
c'est O (N) et nécessite de toucher tout le cache de bye bye. Et troisièmement, il est plus facile de contourner les sous-ensembles - par exemple, ils pourraient donner moins que la longueur réelle.la source
strlen
dans un test de boucle.)Vous devez faire la distinction entre passer une chaîne et passer un tampon .
En C, les chaînes sont traditionnellement terminées par NUL. Il est tout à fait raisonnable de s’attendre à cela. Par conséquent, il n'est généralement pas nécessaire de contourner la longueur de la chaîne; il peut être calculé avec
strlen
si nécessaire.Lorsque vous passez un tampon , en particulier celui qui est écrit, vous devez absolument transmettre la taille du tampon. Pour un tampon de destination, cela permet à l'appelé de s'assurer qu'il ne déborde pas du tampon. Pour un tampon d'entrée, cela permet à l'appelé d'éviter de lire après la fin, surtout si le tampon d'entrée contient des données arbitraires provenant d'une source non fiable.
Il y a peut-être une certaine confusion car les chaînes et les tampons peuvent l'être
char*
et parce que de nombreuses fonctions de chaîne génèrent de nouvelles chaînes en écrivant dans les tampons de destination. Certaines personnes concluent alors que les fonctions de chaîne doivent prendre des longueurs de chaîne. Cependant, c'est une conclusion inexacte. La pratique d'inclure une taille avec un tampon (que ce tampon soit utilisé pour des chaînes, des tableaux d'entiers, des structures, etc.) est un mantra plus utile et plus général.(Dans le cas de la lecture d'une chaîne à partir d'une source non fiable (par exemple une prise réseau), il est important de fournir une longueur car l'entrée peut ne pas se terminer par NUL. Cependant , vous ne devez pas considérer l'entrée comme une chaîne. Vous devrait le traiter comme un tampon de données arbitraire qui pourrait contenir une chaîne (mais vous ne savez pas jusqu'à ce que vous le validiez réellement), donc cela suit toujours le principe selon lequel les tampons doivent avoir des tailles associées et que les chaînes n'en ont pas besoin.)
la source
Si les fonctions sont principalement utilisées avec des littéraux de chaîne, la douleur de traiter des longueurs explicites peut être minimisée en définissant certaines macros. Par exemple, étant donné une fonction API:
on pourrait définir une macro:
puis l'invoquez comme indiqué dans:
Bien qu'il soit possible de trouver des choses "créatives" pour passer cette macro qui se compilera mais ne fonctionnera pas réellement, l'utilisation de
""
chaque côté de la chaîne dans l'évaluation de "sizeof" devrait intercepter les tentatives accidentelles d'utiliser le caractère pointeurs autres que les littéraux de chaîne décomposés [en l'absence de ceux-ci""
, une tentative de passer un pointeur de caractère donnerait à tort la longueur comme la taille d'un pointeur, moins un.Une approche alternative dans C99 serait de définir un type de structure "pointeur et longueur" et de définir une macro qui convertit un littéral de chaîne en un littéral composé de ce type de structure. Par exemple:
Notez que si l'on utilise une telle approche, il faut passer ces structures par valeur plutôt que de passer autour de leurs adresses. Sinon, quelque chose comme:
peut échouer car la durée de vie des littéraux composés prendrait fin à la fin des instructions qui les entourent.
la source