10
votes

Cordes en C: pièges et techniques

Je vais coacher une équipe ACM le mois prochain (aller à la figure), et le temps est venu de parler de chaînes de C. Outre une discussion sur la norme Lib, Strcpy , STRCMP , etc., je voudrais leur donner des indices (quelque chose comme STR [0] est équivalent à * STR et des choses comme ça).

Connaissez-vous de toutes les listes (comme des feuilles de triche) ou votre propre expérience dans la matière?

Je suis déjà au courant des livres pour la compétition ACM (qui sont bons, voir en particulier Ce ), mais je suis après des tours du commerce.

merci.

Modifier : Merci beaucoup tout le monde. J'accepterai la réponse la plus votée et j'ai dûment avancé les autres que je pense être pertinents. Je m'attends à faire un résumé ici (comme je l'ai fait ici , dès que possible). J'ai assez de matériau maintenant et je suis certain que cela a été considérablement amélioré la session sur les cordes. Encore une fois, merci.


9 commentaires

Amende. Le titre a été changé.


* STR n'est pas équivalent à STR [0]. Alors, commencez avec ça.


@Hooked: Comment pas? A [i] est équivalent à * (A + i) , Signification A [0] est équivalent à * (A + 0) , qui est à son tour équivalent à * A .


A [0] renvoie une référence de direction. * STR DÉRÉERFERENCES UN POINTAIRE (C'est pourquoi cela s'appelle l'indirection). Deux choses différentes.


Mes compétences Google ne trouvent aucune utilisation de la phrase «référence de la direction» par rapport à C. L'équivalence des tableaux et des pointeurs en C que j'ai illustré précédemment est assez connu - c'est même sur la page Wikipedia - donc je ne peux vraiment pas déterminez ce que vous essayez de dire.


@Accroché. Votre commentaire est faux factuellement. Cela vous dérangerait-il de la supprimer, alors cela ne confond pas d'autres personnes?


Err, référence directe. Qui est factuellement correct. A [0] renvoie toujours une référence, c'est pourquoi cela peut être un lvalue. Je comprends que les pointeurs et les tableaux sont étroitement liés (en particulier pour les CStrings), mais * STR renvoie une référence à la notice, et un [0] renvoie toujours une référence au premier élément. Qui est factuellement correct.


Est le premier élément de A jamais différent de l'élément à quel a pointe-t-il? Sinon, on dirait que vous venez de dire qu'ils sont les mêmes.


@Hooked: A [0] retourne toujours un lvalue (je ne sais pas tout à fait ce que vous entendez par référence ici) où A est pointé. * A retourne toujours un lvalu sur lequel A est pointé. Lisez l'excellent commentaire de Chuck montrant pourquoi ils sont la même chose.


16 Réponses :


3
votes

Les fonctions suivantes peuvent être utilisées pour implémenter une non-mutation strutok : xxx

Le premier trouve le premier caractère dans l'ensemble des délimiteurs que vous passez . Le second découvre le premier caractère pas dans l'ensemble de délimiteurs que vous passez.

i préférez-les à STRPBRK car ils renvoient la longueur de la chaîne s'ils ne peuvent pas correspondre.


0 commentaires

26
votes

Il est évident mais je pense qu'il est important de savoir que les chaînes sont rien b> plus qu'un tableau d'octets, délimité par un octet zéro. C Strings ne sont pas tous les conviviaux que vous le savez probablement.

  • écrire un octet zéro quelque part dans la ficelle le tronquera. Li>
  • sortir des limites se termine généralement mal. Li>
  • jamais, jamais utiliser Strcpy, STRCMP, STRCAT, etc., utilisez plutôt leurs variantes sûres: STRNCMP, STRNCAT, STRNDUP, ... LI>
  • Évitez Strncpy. Strncpy ne sera pas toujours zéro zéro délimiter votre chaîne! Si la chaîne source ne correspond pas dans le tampon de destination, il tronque la chaîne mais elle n'écrira pas d'octet nul à la fin du tampon. De plus, même si le tampon source est beaucoup plus petit que la destination, Strncpy écrasera toujours le tampon entier avec des zéros. Personnellement, j'utilise Strlcpy. Li>
  • N'utilisez pas PrintF (String), utilisez plutôt Printf ("% s", chaîne). Essayez de penser aux conséquences si l'utilisateur met un% d dans la chaîne. Li>
  • Vous ne pouvez pas comparer les chaînes avec xxx pré> Vous devez comparer chaque personnage de la chaîne. Utilisez STRCMP ou meilleur STRNCMP.
    if( strncmp( s1, s2, BUFFER_SIZE ) == 0 )
             doStuff(s1);


5 commentaires

Si vous utilisez vraiment Printf et non une macro enrçonneuse qui fait des choses supplémentaires, les puts / fput sont les fonctions que vous recherchez.


Est strlcpy () standard c? Il est probablement important de savoir que pour une compétition. Sinon, soyez prêt à l'écrire. De plus, Strcpy, etc. est en sécurité si vous pouvez prouver que la destination est suffisamment longue.


Je suis personnellement friand d'utiliser Strncpy, suivi d'écrire une nul à la fin de la matrice de destination. De cette façon, je sais que cela n'a pas été écrasé, et je sais que cela s'est arrêté. Puisque Strlcpy n'est pas (à ma connaissance) encore une norme, je n'aime pas compter sur elle quand je rebondis entre des environnements.


@David Thorley: Strlcpy n'est en effet pas standard et ce drapper idiot refuse de le mettre dans GLIBC. Mais il s'avère vraiment génial, car le Strlcpy que j'ai écrit est plus rapide que Strcpy. Je n'aime pas Strncpy car il écrase toute la matrice, au lieu de quelle taille je donne.


Notez que vous ne pouvez pas utiliser en toute sécurité strncat () sauf si vous pouvez utiliser en toute sécurité memmove () ou memcpy () . En particulier, STRNCAT (cible, source, taille de taille (cible) est incorrect à moins que vous sachiez que * cible == '\ 0' . En utilisant strncat () est généralement une erreur.



3
votes

str [0] est équivalent à 0 [str] , ou plus généralement str [i] est i [str ] et i [str] est * (str + i) . .

nb

Ceci n'est pas spécifique aux chaînes, mais cela fonctionne également pour les tableaux C


3 commentaires

Je ne trouve pas cela incroyablement important, cependant.


Des choses telles que 3 ["Hello"] Equivalent to "Bonjour" [3] , tandis que c'est vrai, ne sont vraiment pas originaires de banlieue que personne n'a jamais utilisé.


C'est tout parce que l'addition est commutative. x [y] est * (x + y) et y [x] est * (y + x)



3
votes

Le str n * Variantes dans stdlib Ne pas nécessairement NULL Terminez la chaîne de destination < / fort>.

Par exemple: de la documentation de MSDN sur Strncpy :

La fonction Strncpy copie la Nombre initial Caractères de Strsource à la stradest et retourne la Strdest. si le nombre est inférieur ou égal à la Longueur de Strsource, un caractère nul n'est pas ajouté automatiquement à la chaîne copiée. si le nombre est plus grand que la longueur de strpsource, le La chaîne de destination est rembourrée avec NULL Caractères jusqu'à la longueur du nombre.


2 commentaires

En fait, ce n'est pas la famille STRN * complète, seulement Strncpy. Strncat a également eu ses propres problèmes. Néanmoins, écrire le NULL ne ferait pas nécessairement rendre votre programme plus sûr. Et si vous souhaitez transférer le contenu des données de fichier / etc / passwd-archives / publics, mais vos données sont tronquées par Strncpy to / etc / passwd?


Oui, le problème général de l'utilisation de chaînes en toute sécurité dans un environnement de mémoire dynamique non géré est lui-même une thèse de maîtrise en soi. En supposant que vous voulez toujours le faire :)



2
votes

strtok n'est pas Safe Safe , car il utilise un tampon privé mutable mutable pour stocker des données entre les appels; Vous ne pouvez pas interlaisser ou annexe strutok également.

Une alternative plus utile est strtok_r , l'utiliser quand vous pouvez . .


2 commentaires

strtok est une fonction de l'enfer. jetonner une chaîne comme celle-ci "asdf" , "FDSA" avec, comme délimiteur, vous obtient 2 résultats au lieu de 5


strtok_r () peut ne pas être disponible dans le concours. Cependant, évitez Strtok () si vous le pouvez.



0
votes

Vous pouvez mentionner l'adressage indexé.

Une adresse d'éléments est l'adresse de base + Index * Taille de l'élément


5 commentaires

Vous devriez clarifier: dans les tableaux et les pointeurs C, * Tailleof (élément) est effectué pour vous par le compilateur, et l'ensemble généré reflétera le facteur Taillef (élément) . Mais qu'est-ce que cela a à voir avec des chaînes? Tailleof (Char) == 1


Juste parce que le compilateur le fait pour vous et la taille d'un caractère ne signifie pas que la mise en œuvre n'est pas importante.


Tailleof (Char) ne "i> arrive doit être 1 - il est spécifié dans la norme.


Vous avez raison, personne ne devrait jamais connaître ces informations car les caractères sont un octet.


Je ne dis pas que cela n'a pas d'importance, je dis que cela n'a rien à voir avec des cordes.



0
votes

Une erreur commune est la suivante: xxx

Il fonctionne jusqu'à ce que vous utilisiez jusqu'à Tailleof (p) octets .. Alors que les choses drôles se produisent (bienvenue dans la jungle) .

Explanation

avec Char * P Vous allouez de l'espace pour contenir un pointeur ( Tailleof (VOID *) BYTES) sur la pile. La bonne chose ici est d'allouer un tampon ou de spécifier simplement la taille du pointeur au moment de la compilation: xxx


6 commentaires

Votre premier exemple ne doit jamais fonctionner, même si vous utilisez moins de Tailleof (* p) octets, car snprintf ne copiera pas une chaîne dans le pointeur, mais la mémoire qui Le pointeur pointe vers . A char * p n'est pas identique à un charp [] . Dans votre deuxième exemple, * p est superflu, car buf pourrait être transmis à snaprintf directement pour rendre le code plus clair.


L'ancien exemple fonctionne, essayez-le avec un compilateur :) dans ce dernier, je sais que * p est superflu mais cela servira à propos de montrer "Comment allouer la mémoire"


Il fonctionne car * p , lors de la déclaration, contient une valeur aléatoire et indique donc à un segment de mémoire aléatoire pouvant être écritable, vous donnant ainsi l'illusion qu'il fonctionne lors de la rédaction de petites quantités de texte à cela, et pourquoi cela se casse lorsque vous essayez d'écrire trop.


En outre, je viens de l'essayer sur mon compilateur. Premier exemple: erreur bus . (GCC 4.0, OS X léopard)


Vous pouvez essayer avec un plus ancien compilateur sur une université plus ancienne, probablement MacOS Randomise Segments pour minimiser les mauvaises choses comme les débordements tampons, etc.


Des choses telles que "Works sur votre machine" ou "Vous pouvez le faire fonctionner sur une université plus ancienne" ne signifie pas que c'est correct. Votre code est un comportement indéfini conformément à la norme C, ce qui signifie qu'il pourrait fonctionner, il pourrait s'écraser ou effacer votre disque dur. C'est ce que c'est un comportement indéfini.



2
votes

confondre strlen () code> avec Tailleof () code> Lorsque vous utilisez une chaîne:

char *p = "hello!!";
strlen(p) != sizeof(p)


0 commentaires

1
votes

J'ai trouvé que la technique Char Buff [0] a été incroyablement utile. Considérons: xxx

vs xxx

voir https://stackoverflow.com/questions/295027

Voir le lien pour les implications et les variations


0 commentaires

0
votes

Je signalerais les pièges de performance de sur-dépendance sur les fonctions de chaîne intégrées.

char* triple(char* source)
{
   int n=strlen(source);
   char* dest=malloc(n*3+1);
   strcpy(dest,src);
   strcat(dest,src);
   strcat(dest,src);
   return dest;
 }


1 commentaires

... avec les pièges d'optimisation prématurée? :-)



1
votes

Je discuterais quand et quand ne pas utiliser strcpy code> et strncpy code> et que peut aller mal:

if (stricmp("StrInG 1", "string 1")==0)
{
    .
    .
    .
}


1 commentaires

stricmp () n'est pas une fonction standard ANSI C, il s'agit d'une extension fournie par MS VC ++ et peut-être d'autres implémentations. Dans GCC, la fonction s'appelle strcasecmpmp () (probablement la fois que je vais réellement avec Microsoft sur quelque chose), mais n'est toujours pas standard.



1
votes

Peut-être que vous pourriez illustrer la valeur de Sentinel '\ 0' avec l'exemple suivant

char * a = "bonjour \ 0 monde"; char b [100]; Strcpy (B, A); printf (b);

J'ai eu une fois que mes doigts brûlaient quand dans mon zèle, j'ai utilisé Strcpy () pour copier des données binaires. Cela a fonctionné la plupart du temps mais a échoué mystérieusement parfois. Le mystère a été révélé lorsque j'ai réalisé que l'entrée binaire contenait parfois un octet zéro et Strcpy () se terminerait là-bas.


2 commentaires

Vos doigts guérissent-ils?


Oh c'était il y a longtemps ... Depuis lors, j'ai même grandi de nouveaux ;-)



5
votes

Abuser Strlen () aggravera considérablement la performance.

int length = strlen( string );
for( int i = 0; i < length; i++ ) {
    processChar( string[i] );
}


2 commentaires

Mais le compilateur peut-il optimiser cela et n'accumule-t-il que vraiment la fonction strallen () une fois?


@jaska peut-être que ce sera peut-être peut-être pas - dépend de nombreux facteurs. La norme ne nécessite certainement pas l'optimisation de l'optimisation et il n'interdit ni une telle optimisation.



0
votes

Les pointeurs et les tableaux, tout en ayant la syntaxe similaire, ne sont pas du tout de même. Donné:

Char A [100]; char * p = a;

Pour le tableau, A, il n'y a pas de pointeur stocké nulle part. Tailleof (a)! = Tailleof (P), pour la matrice, il s'agit de la taille du bloc de la mémoire, pour le pointeur, c'est la taille du pointeur. Cela devient important si vous utilisez quelque chose comme: Tailleof (A) / Tailleof (A [0]). De plus, vous ne pouvez pas ++ A, et vous pouvez rendre le pointeur A 'const' pointeur des caractères «const», mais le tableau ne peut être que des caractères «const», auquel cas vous seriez l'introduction en premier. c etcc etc


0 commentaires

0
votes

Si possible, utilisez strlcpy (au lieu de Strncpy) et strlcat.

Mieux encore mieux, pour rendre la vie un peu plus sûre, vous pouvez utiliser une macro comme: p>

#define strlcpy_sz(dst, src) (strlcpy(dst, src, sizeof(dst)))


0 commentaires

2
votes

KMM a déjà une bonne liste. Voici les choses que j'ai eu des problèmes avec quand j'ai commencé à coder c.

  1. Les littéraux de chaîne ont une propre section de mémoire et sont toujours accessibles. Par conséquent, ils peuvent par exemple être une valeur de retour de la fonction.

  2. Gestion de la mémoire des chaînes, en particulier avec une bibliothèque de haut niveau (non libc). Qui est responsable de libérer la chaîne s'il est renvoyé par fonction ou transmis à une fonction?

  3. Quand faut-il "Const Char *" et quand "Char *" être utilisé. Et qu'est-ce que cela me dit si une fonction renvoie un "const char *".

    Toutes ces questions ne sont pas trop difficiles à apprendre, mais difficiles à comprendre si vous ne vous en avez pas appris.


1 commentaires

N'ayez pas à l'esprit que les littéraux de chaîne sont Const Char *, et il est un comportement indéfini si vous essayez de les changer.