web-dev-qa-db-fra.com

Les chaînes C sont-elles toujours terminées par null ou cela dépend-il de la plate-forme?

En ce moment, je travaille avec des systèmes embarqués et je trouve des moyens d'implémenter des chaînes sur un microprocesseur sans système d'exploitation. Jusqu'à présent, ce que je fais est simplement d'utiliser l'idée d'avoir des pointeurs de caractères terminés par NULL et de les traiter comme des chaînes où NULL signifie la fin. Je sais que c'est assez courant, mais pouvez-vous toujours compter sur cela pour être le cas?

La raison pour laquelle je demande, c'est que je pensais peut-être à un moment donné utiliser un système d'exploitation en temps réel, et j'aimerais réutiliser autant que possible mon code actuel. Donc, pour les différents choix qui existent, puis-je m'attendre à peu près à ce que les cordes fonctionnent de la même manière?

Permettez-moi d'être plus précis cependant pour mon cas. J'implémente un système qui prend et traite les commandes sur un port série. Puis-je conserver mon code de traitement des commandes de la même manière, puis m'attendre à ce que les objets chaîne créés sur le RTOS (qui contient les commandes) soient tous terminés NULL? Ou, serait-il différent en fonction de l'OS?

Mise à jour

Après avoir été invité à jeter un coup d'œil à cette question j'ai déterminé qu'elle ne répond pas exactement à ce que je demande. La question elle-même demande si une longueur de chaîne doit toujours être passée, ce qui est entièrement différent de ce que je demande, et bien que certaines des réponses contenaient des informations utiles, elles ne sont pas exactement ce que je recherche. Les réponses semblent donner des raisons pour lesquelles ou pourquoi pas pour terminer une chaîne avec un caractère nul. La différence avec ce que je demande, c'est si je peux plus ou moins m'attendre à ce que les chaînes innées de différentes plates-formes terminent leurs propres chaînes avec null, sans avoir à sortir et essayer chaque plate-forme là-bas si cela a du sens.

13
Snoop

Les choses qui sont appelées "chaînes C" seront terminées par null sur n'importe quelle plate-forme. C'est ainsi que les fonctions de bibliothèque C standard déterminent la fin d'une chaîne.

Dans le langage C, rien ne vous empêche d'avoir un tableau de caractères qui ne se termine pas par un null. Cependant, vous devrez utiliser une autre méthode pour éviter d'exécuter la fin d'une chaîne.

42
Simon B

La détermination du caractère de terminaison dépend du compilateur pour les littéraux et de l'implémentation de la bibliothèque standard pour les chaînes en général. Il n'est pas déterminé par le système d'exploitation.

La convention de la terminaison NUL remonte au C pré-standard, et dans 30+ ans, je ne peux pas dire que j'ai rencontré un environnement qui fait autre chose. Ce comportement a été codifié en C89 et continue de faire partie de la norme de langage C (lien vers un brouillon de C99):

  • La section 6.4.5 définit l'étape des chaînes terminées par NUL en exigeant qu'un NUL soit ajouté aux littéraux de chaîne.
  • La section 7.1.1 apporte cela aux fonctions de la bibliothèque standard en définissant une chaîne comme "une séquence contiguë de caractères se terminant par et incluant le premier caractère nul . "

Il n'y a aucune raison pour que quelqu'un ne puisse pas écrire des fonctions qui gèrent des chaînes terminées par un autre caractère, mais il n'y a également aucune raison de contourner la norme établie dans la plupart des cas, sauf si votre objectif est de donner aux programmeurs des ajustements. :-)

22
Blrfl

Je travaille avec des systèmes embarqués ... sans système d'exploitation ... J'utilise ... l'idée d'avoir des pointeurs de caractères terminés par NULL et de les traiter comme des chaînes où le NULL signifie la fin. Je sais que c'est assez courant, mais pouvez-vous toujours compter sur cela pour être le cas?

Il n'y a pas de type de données de chaîne dans le langage C, mais il y a littéraux de chaîne.

Si vous mettez un littéral de chaîne dans votre programme, il se terminera généralement par NUL (mais voyez le cas spécial, discuté dans les commentaires ci-dessous.) C'est-à-dire, si vous mettez "foobar" dans un endroit où un const char * une valeur est attendue, le compilateur émettra foobar⊘ au segment/section const/code de votre programme, et la valeur de l'expression sera un pointeur vers l'adresse où elle a stocké le caractère f. (Remarque: j'utilise pour signifier l'octet NUL.)

Le seul autre sens dans lequel le langage C a des chaînes est qu'il a des routines de bibliothèque standard qui fonctionnent sur des séquences de caractères terminées par NUL. Ces routines de bibliothèque n'existeront pas dans un environnement sans système d'exploitation sauf si vous les portez vous-même.

Ce n'est que du code --- pas différent du code que vous écrivez vous-même. Si vous ne les cassez pas lorsque vous les portez, ils feront ce qu'ils font toujours (par exemple, arrêtez-vous sur un NUL.)

3
Solomon Slow

Comme d'autres l'ont mentionné, la terminaison nulle des chaînes est une convention de la bibliothèque standard C. Vous pouvez gérer les chaînes comme vous le souhaitez si vous n'utilisez pas la bibliothèque standard.

Cela est vrai de tout système d'exploitation avec un compilateur "C", et vous pouvez également écrire des programmes "C" qui ne sont pas exécutés sous un véritable système d'exploitation, comme vous le mentionnez dans votre question. Un exemple serait le contrôleur d'une imprimante à jet d'encre que j'ai conçue une fois. Dans les systèmes embarqués, la surcharge de mémoire d'un système d'exploitation peut ne pas être nécessaire.

Dans des situations où la mémoire est limitée, je regarderais par exemple les caractéristiques de mon compilateur par rapport au jeu d'instructions du processeur. Dans une application où les chaînes sont beaucoup traitées, il peut être souhaitable d'utiliser des descripteurs tels que la longueur des chaînes. Je pense à un cas où le CPU est particulièrement efficace pour travailler avec des décalages courts et/ou des décalages relatifs avec des registres d'adresses.

Alors, qu'est-ce qui est le plus important dans votre application: la taille et l'efficacité du code, ou la compatibilité avec un système d'exploitation ou une bibliothèque? Une autre considération pourrait être la maintenabilité. Plus vous vous éloignez de la convention, plus il sera difficile à quelqu'un de maintenir.

2
Hugh Buntu

D'autres ont abordé le problème qu'en C, les chaînes sont en grande partie ce que vous en faites. Mais il semble y avoir une certaine confusion dans votre question w.r.t. le terminateur lui-même, et d'un point de vue, cela pourrait être ce qui inquiète quelqu'un dans votre position.

Les chaînes C sont terminées par null. Autrement dit, ils se terminent par le caractère nul, NUL. Ils ne sont pas terminés par le pointeur nul NULL, qui est un type de valeur complètement différent avec un objectif complètement différent.

NUL est garanti d'avoir la valeur entière zéro. Dans la chaîne, il aura également la taille du type de caractère sous-jacent, qui sera généralement 1.

NULL n'est pas du tout garanti d'avoir un type entier. NULL est destiné à être utilisé dans un contexte de pointeur, et devrait généralement avoir un type de pointeur, qui ne devrait pas être converti en caractère ou entier si votre compilateur est bon. Alors que la définition de NULL implique le glyphe 0, il n'est pas garanti d'avoir réellement cette valeur [1], et à moins que votre compilateur implémente la constante comme un caractère #define (beaucoup ne le font pas, car NULLvraiment ne devrait pas être significatif dans un contexte sans pointeur), le code développé n'est donc pas garanti d'impliquer réellement une valeur nulle ( même si cela implique un glyphe nul).

Si NULL est tapé, il est également peu probable qu'il ait une taille de 1 (ou une autre taille de caractère). Cela peut éventuellement entraîner des problèmes supplémentaires, bien que les constantes de caractères réelles n'aient pas de taille de caractère pour la plupart.

Maintenant, la plupart des gens verront cela et penseront, "pointeur nul comme autre chose que des bits zéro? Quelle absurdité" - mais des hypothèses comme celles-ci ne sont sûres que sur des plates-formes courantes comme x86. Puisque vous avez explicitement mentionné votre intérêt à cibler d'autres plates-formes, vous devez prendre en compte ce problème, car vous avez explicitement séparé votre code des hypothèses sur la nature de la relation entre les pointeurs et les entiers.

Par conséquent, bien que les chaînes C soient terminées par null, elles ne sont pas terminées par NULL, mais par NUL (généralement écrit '\0'). Le code qui utilise explicitement NULL comme terminateur de chaîne fonctionnera sur les plates-formes avec une structure d'adresse simple, et compilera même avec de nombreux compilateurs, mais ce n'est absolument pas correct C.


[1] la valeur réelle du pointeur nul est insérée par le compilateur lorsqu'il lit un 0token dans un contexte où il serait converti en type de pointeur. Ce n'est pas une conversion à partir de l'entier valeur 0, et il n'est pas garanti de tenir si autre chose que le jeton 0 lui-même est utilisé, comme une valeur dynamique d'une variable; la conversion n'est pas non plus réversible, et un pointeur nul n'a pas à donner la valeur 0 lorsqu'il est converti en entier.

1
Leushenko

J'ai utilisé une chaîne en C, cela signifie que les caractères avec une terminaison nulle s'appellent des chaînes.

Il n'aura aucun problème lorsque vous utilisez dans baremetal ou dans tout système d'exploitation tel que Windows, Linux, RTOS: (FreeRTO, OSE).

Dans le monde intégré, la terminaison nulle permet en réalité de mieux marquer le caractère sous forme de chaîne.

J'ai utilisé des chaînes en C comme ça dans de nombreux systèmes critiques pour la sécurité.

Vous vous demandez peut-être quelle est la chaîne en C?

Les chaînes de style C, qui sont des tableaux, il existe également des littéraux de chaîne, tels que "this". En réalité, ces deux types de chaînes ne sont que des ensembles de caractères assis côte à côte en mémoire.

Chaque fois que vous écrivez une chaîne, entre guillemets, C crée automatiquement un tableau de caractères pour nous, contenant cette chaîne, terminée par le caractère\0.

Par exemple, vous pouvez déclarer et définir un tableau de caractères et l'initialiser avec une constante de chaîne:

char string[] = "Hello cruel world!";

Réponse simple: vous n'avez pas vraiment besoin de vous soucier de l'utilisation de caractères avec une terminaison nulle, ce travail indépendamment de toute plate-forme.

1
danglingpointer

Comme d'autres l'ont dit, la terminaison nulle est à peu près universelle pour le standard C. Mais (comme d'autres l'ont également souligné) pas à 100%. Pour (un autre) exemple, le système d'exploitation VMS utilise généralement ce qu'il appelle des "descripteurs de chaîne" http://h41379.www4.hpe.com/commercial/c/docs/5492p012.html accessible en C by # include <descrip.h>

Les éléments au niveau de l'application peuvent utiliser une terminaison nulle ou non, mais le développeur le juge utile. Mais les trucs VMS de bas niveau nécessitent absolument des descripteurs, qui n'utilisent pas du tout de terminaison nulle (voir le lien ci-dessus pour plus de détails). C'est en grande partie pour que tous les langages (C, Assembly, etc.) qui utilisent directement les internes VMS puissent avoir une interface commune avec eux.

Donc, si vous prévoyez tout type de situation similaire, vous voudrez peut-être être un peu plus prudent que ce que la "terminaison nulle universelle" pourrait suggérer est nécessaire. Je serais plus prudent si je faisais ce que vous faites, mais pour mes trucs au niveau de l'application, il est sûr de supposer une terminaison nulle. Je ne vous proposerais tout simplement pas le même niveau de sécurité. Votre code pourrait bien avoir à s'interfacer avec Assembly et/ou un autre code de langue à un moment donné, qui ne sera pas toujours conforme à la norme C des chaînes terminées par null.

1
John Forkosh

D'après mon expérience des systèmes embarqués, critiques pour la sécurité et en temps réel, il n'est pas rare d'utiliser à la fois les conventions de chaîne C et Pascal, c'est-à-dire de fournir la longueur des chaînes comme premier caractère (ce qui limite la longueur à 255) et de mettre fin à la chaîne avec au moins un 0x00, (NUL), ce qui réduit la taille utilisable à 254.

Une raison à cela est de savoir combien de données vous attendez après la réception du premier octet et une autre est que, dans de tels systèmes, les tailles de mémoire tampon dynamiques sont évitées dans la mesure du possible - l'allocation d'une taille de mémoire tampon 256 fixe est plus rapide et plus sûre, (non besoin de vérifier si malloc a échoué). Un autre est que les autres systèmes avec lesquels vous communiquez peuvent ne pas être écrits en ANSI-C.

Dans tout travail intégré, il est important d'établir et de maintenir un document de contrôle d'interface (IDC), qui définit toutes vos structures de communication, y compris les formats de chaîne, l'endianité, les tailles entières, etc., dès que possible, ( idéalement avant de commencer ), et ce devrait être votre livre sacré et toutes les équipes lors de l'écriture du système - si quelqu'un souhaite introduire une nouvelle structure ou la formater doit y être documenté premier et toutes les personnes susceptibles d'être impactées doivent être informées, éventuellement avec une option de veto sur le changement.

0
Steve Barnes