11
votes

Traiter avec des tampons de char

En tant que programmeur C ++, j'ai parfois besoin de faire affaire avec des tampons de mémoire à l'aide de techniques de C. Par exemple:

TCHAR buffer[MAX_PATH+1]; // edit: +1 added
::GetCurrentDirectory(sizeof(buffer)/sizeof(TCHAR), &buffer[0]);
  • passe le tampon comme & tampon [0] code> meilleur style de programmation que de passer tampon code>? (Je préfère & tampon [0] code>.) Li>
  • Y a-t-il une taille maximale considérée comme sûre pour les tampons alloués à la pile?
    • Mise à jour: i> Je veux dire, par exemple, la valeur la plus élevée pouvant être considérée comme sûre pour les applications de bureau multiplate-forme sur Mac, Windows, Linux Desktops (pas mobile!). LI> ul> li>
    • est un tampon statique ( tampon de charme statique [N]; code>) plus rapidement? Y a-t-il d'autres arguments pour ou contre cela? LI>
    • Lorsque vous utilisez des tampons statiques, vous pouvez utiliser le type de retour const char * code>. Est-ce (généralement) une bonne ou une mauvaise idée? (Je me rends compte que l'appelant devra faire sa propre copie pour éviter que l'appel suivant modifierait la valeur de retour précédente.) LI>
    • Qu'en est-il d'utiliser Statique Char * tampon = Nouveau Char [N]; Code>, Ne jamais supprimer le tampon et la réutiliser sur chaque appel. Li>
    • Je comprends que l'allocation de tas doit être utilisée lorsque (1) traite avec de gros tampons ou (2) la taille maximale de la mémoire tampon est inconnue au moment de la compilation. Existe-t-il d'autres facteurs qui jouent dans la décision de la pile / de l'allocation de tas? LI>
    • Si vous préférez le sprintf_s code>, memcpy_s code>, ... des variantes? (Visual Studio a essayé de me convaincre de cela depuis longtemps, mais je veux un deuxième avis: P) Li> ul> p>

c c++

2 commentaires

Quel est le problème avec Boost :: System?


Vous avez oublié quelques lignes: nom d'utilisateur.Resize (1024, 'A'); nom d'utilisateur.insert (0, "World of Buffer Overflows!");


9 Réponses :


4
votes
  • C'est à vous de décider tampon est plus terres mais si c'était un vecteur , vous auriez besoin de faire et de tampon [0] Quoi qu'il en soit.
  • dépend de votre plate-forme prévue.
  • est-ce important? Avez-vous décidé que ce soit un problème? Écrivez le code qui est le plus facile à lire et à entretenir avant de vous inquiéter si vous pouvez l'obscurcer dans quelque chose de plus rapidement. Mais pour ce que cela vaut la peine, l'allocation sur la pile est très rapide (vous venez de changer la valeur du pointeur de la pile.)
  • Vous devriez utiliser std :: string . Si la performance devient un problème, vous pourrez réduire les allocations dynamiques en retournant simplement le tampon interne. Mais l'interface std :: string est bien plus agréable et plus sûre, et la performance est votre dernière préoccupation.
  • c'est une fuite de mémoire. Beaucoup diront que ça va, puisque le système d'exploitation est de toute façon, mais je pense que c'est une pratique terrible pour simplement fuir les choses. Utilisez un std :: vecteur , vous devez jamais faire une allocation brute! Si vous vous mettez dans une position où vous risquez de fuir (car cela doit être fait explicitement), vous le faites mal.
  • Je pense que votre (1) et (2) vient de le couvrir. L'allocation dynamique est presque toujours plus lente que la répartition des piles, mais vous devriez être plus préoccupé par le sens de votre situation.
  • Vous ne devriez pas utiliser celles du tout. Utilisez std :: chaîne , std :: stringstream , std :: copie , etc.

2 commentaires

J'oublie toujours que je peux utiliser un std :: Vecteur A Création d'un tampon avec Malloc / Nouveau. Merci de me rappeler :) J'accepte que les constructions C ++ doivent être préférées, mais j'aime faire une exception pour Sprintf / Printf. Je trouve plus élégant que de jeter mon code avec des opérateurs de flux.


@Stact: Vous devez utiliser Boost.Format puis, ou envelopper des choses dans une fonction comme boost :: lexical_cast .



3
votes

Vous avez beaucoup de questions! Je ferai de mon mieux pour répondre à un couple et vous donner un endroit pour rechercher les autres.

Y a-t-il une taille maximale considérée comme sûre pour les tampons de pile alloués?

Oui, mais la taille de la pile elle-même varie en fonction de la plate-forme sur laquelle vous travaillez. Voir Quand vous souciez-vous de la taille de la pile? pour une question très similaire .

est un tampon de charme statique [N]; plus rapide? Sommes il y a d'autres arguments pour ou contre cela?

Le sens de statique dépend de l'endroit où le tampon est déclaré, mais je suppose que vous parlez d'un statique déclaré dans une fonction, il est donc initialisé une seule fois. Dans les fonctions appelées plusieurs fois, l'utilisation de tampons statiques peut être une bonne idée de prévenir le débordement de la pile, mais autrement gardez à l'esprit que l'allocation de tampons est une opération bon marché. En outre, les tampons statiques sont beaucoup plus difficiles à travailler lorsque vous traitez avec plusieurs threads.

Pour réponses à la plupart de vos autres questions, voir Grands tampons VS Grands tampons statiques, y a-t-il un avantage? .


0 commentaires

8
votes
  1. Éloignez-vous des tampons statiques si vous souhaitez utiliser votre code à nouveau.

  2. Utilisez snprintf () au lieu de sprintf () afin que vous puissiez contrôler des dépassements tampons.

  3. Vous ne savez jamais combien d'espace de pile est laissé dans le contexte de votre appel - aucune taille n'est donc techniquement «sûre». Vous avez beaucoup de poussée pour jouer avec la plupart du temps. Mais cela vous fera de bonnes. J'utilise une règle de base pour ne jamais mettre des tableaux sur la pile.

  4. Demandez au client le tampon et le transmettez-le et sa taille à votre fonction. Cela le rend à nouveau entrant et ne laisse aucune ambiguïté quant à qui doit gérer la vie du tampon.

  5. Si vous traitez des données de chaîne, vérifiez deux fonctions de chaîne pour vous assurer qu'ils se terminent surtout lorsqu'ils touchent la fin du tampon. La bibliothèque C est très incompatible lorsqu'il s'agit de manipuler la résiliation des chaînes dans les différentes fonctions.


8 commentaires

Ne peut pas être d'accord sur # 3. Apprenez à connaître votre code! Sans tampons de pile-alloués, vous devez toujours utiliser un mutex dans une application multi-threadée, et j'essaie personnellement aussi fort que possible pour éviter le mutex ou, en d'autres termes, écrivez un code sans verrouillage. Performance garantie. Si vous ne connaissez pas le comportement de votre application, vous êtes dans un problème (:


@Poni: Le verrouillage de Mutex n'est pas la seule alternative aux tampons de pile lors de la réintensif. Vous pouvez passer des tampons à partir de l'appelant, utilisez directement le tas ou utilisez des pools de tampons de tas pré-alloués.


Pourtant, tant que vous accédez à la même collection (que le tas est à la fin de la journée) de manière multi-filetée, vous aurez besoin de synchroniser. (Presque) aucun moyen de l'éviter.


@Poni: La propriété tampon n'a rien à voir avec le partage des données entre les threads. Si un contexte de fonction nécessite un tampon unique pour effectuer son travail, cela n'a pas d'importance d'un point de vue de synchronisation s'il provient de l'appelant, du tas ou de la pile. Si vous partageiez des tampons de pile entre les threads (mauvais pratique, mais certains le font), vous aurez toujours besoin de synchronisation. Si chaque fil est passé dans son propre tampon unique mais que je ne l'ai pas partagé, aucune synchronisation n'est nécessaire.


Vous avez pris le sujet dans une direction complète qui n'est pas pertinente pour le poste d'origine. Je vais le mettre dans ces mots: votre fonction doit lire 1 000 octets d'un fichier. À la fin de cette fonction, vous n'avez pas besoin de ces données. Où alloueriez-vous un tampon pour la lecture?


@Poni: OP parlait de créer un tampon sur la pile pour une utilisation locale. Mon point n ° 3 est qu'une fonction ne peut pas dire à l'avance si elle va épuiser la pile du fil d'appel lorsque cela le faisait. L'origine de ces 1 000 octets devrait être du tas ou un pointeur tampon transmis de l'appelant si vous souhaitez minimiser l'empreinte automatique variable.


Du tas! @ ?! ... Vous devez confuser mon ami!


@Amardeep: Techniquement parlant, si la pile est pleine, tout objet local peut trop déborder, peu importe la taille. Mais je conviens que plus l'objet local est grand, plus le débordement est de plus de chances.



0
votes
  • tampon ou tampon [0] est exactement le même. Vous pouvez même écrire tampon + 0. Personnellement, je préfère simplement écrire un tampon (et je pense que la plupart des développeurs préfèrent également cela), mais c'est votre choix personnel
  • Le maximum dépend de la taille et de la profondeur de votre pile. Si vous avez déjà 100 fonctions profondément dans la pile, la taille maximale sera plus petite. Si vous pouvez utiliser C ++, vous pouvez écrire une classe tampon qui choisit de manière dynamique s'il faut utiliser la pile (pour les petites tailles) ou le tas (pour les grandes tailles). Vous trouverez le code ci-dessous.
  • Un tampon statique est plus rapide puisque le compilateur réservera l'espace à l'avance. Un tampon de pile est également rapide. Pour un tampon de pile, l'application doit simplement augmenter le pointeur de la pile. Pour un tampon de tas, le gestionnaire de mémoire doit trouver un espace libre, poser le système d'exploitation pour la nouvelle mémoire, puis libérer à nouveau, faites-le de la comptabilité, ...
  • Si possible, utilisez C ++ cordes pour éviter les fuites de mémoire. Sinon, l'appelant doit savoir s'il doit libérer la mémoire par la suite ou non. L'inconvénient est que les chaînes C ++ sont plus lentes que les tampons statiques (car ils sont alloués sur le tas).
  • Je n'utiliserais pas l'allocation de mémoire sur les variables globales. Quand allez-vous la supprimer? Et pouvez-vous être sûr qu'aucune autre variable globale n'aura besoin de la mémoire allouée (et d'être utilisée avant que votre tampon statique soit alloué)?
  • Quel que soit le type de tampon que vous utilisez, essayez de masquer la mise en œuvre à partir de l'appelant de votre fonction. Vous pouvez essayer de masquer le pointeur tampon dans une classe laissez la classe rappelez-vous si le tampon est alloué de manière dynamique ou non (et devrait donc la supprimer dans son destructeur ou non). Ensuite, il est facile de changer le type de tampon que vous ne pouvez pas faire si vous venez de retourner un pointeur de char.
  • Personnellement, je préfère les variantes Sprintf normales, mais c'est probablement parce que j'ai encore beaucoup de code plus âgé et je ne veux pas une situation mixte. En tout état de cause, envisagez d'utiliser SnPRINTF, où vous pouvez passer la taille de la mémoire tampon.

    code pour la pile dynamique / tampon de tas: xxx


0 commentaires

1
votes
Passe le tampon comme tampon et [0] meilleur style de programmation que de passer tampon? (Je préfère et solutions tampons [0].)

& tampon [0] rend le code moins lisible pour moi. Je dois faire une pause pour une seconde et je me demande pourquoi quelqu'un a utilisé au lieu de simplement passer tampon . Parfois, vous devez utiliser et solutions tampons [0] (si le tampon est un std :: vector ), mais sinon, bâton avec le style standard C .

Y at-il une taille maximale qui est considéré comme sûr pour les tampons de pile alloué?

Je doute qu'il y ait une limite pratique, aussi longtemps que vous utilisez la pile raisonnablement. Je ne l'ai jamais eu aucun problème dans mon développement.

Si je lis MSDN correctement, les discussions sur Windows par défaut 1 Mo de taille de la pile. Ceci est configurable. D'autres plates-formes ont d'autres limites.

Est-tampon statique char [N]; plus rapide? Y at-il d'autres arguments pour ou contre?

D'une part, il pourrait réduire la nécessité d'engager des pages de mémoire pour la pile, de sorte que votre application peut courir plus vite. D'autre part, en allant au segment BSS ou équivalent pourrait réduire la localité de cache par rapport à la pile, de sorte que votre application peut fonctionner plus lentement. Je doute sérieusement que vous auriez remarqué la différence dans les deux cas.

Utilisation statique ne sont pas threadsafe, tout en utilisant la pile est. C'est un énorme avantage pour la pile. (Même si vous ne pensez pas que vous allez multithread, pourquoi rendre la vie plus difficile que si des changements dans l'avenir?)

Lorsque vous utilisez des tampons statiques vous pouvez avoir votre retour de la fonction du const char * type de retour. Est-ce une bonne idée? (Je me rends compte que l'appelant devra faire sa propre copie pour éviter que le prochain appel changerait la valeur de retour précédente.)

Const correct est toujours une bonne chose. < / p>

Le retour des pointeurs vers des tampons statiques est sujette à l'erreur; un appel plus tard pourrait le modifier, un autre thread peut modifier, utiliser std :: string au lieu ou tout autre lieu mémoire allouée automatique (même si vos besoins fonctionnels pour traiter en interne avec des tampons char tels que votre GetCurrentDirectory par exemple.)

Qu'en est-il en utilisant char statique * buffer = new char [N]; et ne jamais supprimer le tampon? (Réutilisation le même tampon chaque appel.)

Moins efficace que d'utiliser simplement char buffer statique [N] , puisque vous avez besoin d'une allocation de tas.

Je comprends que l'allocation de tas doit être utilisé lorsque (1) traitant de grands tampons ou (2) la taille de la mémoire tampon maximale est inconnue au moment de la compilation. Y a-t-il d'autres facteurs qui jouent dans la pile / décision d'attribution tas?

Voir la réponse de Justin Ardini.

Si vous préférez les sprintf_s, memcpy_s, ... Des variantes? (Visual Studio a essayé de me convaincre de cela depuis longtemps, mais je veux un deuxième avis: p)

Ceci est une question de débat. Personnellement, je pense que ces fonctions sont une bonne idée, et si vous ciblez de Windows exclusivement, alors il y a un certain avantage à prendre l'approche de Windows préférée et l'utilisation de ces fonctions. (Et ils sont assez simples à réimplémentez si vous avez besoin plus tard pour cible autre chose que Windows, aussi longtemps que vous ne comptez pas sur leur comportement de gestion des erreurs). D'autres pensent que les fonctions CRT sécurisés sont plus sûrs que correctement utilisé C et introduire d'autres inconvénients; liens Wikipédia à quelques arguments contre eux.


0 commentaires

0
votes

passe le tampon comme et tampon [0] Meilleur style de programmation que le tampon de passage? (Je préfère et tampon [0].)

Cela dépend des normes de codage. Je préfère personnellement: tampon + index au lieu de & tampon [index] mais c'est une question de goût.

y a-t-il une taille maximale qui est considéré comme sûr pour la pile des tampons alloués?

Cela dépend de la taille de la pile. Si la quantité de pile nécessaire à votre tampon dépasse le montant disponible sur la pile, il résulte d'un Stack-Overflow < /a>.



> Tampon de charme statique [N]; plus rapide? Y a-t-il d'autres arguments pour ou contre cela?

Oui, il devrait être plus rapide. Voir aussi cette question: Est-il une mauvaise pratique de déclarer une fonction matrice moyenne

lorsque vous utilisez des tampons statiques, vous pouvez avoir votre retour de fonction dispose du type de retour de Cons-Char *. Est-ce une bonne idée? (Je me rends compte que l'appelant devra faire sa propre copie pour éviter que l'appel suivant modifierait la valeur de retour précédente.)

Je ne sais pas quels statiques signifie dans ce cas mais: < / p>

  1. Si la variable est déclarée sur pile ( char buf [100] ): Vous devriez pas renvoyer des références à des trucs qui sont déclarés sur la pile. Ils seront détruits à la prochaine fonction Call / Déclaration (par exemple, lorsque la pile est utilisée à nouveau).

  2. Si la variable est déclarée sous forme statique statique il rendra votre code non réentrant. Strtok est un exemple dans ce cas.

    Qu'en est-il de l'utilisation de la mémoire tampon Static Char * = Nouveau Char [N]; et ne jamais supprimer le tampon? (Réutiliser le même tampon chaque appel.)

    C'est une possibilité, bien que non recommandée car il rend votre code non réentrant .

    Je comprends que l'allocation de tas doit être utilisée lorsque (1) traitant de gros tampons ou (2 ) La taille maximale de la mémoire tampon est inconnue au moment de la compilation. Y a-t-il d'autres facteurs qui jouent dans la décision de la pile / de l'allocation de tas?

    La taille de la pile du thread en marche est trop petite pour s'adapter à la déclaration de pile (mentionnée précédemment).

    Devriez-vous préférer le sprintf_s, memcpy_s, ... Variantes? (Visual Studio a essayé de me convaincre de cela pendant une longue période, mais je veux une seconde opinion: p)

    Si vous voulez que votre code soit portable: Non. En créant une macro portable est assez faible dans ce cas: xxx


1 commentaires

Votre idée de MCAO pour la portabilité ne fonctionnera pas, car la plupart des versions _s prennent des arguments supplémentaires.



1
votes

Si une fonction vous donne une méthode de savoir combien de caractères il retournera, utilisez-le. Votre exemple GetCurrentDirectory est un bon exemple:

std::vector<TCHAR> buffer(length, 0);
// assert(buffer.capacity() >= length);  // should always be true
GetCurrentDirectory(length, &buffer[0]);


0 commentaires

7
votes

Je suppose que votre intérêt vient principalement d'une perspective de performance, car des solutions telles que le vecteur, la chaîne, le wstring, etc. fonctionneront généralement même pour interagir avec C API. Je recommande d'apprendre à utiliser ceux-ci et comment les utiliser efficacement. Si vous en avez vraiment besoin, vous pouvez même écrire votre propre allocateur de mémoire pour les rendre super rapides. Si vous êtes sûr qu'ils ne sont sûrs qu'ils ne sont pas ce dont vous avez besoin, il n'y a toujours aucune excuse pour que vous n'écrivez pas à ne pas écrire une simple enveloppe pour gérer ces tampons de cordes avec Raii pour les cas dynamiques.

avec ça hors de la route:

passe le tampon comme et tampon [0] Meilleur style de programmation que de passer amortir? (Je préfère et tampon [0].)

non. Je considérerais que ce style sera légèrement moins utile (certes être subjectif ici) car vous ne pouvez pas l'utiliser pour passer un tampon NULL et qu'il faudrait donc faire des exceptions à votre style pour passer des pointeurs à des tableaux qui peuvent être nuls. Il est requis si vous passez dans des données de STD :: Vecteur vers une API C S'attendant à un pointeur, cependant.

Y a-t-il une taille maximale qui est considéré comme sûr pour la pile alloué tampons?

Cela dépend de vos paramètres de plate-forme et de votre compilateur. Règle simple: Si vous doutez de savoir si votre code déborde de la pile, écrivez-le d'une manière qui ne peut pas.

est un tampon statique (charcuterie statique tampon [n];) plus rapide? Y a-t-il autres arguments pour ou contre cela?

Oui, il y a un grand argument contre celui-ci, et c'est que cela rend votre fonction ne ré-entrant plus. Si votre application devient multipliée, ces fonctions ne seront pas en sécurité. Même dans une application à une seule-filetée, partager le même tampon lorsque ces fonctions sont appelées de manière récursive peut entraîner des problèmes.

Qu'en est-il de l'utilisation du tampon de char * statique = nouveau char [n]; et ne jamais supprimer le tampon? (Réutiliser le même tampon chacun appel.)

Nous avons toujours les mêmes problèmes avec la réintensif.

Je comprends cette allocation de tas devrait être utilisé quand (1) traitant de grands tampons ou (2) tampon maximum la taille est inconnue au moment de la compilation. Sommes Il y a d'autres facteurs qui jouent dans La décision de la pile / de l'allocation de tas?

La pile déroulant détruit les objets sur la pile. Ceci est particulièrement important pour la sécurité des exceptions. Ainsi, même si vous allouez la mémoire sur le tas dans une fonction, elle devrait généralement être gérée par un objet sur la pile (ex: pointeur intelligent). //// @ Voir Raii.

Si vous préférez le sprintf_s, memcpy_s, ... Variantes? (Visual Studio a essayé de me convaincre de cela Pendant longtemps, mais je veux une seconde AVIS: P)

MS a eu raison à propos de ces fonctions étant des alternatives plus sûres, car elles n'ont pas de problèmes de dépassement de tampon, mais si vous écrivez ce code comme (sans écriture de variantes pour d'autres plates-formes), votre code sera marié à Microsoft puisque elle être non portable.

Lorsque vous utilisez des tampons statiques, vous pouvez utiliser Type de retour Const de caractères *. Est-ce (généralement) une bonne ou une mauvaise idée? (JE se rend compte que l'appelant aura besoin faire sa propre copie pour éviter que le appel suivant changerait le précédent Valeur de retour.)

Je dirais dans presque tous les cas, vous souhaitez utiliser Const Char * pour les types de retour pour une fonction renvoyant un pointeur sur un tampon de caractères. Pour une fonction de retourner un caractère mutable * est généralement déroutant et problématique. Soit il renvoie une adresse à des données globales / statiques qu'il ne devrait pas utiliser en premier lieu (voir Recettentrance ci-dessus), les données locales d'une classe (s'il s'agit d'une méthode) dans laquelle le renvoi de la capacité de la classe à Maintenir les invariants en permettant aux clients de l'altérer, mais ils aiment (ex: la chaîne stockée doivent toujours être valides) ou la mémoire de retour spécifiée par un pointeur transmise à la fonction (le seul cas où l'on pourrait raisonnablement affirmer que le charnel mutable * devrait être retourné).


0 commentaires

2
votes

1) tampon et & tampon [0] devrait être équivalent.

2) Les limites de la taille de la pile dépendront de votre plate-forme. Pour la plupart des fonctions simples, ma règle de base personnelle est quelque chose de plus de ~ 256kb est déclarée dynamiquement; Il n'y a pas de véritable rime ni raison de ce nombre, cependant, c'est juste ma propre convention et il est actuellement dans les tailles de pile par défaut pour toutes les plates-formes que je développe.

3) Les tampons statiques ne sont pas plus rapides ou plus lents (à toutes fins utiles). La seule différence est le mécanisme de contrôle d'accès. Le compilateur place généralement des données statiques dans une section distincte du fichier binaire que des données non statiques, mais il n'existe aucun avantage notable / performance significatif ou pénalité impliqué. Le seul moyen réel de dire certitude est d'écrire le programme à la fois et de les utiliser (car de nombreux aspects de vitesse impliqués ici dépendent de votre plate-forme / compilateur).

4) Ne renvoyez pas un pointeur const si l'appelant devra le modifier (qui défait le point de const ). Utilisez const pour les paramètres de fonction et les types de retour si et uniquement s'ils ne sont pas conçus pour être modifiés. Si l'appelant devra modifier la valeur, votre meilleur pari est que l'appelant passe la fonction un pointeur sur un tampon pré-alloué (ainsi que la taille du tampon) et pour la fonction d'écrire les données dans ce tampon.

5) La réutilisation d'un tampon peut entraîner une amélioration des performances des plus gros tampons dues à contourner la surcharge impliquée dans l'appelant Malloc / ou neuf < / code> / Supprimer à chaque fois. Cependant, vous risquez d'utiliser des données anciennes si vous oubliez d'effacer le tampon à chaque fois ou que vous essayez d'exécuter deux copies de la fonction en parallèle. Encore une fois, la seule façon réelle de savoir, bien sûr, c'est d'essayer les deux manières et de mesurer la durée pendant laquelle le code prend pour courir.

6) Un autre facteur d'allocation de pile / de tas est de la scoraison. Une variable de pile dépasse de la portée lorsque la fonction qu'il vit en retour, mais une variable allouée de manière dynamique sur le tas peut être renvoyée à l'appelant en toute sécurité ou accédée à la prochaine fois que la fonction est appelée (A strtok ).

7) Je recommanderais à l'utilisation de sprintf_s , memcpy_s et amis. Ils ne font pas partie de la bibliothèque standard et ne sont pas portables. Plus vous utilisez ces fonctions, plus vous aurez de travail supplémentaire lorsque vous souhaitez exécuter votre code sur une autre plate-forme ou utiliser un compilateur différent.


1 commentaires

Bien sûr, le commentaire dans N ° 6 ne s'applique pas aux variables de pile déclarées comme statique .