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

13

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 chaînes fonctionnent de la même manière?

Permettez-moi cependant d'être plus précis 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-ce différent en fonction du système d'exploitation?

Mise à jour

Après avoir été invité à jeter un œ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 transmise, 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 ne pas 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.

Fouiner
la source
3
Je n'ai pas utilisé C depuis longtemps, mais je ne peux pas penser à un moment où j'ai rencontré une implémentation qui n'utilisait pas de chaînes terminées par NULL. Cela fait partie de la norme C, si je me souviens bien (comme je l'ai dit, ça fait un moment ...)
MetalMikester
1
Je ne suis pas un spécialiste de C, mais pour autant que je sache, toutes les chaînes en C sont des tableaux de char, à terminaison nulle. Vous pouvez cependant créer votre propre type de chaîne, mais vous devez implémenter vous-même toutes les fonctions de manipulation de chaîne.
Machado
1
@MetalMikester Vous pensez que ces informations se trouvent dans la spécification C standard?
Snoop
3
@Snoopy Très probablement, oui. Mais vraiment, quand on parle de chaînes en C, ce ne sont qu'un tableau de caractères qui se terminent par NULL et c'est tout, à moins que vous n'utilisiez une sorte de bibliothèque de chaînes non standard, mais ce n'est pas ce dont nous parlons ici de toute façon. Je doute que vous trouverez une plate-forme qui ne respecte pas cela, en particulier avec l'un des points forts de C étant la portabilité.
MetalMikester

Réponses:

42

Les choses 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.

Simon B
la source
4
juste pour ajouter; généralement, vous avez un entier quelque part pour garder une trace de la longueur de la chaîne, puis vous vous retrouvez avec une structure de données personnalisée pour le faire correctement, quelque chose comme la classe QString dans Qt
Rudolf Olah
8
Exemple: je travaille avec un programme C qui utilise au moins cinq formats de chaîne différents: des chartableaux à terminaison nulle , des chartableaux dont la longueur est codée dans le premier octet (communément appelés "chaînes Pascal"), des wchar_tversions basées sur les deux ci-dessus et des chartableaux qui combinent les deux méthodes: longueur codée dans le premier octet et un caractère nul terminant la chaîne.
Mark
4
@Mark Interfaçage avec de nombreux composants / applications tiers ou un désordre de code hérité?
Dan est en train de jouer par Firelight
2
@DanNeely, tout ce qui précède. Chaînes Pascal pour interfacer avec MacOS classique, chaînes C pour usage interne et Windows, chaînes larges pour ajouter la prise en charge Unicode et chaînes bâtard parce que quelqu'un a essayé d'être intelligent et de créer une chaîne qui pourrait s'interfacer avec MacOS et Windows en même temps.
Mark
1
@Mark ... et bien sûr, personne n'est prêt à dépenser de l'argent pour rembourser la dette technique parce que le MacOS classique est mort depuis longtemps, et les cordes bâtardes sont un double clusterfrak à chaque fois qu'elles doivent être touchées. Mes condoléances.
Dan est en train de jouer par Firelight
22

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 NULrésiliation remonte au C pré-standard, et dans plus de 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 du langage C (lien vers un projet de C99):

  • La section 6.4.5 définit l'étape NULdes chaînes terminées en exigeant que a NULsoit 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. :-)

Blrfl
la source
2
L'une des raisons serait d'éviter d'avoir à rechercher la fin de la même chaîne encore et encore.
Paŭlo Ebermann
@ PaŭloEbermann Droite. Au détriment d'avoir à passer deux valeurs au lieu d'une. Ce qui est un peu gênant si vous passez juste une chaîne littérale comme dans printf("string: \"%s\"\n", "my cool string"). La seule façon de passer quatre paramètres dans ce cas (autre qu'un certain octet de terminaison) serait de définir une chaîne comme quelque chose comme std::stringen C ++, qui a ses propres problèmes et limites.
cmaster - réintègre monica
1
La section 6.4.5 n'exige pas qu'un littéral de chaîne se termine par un caractère nul. Il note explicitement " Un littéral de chaîne de caractères n'a pas besoin d'être une chaîne (voir 7.1.1), car un caractère nul peut y être incorporé par une séquence d'échappement \ 0. "
bzeaman
1
@bzeaman La note de bas de page indique que vous pouvez construire un littéral de chaîne qui ne correspond pas à la définition 7.1.1 d'une chaîne, mais la phrase qui y fait référence indique des compilateurs conformes - NULterminez-les quoi qu'il arrive: "Dans la phase de traduction 7, un octet ou un code de la valeur zéro est ajoutée à chaque séquence de caractères multi-octets qui résulte d'un ou plusieurs littéraux de chaîne. " Les fonctions de bibliothèque utilisant la définition de 7.1.1 s'arrêtent dès NULqu'elles trouvent et ne savent pas ou ne se soucient pas que des caractères supplémentaires existent au-delà.
Blrfl
Je me suis trompé. J'ai recherché divers termes comme «null» mais j'ai manqué 6.4.5.5 en mentionnant la «valeur zéro».
bzeaman
3

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 existe des 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.) Autrement dit, si vous mettez "foobar"à un endroit où une const char *valeur est attendue, le compilateur émettra foobar⊘vers le segment / section const / code de votre programme, et la valeur de l'expression sera un pointeur vers l'adresse où il a stocké le fcaractère. (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.)

Solomon Slow
la source
2
Re: "Si vous mettez un littéral de chaîne dans votre programme, il sera toujours terminé par NUL": en êtes-vous sûr? Je suis à peu près sûr que (par exemple) char foo[4] = "abcd";est un moyen valide de créer un tableau de quatre caractères non terminé par null.
ruakh
2
@ruakh, Oups! c'est un cas que je n'ai pas considéré. Je pensais à un littéral de chaîne qui apparaît à un endroit où une char const * expression est attendue. J'ai oublié que les initialiseurs C peuvent parfois obéir à des règles différentes.
Solomon Slow
@ruakh Le littéral de chaîne se termine par NUL. Le tableau ne l'est pas.
jamesdlin
2
@ruakh vous en avez un char[4]. Ce n'est pas une chaîne, mais elle a été initialisée à partir d'une
Caleth
2
@Caleth, "initialisé à partir de un" n'est pas quelque chose qui doit se produire au moment de l'exécution. Si nous ajoutons le mot-clé staticà l'exemple de Ruakh, alors le compilateur peut émettre un "abcd" non terminé par NUL vers un segment de données initialisé afin que la variable soit initialisée par le chargeur de programme. Donc, Ruakh avait raison: il y a au moins un cas où l'apparition d'un littéral de chaîne dans un programme ne nécessite pas que le compilateur émette une chaîne terminée par NUL. (ps, j'ai en fait compilé l'exemple avec gcc 5.4.0, et le compilateur n'a pas émis le NUL.)
Solomon Slow
2

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: taille et efficacité du code, ou 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.

Hugh Buntu
la source
1

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 qu'il y ait une certaine confusion dans votre question par rapport au terminateur lui-même, et d'un point de vue, cela pourrait inquiéter quelqu'un dans votre position.

Les chaînes C sont terminées par null. Autrement dit, ils sont terminés 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.

NULest 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.

NULLn'est pas du tout garanti d'avoir un type entier. NULLest 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. Bien que la définition de NULLimplique 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 NULL vraiment ne devrait pas avoir de sens dans un non contexte du pointeur), le code développé n'est donc pas garanti d'impliquer réellement une valeur zéro (même s'il implique de manière confuse un zéro glyphe).

Si NULLest 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. Étant donné que vous avez explicitement mentionné un intérêt pour le ciblage 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 NULLcomme terminateur de chaîne fonctionnera sur les plates-formes avec une structure d'adresse simple et sera même compilé 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 0 jeton dans un contexte où il serait converti en un type de pointeur. Il ne s'agit pas d'une conversion à partir de la valeur entière 0 et il n'est pas garanti de la conserver si autre chose que le jeton 0lui-même est utilisé, comme une valeur dynamique à partir 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.

Leushenko
la source
Bon point. J'ai soumis une modification pour clarifier ce point.
Monty Harder
" NULest garanti d'avoir la valeur entière zéro." -> C ne définit pas NUL. Au lieu de cela, C définit que les chaînes ont un dernier caractère nul , un octet avec tous les bits mis à 0.
chux - Rétablir Monica le
1

J'utilise 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 aide en fait plus à 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 à vous soucier de l'utilisation de caractères avec une terminaison nulle, ce travail indépendamment de toute plate-forme.

danglingpointer
la source
Merci, je ne savais pas que lorsqu'il est déclaré avec des guillemets doubles, un NULest automatiquement ajouté.
Snoop
1

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 par #include <descrip.h >

Les éléments au niveau de l'application peuvent utiliser une terminaison nulle ou non, mais le développeur le juge approprié. 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, assemblage, 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 l'assembly et / ou un autre code de langue à un moment ultérieur, qui ne sera peut-être pas toujours conforme à la norme C des chaînes à terminaison nulle.

John Forkosh
la source
Aujourd'hui, la terminaison 0 est en fait assez inhabituelle. C ++ std :: string ne le fait pas, Java String ne le fait pas, Objective-C NSString ne le fait pas, Swift String ne le fait pas - par conséquent, chaque bibliothèque de langues prend en charge les chaînes avec des codes NUL à l' intérieur de la chaîne (ce qui est impossible avec C cordes pour des raisons évidentes).
gnasher729
@ gnasher729 J'ai changé "... à peu près universel" en "à peu près universel pour le standard C", ce qui, je l'espère, supprime toute ambiguïté et reste correct aujourd'hui (et c'est ce que je voulais dire, selon le sujet et la question de l'OP).
John Forkosh
0

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 dynamique 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 en cas d' mallocéchec). 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 cela devrait être votre livre sacré, ainsi que toutes les équipes, lors de l'écriture du système - si quelqu'un souhaite introduire une nouvelle structure ou un nouveau format, il doit d' abord y être documenté et toutes les personnes susceptibles d'être impactées doivent être informées, éventuellement avec une option de veto sur le changement .

Steve Barnes
la source