Quelles sont les failles qui vous rendent fou dans les API C (y compris les bibliothèques standard, les bibliothèques tierces et les en-têtes à l'intérieur d'un projet)? L'objectif est d'identifier les pièges de conception d'API en C, afin que les personnes qui écrivent de nouvelles bibliothèques C puissent apprendre des erreurs du passé.
Expliquez pourquoi la faille est mauvaise (de préférence avec un exemple) et essayez de suggérer une amélioration. Bien que votre solution puisse ne pas être pratique dans la vie réelle (il est trop tard pour la réparer strncpy
), elle devrait donner un coup de pouce aux futurs rédacteurs de bibliothèques.
Bien que l'objectif de cette question soit les API C, les problèmes qui affectent votre capacité à les utiliser dans d'autres langues sont les bienvenus.
Veuillez donner un défaut par réponse, afin que la démocratie puisse trier les réponses.
la source
malloc
chaîne pourrait le résoudre. Je pense que donner un bon exemple avec la première réponse pourrait vraiment aider cette question à prospérer. Merci!Réponses:
Fonctions avec des valeurs de retour incohérentes ou illogiques. Deux bons exemples:
1) Certaines fonctions Windows qui renvoient un HANDLE utilisent NULL / 0 pour une erreur (CreateThread), certaines utilisent INVALID_HANDLE_VALUE / -1 pour une erreur (CreateFile).
2) La fonction POSIX 'time' renvoie '(time_t) -1' en cas d'erreur, ce qui est vraiment illogique car 'time_t' peut être soit un type signé soit non signé.
la source
int time(time_t *out);
etBOOL CreateFile(LPCTSTR lpFileName, ..., HANDLE *out);
.Fonctions ou paramètres avec des noms non descriptifs ou affirmativement déroutants. Par exemple:
1) CreateFile, dans l'API Windows, ne crée pas réellement de fichier, il crée un descripteur de fichier. Il peut créer un fichier, tout comme 'open' peut, si on le lui demande via un paramètre. Ce paramètre a des valeurs appelées «CREATE_ALWAYS» et «CREATE_NEW» dont les noms ne font même pas allusion à leur sémantique. ('CREATE_ALWAYS' signifie-t-il qu'il échoue si le fichier existe? Ou crée-t-il un nouveau fichier par-dessus? Est-ce que 'CREATE_NEW' signifie qu'il crée toujours un nouveau fichier et échoue si le fichier existe déjà? Ou crée-t-il un nouveau fichier par-dessus?)
2) pthread_cond_wait dans l'API POSIX pthreads, qui malgré son nom, est une attente inconditionnelle .
la source
pthread_cond_wait
ne signifie pas "conditionnellement attendre". Cela fait référence au fait que vous attendez une variable de condition .Types opaques transmis via l'interface en tant que poignées de type supprimé. Le problème est, bien sûr, que le compilateur ne peut pas vérifier le code utilisateur pour les types d'arguments corrects.
Cela se présente sous diverses formes et saveurs, y compris, mais sans s'y limiter:
void*
abuser deutilisation
int
comme ressource (exemple: la bibliothèque CDI)arguments typés en chaîne
Les types les plus distincts (= ne peuvent pas être utilisés de manière totalement interchangeable) sont mappés au même type supprimé, le pire. Bien sûr, le remède consiste simplement à fournir des pointeurs opaques de type sécurisé le long de (exemple C):
la source
Fonctions avec des conventions de retour de chaîne incohérentes et souvent encombrantes.
Par exemple, getcwd demande un tampon fourni par l'utilisateur et sa taille. Cela signifie qu'une application doit soit fixer une limite arbitraire sur la longueur actuelle du répertoire, soit faire quelque chose comme ça ( depuis CCAN ):
Ma solution: renvoyer une
malloc
chaîne ed. C'est simple, robuste et non moins efficace. À l'exception des plates-formes intégrées et des anciens systèmes,malloc
est en fait assez rapide.la source
snprintf(buf, 32, "%d", n)
, où la longueur de sortie est prévisible (certainement pas plus de 30, à moins qu'elle neint
soit vraiment énorme sur votre système). En effet, malloc n'est pas disponible sur de nombreux systèmes, mais pour les environnements de bureau et de serveur, il l'est, et il fonctionne très bien.Fonctions qui prennent / retournent des types de données composés par valeur, ou qui utilisent des rappels.
Pire encore si ledit type est une union ou contient des champs binaires.
Du point de vue d'un appelant C, ceux-ci sont en fait OK, mais je n'écris pas en C ou C ++ à moins d'y être obligé, donc j'appelle généralement via un FFI. La plupart des FFI ne prennent pas en charge les unions ou les champs de bits, et certains (tels que Haskell et MLton) ne peuvent pas prendre en charge les structures passées par valeur. Pour ceux qui peuvent gérer les structures par valeur, au moins Common Lisp et LuaJIT sont forcés sur des chemins lents - L'interface Common Foreign Function de Lisp doit effectuer un appel lent via libffi, et LuaJIT refuse de compiler JIT le chemin de code contenant l'appel. Les fonctions qui peuvent rappeler dans les hôtes déclenchent également des chemins lents sur LuaJIT, Java et Haskell, LuaJIT ne pouvant pas compiler un tel appel.
la source